rork-toolkit-docs

ربط Rork مع Chatwoot Captain

الحكم

Rork لا يُركب مباشرة كـOpenAI Base URL داخل Chatwoot Captain، لأن:

الحل الصحيح هو Internal Adapter/Gateway.

المعمارية الموصى بها

Chatwoot Captain
      |
      | OpenAI-compatible requests
      v
Rork Captain Adapter
  - Authentication
  - Tenant isolation
  - OpenAI <-> Rork mapping
  - Stream translation
  - Tool-call state
  - Validation
  - Timeouts/retries
  - Logs/metrics
  - Fallback provider
      |
      v
Rork /agent/chat

Endpoint يقدمه الـAdapter لـChatwoot

الحد الأدنى:

POST /v1/chat/completions
GET  /v1/models
GET  /health

اختياري حسب إعداد Captain:

POST /v1/embeddings

لا توجه /v1/embeddings إلى Rork؛ استخدم مزود Embeddings منفصلًا.

المسار الذي يستعمله الـAdapter في Rork

POST https://toolkit.rork.com/agent/chat

استخدم /llm/text فقط لمسارات Text بسيطة لا تحتاج Tools أو streaming حقيقي.


Mapping الرسائل

OpenAI user message

Input من Chatwoot:

{
  "role": "user",
  "content": "Where is order ORD-42?"
}

Rork UIMessage:

{
  "id": "cw-msg-123",
  "role": "user",
  "parts": [
    {
      "type": "text",
      "text": "Where is order ORD-42?"
    }
  ]
}

OpenAI assistant text

{
  "role": "assistant",
  "content": "I will check the order."
}

Rork:

{
  "id": "assistant-123",
  "role": "assistant",
  "parts": [
    {
      "type": "text",
      "text": "I will check the order."
    }
  ]
}

System prompt

Rork Agent يقبل system role ضمن model messages في الاختبارات، لكن UI message transport يجب اختباره مع نسخة الـSDK المستخدمة. أبسط تصميم للـAdapter:


Mapping تعريف الأدوات

OpenAI tool

{
  "type": "function",
  "function": {
    "name": "lookup_order",
    "description": "Look up an order by ID",
    "parameters": {
      "type": "object",
      "properties": {
        "orderId": {"type": "string"}
      },
      "required": ["orderId"],
      "additionalProperties": false
    }
  }
}

Rork tool

{
  "lookup_order": {
    "description": "Look up an order by ID",
    "jsonSchema": {
      "type": "object",
      "properties": {
        "orderId": {"type": "string"}
      },
      "required": ["orderId"],
      "additionalProperties": false
    }
  }
}

Mapping:

function openAIToolsToRork(tools: OpenAITool[]) {
  return Object.fromEntries(
    tools
      .filter(tool => tool.type === "function")
      .map(tool => [
        tool.function.name,
        {
          description: tool.function.description ?? "",
          jsonSchema: tool.function.parameters
        }
      ])
  );
}

قبل الإرسال:


Mapping Tool Call

Rork event:

{
  "type": "tool-input-available",
  "toolCallId": "call_123",
  "toolName": "lookup_order",
  "input": {
    "orderId": "ORD-42"
  }
}

OpenAI-compatible non-stream response part:

{
  "id": "chatcmpl-rork-123",
  "object": "chat.completion",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_123",
            "type": "function",
            "function": {
              "name": "lookup_order",
              "arguments": "{\"orderId\":\"ORD-42\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

OpenAI stream chunk:

{
  "choices": [
    {
      "index": 0,
      "delta": {
        "tool_calls": [
          {
            "index": 0,
            "id": "call_123",
            "type": "function",
            "function": {
              "name": "lookup_order",
              "arguments": "{\"orderId\":\"ORD-42\"}"
            }
          }
        ]
      },
      "finish_reason": null
    }
  ]
}

لا ترسل OpenAI tool call قبل اكتمال tool-input-available إلا إذا نفذت parser مضبوطًا لتجميع tool-input-delta.


طريقتان لتنفيذ Tools

A. Chatwoot ينفذ Tool

Rork tool event
      |
Adapter maps to OpenAI tool_call
      |
Chatwoot executes Captain custom tool
      |
Chatwoot sends role=tool result
      |
Adapter maps result back to Rork

هذه الطريقة مفضلة عندما تريد الاستفادة من:

B. الـAdapter ينفذ Tool

Rork tool event
      |
Adapter validates and executes tool
      |
Adapter appends Rork output-available
      |
Rork returns final text
      |
Adapter sends only final answer to Chatwoot

هذه الطريقة مفيدة عندما تكون الأدوات داخلية عندك أو عندما لا يدعم مسار Captain المستخدم tool round-trip بالشكل المطلوب.

التوصية

ابدأ بـA إن كانت نسخة Chatwoot الحالية عندك تدعم tool calls مع custom endpoint بشكل كامل. استخدم B للأدوات الخاصة بالـERP/OpenWA أو عندما تحتاج تحكمًا أدق في التنفيذ.


Mapping Tool Result إلى Rork

Chatwoot/OpenAI message:

{
  "role": "tool",
  "tool_call_id": "call_123",
  "content": "{\"status\":\"shipped\",\"trackingNumber\":\"TRK-9001\"}"
}

الـAdapter يحتاج state محفوظًا من الطلب السابق:

{
  "toolCallId": "call_123",
  "toolName": "lookup_order",
  "input": {
    "orderId": "ORD-42"
  }
}

ثم يبني Rork assistant tool part:

{
  "id": "assistant-tool-result-123",
  "role": "assistant",
  "parts": [
    {
      "type": "tool-lookup_order",
      "toolCallId": "call_123",
      "state": "output-available",
      "input": {
        "orderId": "ORD-42"
      },
      "output": {
        "status": "shipped",
        "trackingNumber": "TRK-9001"
      }
    }
  ]
}

ثم يعيد إرسال history إلى /agent/chat للحصول على final answer.


Streaming text mapping

Rork:

{
  "type": "text-delta",
  "id": "gen-1",
  "delta": "Hello"
}

OpenAI SSE:

data: {"id":"chatcmpl-rork-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}

Rork finish:

{
  "type": "finish",
  "finishReason": "stop"
}

OpenAI finish:

data: {"id":"chatcmpl-rork-1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

State store المطلوب

Key مقترح:

chatwoot:{account_id}:{conversation_id}

البيانات:

{
  "rorkMessages": [],
  "pendingToolCalls": {},
  "lastRequestId": "...",
  "updatedAt": "..."
}

استخدم Redis أو storage مشترك؛ لا تعتمد على memory داخل process واحد.

Timeout policy

المرحلة اقتراح مبدئي
Rork TTFB 5s
Text completion 30s
Tool execution 10–20s حسب الأداة
Image operations 30–60s
Total Captain turn حد مناسب لتجربة Chatwoot

Embeddings وKnowledge Base

لم يُكتشف Rork embeddings endpoint. افصل المسار:

Captain chat completion -> Rork Adapter
Knowledge embeddings -> OpenAI/other embedding provider

لا تحاول تزييف /v1/embeddings باستخدام Text Generation.

Headers داخلية مفيدة

X-Chatwoot-Account-Id
X-Chatwoot-Conversation-Id
X-Chatwoot-Inbox-Id
X-Request-Id

تحقق منها داخل شبكة موثوقة فقط، ولا تقبلها كهوية من الإنترنت دون توقيع أو auth.

Acceptance criteria للـAdapter