أدوات جهة العميل
عرض بصيغة Markdown# أدوات جهة العميل
> دع الوكيل يستخدم دوال تعمل داخل تطبيقك — عرّفها، وتعامل مع requires_action، وأرسل نتائج الأدوات لإكمال الرد.
**أداة جهة العميل** دالة يشغّلها تطبيقك وينتظرها K-Agent: قراءة سلة العميل في المتصفح، أو فحص حالة جهاز في تطبيق الجوال، أو استدعاء نظام لا يستطيع K-Agent الوصول إليه. يقرر النموذج متى يستدعيها؛ ويشغّلها كودك ويعيد النتيجة؛ ثم يكمل الوكيل رده.
## 1. عرّف الأداة في الوكيل
```bash
curl -X PATCH https://api.k-agent.kerneltics.com/v1/agents/store-assistant \
-H "Authorization: Bearer $KAGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "If-Match: $ETAG" \
-d '{
"config": {
"tools": {
"client": [{
"name": "get_cart",
"description": "يعيد المنتجات في سلة العميل مع أسعارها.",
"parameters": { "type": "object", "properties": {}, "additionalProperties": false },
"effect": "read",
"rules": "استدعها قبل ذكر رسوم التوصيل، فهي تعتمد على إجمالي السلة."
}]
}
}
}'
```
ثم انشر الوكيل. ملاحظات:
- `name` يطابق `^[a-z][a-z0-9_]{0,47}$` ويجب ألا يتعارض مع أدوات الوكيل الأخرى.
- `parameters` مخطط JSON؛ ويُتحقق من المعاملات بصرامة قبل أن يراها تطبيقك.
- `effect` افتراضيًا `action`. اجعله `read` للأدوات التي تستعلم فقط، فتعمل أيضًا للمستخدمين المجهولين وفي طلبات السؤال الواحد دون `allow_actions`.
## 2. يتوقف التشغيل مؤقتًا بحالة `requires_action`
حين يستدعي النموذج الأداة يتوقف التشغيل وينتظرك:
```json
{
"id": "run_01k6rz7d9f1h3k5n7q9s1v3x5z",
"object": "run",
"status": "requires_action",
"outcome": null,
"required_action": {
"type": "submit_tool_outputs",
"tool_calls": [{ "call_id": "call_8f2k1", "name": "get_cart", "arguments": {} }],
"expires_at": 1791272520
}
}
```
- دون بث، يعيد `POST …/messages` (أو `ask`) هذا التشغيل بالحالة `200`.
- مع البث، ينتهي البث بحدث `run.requires_action` يحمل `run` فيه `required_action`.
- التشغيل المتوقف يحتفظ بدور الجلسة: مع `queue` تنتظر الرسائل الجديدة خلفه، ومع `reject` تحصل على `409 session_busy`.
## 3. شغّل الأداة وأرسل النتائج
أجب عن **كل** استدعاء في `tool_calls`، خلال 10 دقائق:
**JavaScript**
```js
const BASE = 'https://api.k-agent.kerneltics.com/v1';
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
// Your implementations, keyed by tool name.
const clientTools = {
get_cart: async () => ({ items: [{ name: 'صندوق بخور', qty: 1, price_sar: 95 }], subtotal_sar: 95 }),
};
async function send(session, input) {
let res = await fetch(`${BASE}/sessions/${encodeURIComponent(session)}/messages`, {
method: 'POST',
headers,
body: JSON.stringify({ input, client_message_id: crypto.randomUUID() }),
});
let { run } = await res.json();
// A run can pause more than once: keep answering until it finishes.
while (run?.status === 'requires_action') {
const tool_outputs = await Promise.all(
run.required_action.tool_calls.map(async (call) => {
try {
const output = await clientTools[call.name](call.arguments);
return { call_id: call.call_id, output, is_error: false };
} catch (err) {
return { call_id: call.call_id, output: { message: String(err) }, is_error: true };
}
}),
);
res = await fetch(`${BASE}/runs/${run.id}/submit_tool_outputs`, {
method: 'POST',
headers,
body: JSON.stringify({ tool_outputs }),
});
run = await res.json(); // submit_tool_outputs returns the run
}
return run; // completed: read run.output_text and run.outcome
}
```
**Python**
```python
import uuid, requests
BASE = "https://api.k-agent.kerneltics.com/v1"
HEADERS = {"Authorization": f"Bearer {token}"}
# Your implementations, keyed by tool name.
CLIENT_TOOLS = {
"get_cart": lambda args: {"items": [{"name": "صندوق بخور", "qty": 1, "price_sar": 95}], "subtotal_sar": 95},
}
def send(session: str, text: str) -> dict:
r = requests.post(f"{BASE}/sessions/{session}/messages", headers=HEADERS,
json={"input": text, "client_message_id": str(uuid.uuid4())}, timeout=120)
run = r.json()["run"]
# A run can pause more than once: keep answering until it finishes.
while run and run["status"] == "requires_action":
outputs = []
for call in run["required_action"]["tool_calls"]:
try:
outputs.append({"call_id": call["call_id"],
"output": CLIENT_TOOLS[call["name"]](call["arguments"]),
"is_error": False})
except Exception as exc:
outputs.append({"call_id": call["call_id"], "output": {"message": str(exc)}, "is_error": True})
r = requests.post(f"{BASE}/runs/{run['id']}/submit_tool_outputs", headers=HEADERS,
json={"tool_outputs": outputs}, timeout=120)
run = r.json() # submit_tool_outputs returns the run
return run # completed: read run["output_text"] and run["outcome"]
```
**curl**
```bash
curl https://api.k-agent.kerneltics.com/v1/runs/run_01k6rz7d9f1h3k5n7q9s1v3x5z/submit_tool_outputs \
-H "Authorization: Bearer $KAGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool_outputs": [
{ "call_id": "call_8f2k1", "output": { "items": [{ "name": "صندوق بخور", "qty": 1, "price_sar": 95 }], "subtotal_sar": 95 }, "is_error": false }
]
}'
```
- `output` أي قيمة JSON. اجعل `is_error: true` حين تفشل الأداة، ليتعامل النموذج مع الموقف بلباقة (مثل سؤال العميل أو التحويل لموظف).
- يقبل `submit_tool_outputs` الحقلين `stream: true` و`wait_seconds` مثل أي طلب تشغيل آخر، ويعيد التشغيل.
- النتائج الناقصة تعيد `422 tool_outputs_incomplete`. والإرسال إلى تشغيل لا ينتظر يعيد `409 run_not_requires_action`.
- إذا لم يصل شيء خلال 10 دقائق ينتهي التشغيل `failed` بالرمز `tool_outputs_expired` (دون رد طوارئ ودون تحويل). ورسالة العميل التالية تبدأ تشغيلًا جديدًا.
## من المتصفح
تستطيع رموز العميل استدعاء `submit_tool_outputs` لتشغيلات جلساتها، فيستطيع تطبيق الويب الإجابة عن أدوات جهة العميل مباشرة في المتصفح — مثل قراءة سلة لا توجد إلا هناك. أما خطوات التشغيل (`/steps`) فتبقى خاصة بخادمك.
## معلومات مفيدة
- لا تُعرض أدوات جهة العميل في طلبات `store: false`.
- مع [الواجهة المتوافقة مع OpenAI](/docs/guides/openai-sdk/#الأدوات) تعرض الطلبات بلا حالة أدوات جهة العميل استدعاءاتٍ عادية في `tool_calls` مع `finish_reason: "tool_calls"`؛ وينتهي التشغيل بالنتيجة `client_tool_calls`، ويكون طلبك التالي تشغيلًا جديدًا.
- `requires_action` حالة وليست نتيجة أبدًا: التشغيل المتوقف ينتهي لاحقًا دائمًا بـ `completed` أو `failed` أو `cancelled` أو `superseded`.
أداة جهة العميل دالة يشغّلها تطبيقك وينتظرها K-Agent: قراءة سلة العميل في المتصفح، أو فحص حالة جهاز في تطبيق الجوال، أو استدعاء نظام لا يستطيع K-Agent الوصول إليه. يقرر النموذج متى يستدعيها؛ ويشغّلها كودك ويعيد النتيجة؛ ثم يكمل الوكيل رده.
1. عرّف الأداة في الوكيل
رابط القسم «1. عرّف الأداة في الوكيل»curl -X PATCH https://api.k-agent.kerneltics.com/v1/agents/store-assistant \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -H "If-Match: $ETAG" \ -d '{ "config": { "tools": { "client": [{ "name": "get_cart", "description": "يعيد المنتجات في سلة العميل مع أسعارها.", "parameters": { "type": "object", "properties": {}, "additionalProperties": false }, "effect": "read", "rules": "استدعها قبل ذكر رسوم التوصيل، فهي تعتمد على إجمالي السلة." }] } } }'ثم انشر الوكيل. ملاحظات:
nameيطابق^[a-z][a-z0-9_]{0,47}$ويجب ألا يتعارض مع أدوات الوكيل الأخرى.parametersمخطط JSON؛ ويُتحقق من المعاملات بصرامة قبل أن يراها تطبيقك.effectافتراضيًاaction. اجعلهreadللأدوات التي تستعلم فقط، فتعمل أيضًا للمستخدمين المجهولين وفي طلبات السؤال الواحد دونallow_actions.
2. يتوقف التشغيل مؤقتًا بحالة requires_action
رابط القسم «2. يتوقف التشغيل مؤقتًا بحالة requires_action»حين يستدعي النموذج الأداة يتوقف التشغيل وينتظرك:
{ "id": "run_01k6rz7d9f1h3k5n7q9s1v3x5z", "object": "run", "status": "requires_action", "outcome": null, "required_action": { "type": "submit_tool_outputs", "tool_calls": [{ "call_id": "call_8f2k1", "name": "get_cart", "arguments": {} }], "expires_at": 1791272520 }}- دون بث، يعيد
POST …/messages(أوask) هذا التشغيل بالحالة200. - مع البث، ينتهي البث بحدث
run.requires_actionيحملrunفيهrequired_action. - التشغيل المتوقف يحتفظ بدور الجلسة: مع
queueتنتظر الرسائل الجديدة خلفه، ومعrejectتحصل على409 session_busy.
3. شغّل الأداة وأرسل النتائج
رابط القسم «3. شغّل الأداة وأرسل النتائج»أجب عن كل استدعاء في tool_calls، خلال 10 دقائق:
const BASE = 'https://api.k-agent.kerneltics.com/v1';const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
// Your implementations, keyed by tool name.const clientTools = { get_cart: async () => ({ items: [{ name: 'صندوق بخور', qty: 1, price_sar: 95 }], subtotal_sar: 95 }),};
async function send(session, input) { let res = await fetch(`${BASE}/sessions/${encodeURIComponent(session)}/messages`, { method: 'POST', headers, body: JSON.stringify({ input, client_message_id: crypto.randomUUID() }), }); let { run } = await res.json();
// A run can pause more than once: keep answering until it finishes. while (run?.status === 'requires_action') { const tool_outputs = await Promise.all( run.required_action.tool_calls.map(async (call) => { try { const output = await clientTools[call.name](call.arguments); return { call_id: call.call_id, output, is_error: false }; } catch (err) { return { call_id: call.call_id, output: { message: String(err) }, is_error: true }; } }), ); res = await fetch(`${BASE}/runs/${run.id}/submit_tool_outputs`, { method: 'POST', headers, body: JSON.stringify({ tool_outputs }), }); run = await res.json(); // submit_tool_outputs returns the run } return run; // completed: read run.output_text and run.outcome}import uuid, requests
BASE = "https://api.k-agent.kerneltics.com/v1"HEADERS = {"Authorization": f"Bearer {token}"}
# Your implementations, keyed by tool name.CLIENT_TOOLS = { "get_cart": lambda args: {"items": [{"name": "صندوق بخور", "qty": 1, "price_sar": 95}], "subtotal_sar": 95},}
def send(session: str, text: str) -> dict: r = requests.post(f"{BASE}/sessions/{session}/messages", headers=HEADERS, json={"input": text, "client_message_id": str(uuid.uuid4())}, timeout=120) run = r.json()["run"] # A run can pause more than once: keep answering until it finishes. while run and run["status"] == "requires_action": outputs = [] for call in run["required_action"]["tool_calls"]: try: outputs.append({"call_id": call["call_id"], "output": CLIENT_TOOLS[call["name"]](call["arguments"]), "is_error": False}) except Exception as exc: outputs.append({"call_id": call["call_id"], "output": {"message": str(exc)}, "is_error": True}) r = requests.post(f"{BASE}/runs/{run['id']}/submit_tool_outputs", headers=HEADERS, json={"tool_outputs": outputs}, timeout=120) run = r.json() # submit_tool_outputs returns the run return run # completed: read run["output_text"] and run["outcome"]curl https://api.k-agent.kerneltics.com/v1/runs/run_01k6rz7d9f1h3k5n7q9s1v3x5z/submit_tool_outputs \ -H "Authorization: Bearer $KAGENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tool_outputs": [ { "call_id": "call_8f2k1", "output": { "items": [{ "name": "صندوق بخور", "qty": 1, "price_sar": 95 }], "subtotal_sar": 95 }, "is_error": false } ] }'outputأي قيمة JSON. اجعلis_error: trueحين تفشل الأداة، ليتعامل النموذج مع الموقف بلباقة (مثل سؤال العميل أو التحويل لموظف).- يقبل
submit_tool_outputsالحقلينstream: trueوwait_secondsمثل أي طلب تشغيل آخر، ويعيد التشغيل. - النتائج الناقصة تعيد
422 tool_outputs_incomplete. والإرسال إلى تشغيل لا ينتظر يعيد409 run_not_requires_action. - إذا لم يصل شيء خلال 10 دقائق ينتهي التشغيل
failedبالرمزtool_outputs_expired(دون رد طوارئ ودون تحويل). ورسالة العميل التالية تبدأ تشغيلًا جديدًا.
من المتصفح
رابط القسم «من المتصفح»تستطيع رموز العميل استدعاء submit_tool_outputs لتشغيلات جلساتها، فيستطيع تطبيق الويب الإجابة عن أدوات جهة العميل مباشرة في المتصفح — مثل قراءة سلة لا توجد إلا هناك. أما خطوات التشغيل (/steps) فتبقى خاصة بخادمك.
معلومات مفيدة
رابط القسم «معلومات مفيدة»- لا تُعرض أدوات جهة العميل في طلبات
store: false. - مع الواجهة المتوافقة مع OpenAI تعرض الطلبات بلا حالة أدوات جهة العميل استدعاءاتٍ عادية في
tool_callsمعfinish_reason: "tool_calls"؛ وينتهي التشغيل بالنتيجةclient_tool_calls، ويكون طلبك التالي تشغيلًا جديدًا. requires_actionحالة وليست نتيجة أبدًا: التشغيل المتوقف ينتهي لاحقًا دائمًا بـcompletedأوfailedأوcancelledأوsuperseded.