תיעוד/מדריכים
כלים מותאמים
קובץ JSON אחד ברכיב השיחות מחבר לסוכן endpoints משלכם ב-HTTPS. רכיב השיחות פונה אליהם ישירות במהלך השיחה ומחזיר לסוכן תשובה קצרה שאפשר לומר בקול.
איך זה עובד
כל כלי מותאם הוא פונקציה שהמודל הקולי יכול להפעיל: שם, תיאור, ארגומנטים מוגדרי טיפוס ובקשת HTTPS שהוא שולח. כשמתקשר שואל משהו שהכלי יודע לענות עליו, המודל מפעיל אותו, רכיב השיחות שולח את הבקשה, ותשובת ה-JSON הופכת למשפט שהסוכן יכול לומר.
הקובץ נמצא בסביבת העבודה של הסוכן ב-OpenClaw, בנתיב data/voice-tools.json. בהתקנה של סוכן יחיד, הנתיב המלא הוא:
~/.openclaw/workspace/data/voice-tools.json
בהתקנות אחרות, רכיב השיחות מדפיס את הנתיב של סביבת העבודה שלו בזמן ההפעלה (Workspace: ... ב-ninja-talk logs). הקובץ אופציונלי: בלעדיו, כל הכלים המובנים פעילים ואין כלים מותאמים.
כלי עובד בחמש דקות
הכלי הזה מביא את מזג האוויר מ-API ציבורי בחינם. שמרו אותו כ-voice-tools.json:
{
"version": 1,
"customTools": [
{
"name": "lookup_weather",
"description": "Get the current weather for a city. Returns the temperature in Celsius and a short description.",
"endpoint": { "method": "GET", "url": "https://wttr.in/" },
"urlTemplate": "https://wttr.in/{{city}}?format=j1",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name, for example Toronto" }
},
"required": ["city"]
},
"auth": { "type": "none" },
"responseTemplate": "It is {{current_condition.0.temp_C}} degrees and {{current_condition.0.weatherDesc.0.value}} in {{nearest_area.0.areaName.0.value}}."
}
]
}
אין צורך בהפעלה מחדש: רכיב השיחות קורא את הקובץ מחדש בתחילת כל שיחה. התקשרו לסוכן ושאלו על מזג האוויר בטורונטו.
כלים מובנים
את שלושת הכלים המובנים האלה אפשר לכבות בבלוק builtins. מפתח שלא מופיע נחשב פעיל.
{
"version": 1,
"builtins": { "openclaw_query": true, "kb_search": true, "get_current_time": true },
"customTools": []
}
| כלי | מה הוא עושה |
|---|---|
openclaw_query | מעביר בקשה לסוכן ה-OpenClaw שמאחורי הסוכן הקולי: מייל, יומן, סקילים (skills). איטי יותר מכלי ישיר. |
kb_search | מחפש במאגר הידע של הסוכן. אפשר להחליף את התיאור שלו לכל סוכן עם kbSearchDescription ברמה העליונה (עד 600 תווים). |
get_current_time | התאריך והשעה באזור הזמן של העסק. |
מפתחות לא מוכרים ב-builtins לא משפיעים, ונרשמת עליהם הודעה בלוג. אי אפשר לכבות את end_call. השמות האלה שמורים, ואי אפשר לתת אותם לכלי מותאם: openclaw_query, kb_search, get_current_time, end_call, save_caller_info, connect_me, collect_card.
שדות הכלי
| שדה | כללים |
|---|---|
name (חובה) | תואם ל-^[a-z][a-z0-9_]{1,40}$: אותיות קטנות, מתחיל באות, 2 עד 41 תווים. ייחודי, ולא שם שמור. |
description (חובה) | 5 עד 500 תווים. המודל קורא אותו כדי להחליט מתי להפעיל את הכלי, ולכן כתבו במדויק: "Look up an order's delivery status by order number", ולא "order tool". |
endpoint (חובה) | { "method", "url" }. ה-method הוא GET, POST, PUT, PATCH או DELETE. ה-URL חייב להיות HTTPS לשרת ציבורי, והוא נבדק בזמן טעינת הקובץ. |
parameters (חובה) | JSON Schema עם "type": "object", properties שטוחים מטיפוס string, number או boolean (עם description אופציונלי עד 300 תווים, ו-enum אופציונלי של עד 20 ערכים), ורשימת required אופציונלית. שמות הפרמטרים תואמים ל-^[a-zA-Z][a-zA-Z0-9_]{0,40}$. |
auth | ראו אימות. ברירת המחדל היא { "type": "none" }. |
urlTemplate | ה-URL שאליו הבקשה נשלחת בפועל, עם משתנים בצורת {{argName}}. הערכים מקודדים ל-URL, והתוצאה נבדקת שוב אחרי ההחלפה. בלעדיו, הבקשה נשלחת ל-endpoint.url כמו שהוא. |
bodyTemplate | גוף בקשה ב-JSON עם משתנים בצורת {{argName}}, שנשלח כ-application/json. בבקשות GET המערכת מתעלמת ממנו. |
headers | כותרות קבועות נוספות. השמות תואמים ל-^[\w-]+$. כותרת האימות מתווספת אוטומטית. |
responseTemplate | עד 2000 תווים. הופך את תשובת ה-JSON לטקסט שאפשר לומר, עם הפניות בצורת {{path.to.field}}. |
timeoutMs | ברירת מחדל 3000. בכלי HTTP התקרה היא 3000; ערך גבוה יותר יורד ל-3000 עם אזהרה בלוג. |
privateOnly | true חושף את הכלי רק בשיחות של בעל העסק (מצב פרטי) ובשיחות יוצאות; מתקשרים מהציבור אף פעם לא רואים אותו. ברירת מחדל false. |
ברמה העליונה של הקובץ יש version (חייב להיות 1), builtins, customTools ו-kbSearchDescription.
תבניות
תבניות URL
כל {{argName}} מוחלף בארגומנט אחרי שעבר דרך encodeURIComponent, כך שלוכסנים, רווחים ו-& לא יכולים לשנות את מבנה ה-URL. ארגומנט שהמודל לא העביר הופך למחרוזת ריקה.
"urlTemplate": "https://api.example.com/v1/orders?number={{orderNumber}}&include=shipping"
תבניות גוף
כל {{argName}} מוחלף בארגומנט כערך JSON, כולל המירכאות. שימו משתנים במקום שבו אמור לבוא ערך JSON, ולעולם לא בתוך מירכאות. ארגומנט חסר הופך ל-null.
"bodyTemplate": "{ \"title\": {{title}}, \"priority\": {{priority}}, \"source\": \"voice\" }"
כש-title = Printer offline ו-priority = high, גוף הבקשה הוא { "title": "Printer offline", "priority": "high", "source": "voice" }. אם תכתבו "title": "{{title}}", תקבלו ""Printer offline"", שאינו JSON תקין.
תבניות תשובה
נתיבים עם נקודות קוראים ערכים מתוך תשובת ה-JSON, ומקטע שהוא מספר מצביע על מיקום במערך. שדות חסרים הופכים למחרוזות ריקות. מקטעים בשם __proto__, constructor או prototype נדחים.
Reply: { "order": { "status": "shipped", "items": [ { "name": "Desk lamp" } ] }, "eta": "Friday" }
Template: "Your {{order.items.0.name}} has {{order.status}} and should arrive {{eta}}."
Spoken: "Your Desk lamp has shipped and should arrive Friday."
בלי responseTemplate, ה-JSON הגולמי עובר למודל. זה עובד, אבל צורך יותר טוקנים, והסוכן עלול להקריא בקול שמות של שדות. הטקסט שמועבר למודל מוגבל ל-4000 תווים.
אימות
סודות אף פעם לא נכנסים ל-voice-tools.json. הכלי מציין שם של משתנה סביבה, והערך נמצא בקובץ ה-.env של רכיב השיחות.
| סוג | מה הוא עושה |
|---|---|
none | בלי כותרת אימות. ברירת המחדל. |
bearer | שולח Authorization: Bearer <value>. |
api-key | שולח <headerName>: <value>. |
header | כמו api-key, לכל שם כותרת מותאם. |
openclaw-tool | מריץ ישירות כלי שרשום בסוכן ה-OpenClaw שלכם. השמיטו את endpoint, urlTemplate ו-bodyTemplate. |
"auth": { "type": "none" }
"auth": { "type": "bearer", "envVar": "ORDERS_API_TOKEN" }
"auth": { "type": "api-key", "envVar": "CRM_API_KEY", "headerName": "X-API-Key" }
"auth": { "type": "header", "envVar": "PARTNER_TOKEN", "headerName": "X-Partner-Token" }
"auth": { "type": "openclaw-tool", "toolName": "email_search" }
envVar חייב להיות ב-UPPER_SNAKE_CASE. כדי להוסיף את הסוד, פתחו את ה-.env של רכיב השיחות, הוסיפו שורה, והפעילו מחדש כדי שרכיב השיחות יקרא אותה:
ninja-talk env # opens the .env file (on a Mac, prints where it is)
# add a line such as:
# ORDERS_API_TOKEN=your-token-here
ninja-talk restart
אם המשתנה חסר כשהכלי רץ, הסוכן מקבל את ההודעה "That tool is not configured. The owner needs to add credentials." והלוג מציין את שם המשתנה החסר.
.env של רכיב השיחות גלוי לכל מי ששולט במכונה. השתמשו במפתח שמוגבל למה שהכלי צריך, עדיף לקריאה בלבד, ושאפשר לבטל בנפרד מכל מפתח אחר.דוגמאות מלאות
בדיקת הזמנה (קריאה)
{
"name": "lookup_order_status",
"description": "Look up the delivery status of an order by its order number. Use after the caller gives an order number.",
"endpoint": { "method": "GET", "url": "https://api.example.com/v1/orders" },
"urlTemplate": "https://api.example.com/v1/orders/{{orderNumber}}",
"parameters": {
"type": "object",
"properties": {
"orderNumber": { "type": "string", "description": "Order number as the caller says it, digits only" }
},
"required": ["orderNumber"]
},
"auth": { "type": "bearer", "envVar": "ORDERS_API_TOKEN" },
"responseTemplate": "Order {{order.number}} is {{order.status}}. Expected delivery: {{order.eta}}."
}
פתיחת קריאת שירות (כתיבה)
{
"name": "open_support_ticket",
"description": "Open a support ticket for the caller. Use only after the caller confirms they want a ticket opened.",
"endpoint": { "method": "POST", "url": "https://crm.example.com/api/tickets" },
"parameters": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "One-line summary of the problem" },
"details": { "type": "string", "description": "What the caller described" },
"priority": { "type": "string", "enum": ["low", "normal", "high"] }
},
"required": ["title", "details", "priority"]
},
"bodyTemplate": "{ \"title\": {{title}}, \"details\": {{details}}, \"priority\": {{priority}}, \"source\": \"voice\" }",
"auth": { "type": "api-key", "envVar": "CRM_API_KEY", "headerName": "X-API-Key" },
"responseTemplate": "Ticket {{ticket.id}} is open with {{ticket.priority}} priority."
}
שתי הדוגמאות נכנסות למערך customTools באותו קובץ. כלי שמשנה משהו צריך לציין את זה בתיאור ("use only after the caller confirms"), כדי שהמודל ישאל קודם.
אבטחה ומגבלות
- HTTPS לשרתים ציבוריים בלבד. HTTP רגיל, ושרתים שהם כתובות loopback, טווחים פרטיים או כתובות metadata של ענן, נחסמים בזמן טעינת הקובץ ושוב אחרי ההחלפה ב-URL ("That request was blocked for security reasons."). הבדיקה היא על שם השרת כפי שנכתב, בלי שאילתת DNS.
- הארגומנטים נבדקים מול הסכמה שלכם לפני כל בקשה: ארגומנטי חובה קיימים, הטיפוסים נכונים, ערכי
enumמותרים. - התשובות חייבות להיות JSON עם סטטוס 2xx ועד 512 KB. כל דבר אחר, או חריגה ממגבלת הזמן, מחזיר לסוכן "I could not reach that service right now."
- 20 כלים מותאמים לכל סוכן, כולל כלים שאפליקציות מחוברות כמו Shopify או Wix מוסיפות (עד 5 כלים). כלי האפליקציות נכנסים ראשונים, הכלים הראשונים בקובץ שלכם ממלאים את המקומות שנשארו, והשאר נשמטים עם אזהרה בלוג. כל כלי מאריך את ההנחיות שהמודל מקבל בכל תגובה, ולכן עדיף מעט כלים, מוגדרים היטב.
- קובץ פגום אף פעם לא מפיל את הסוכן. JSON לא תקין או בדיקה שנכשלה נרשמים בלוג, והסוכן ממשיך לעבוד עם כל הכלים המובנים ובלי אף אחד מהכלים המותאמים שבקובץ, עד שתתקנו אותו. כלים של אפליקציות מחוברות עדיין נטענים.
פתרון תקלות
בדקו מה נטען אחרי שיחת בדיקה:
ninja-talk logs -n 300 | grep voice-tools # on a Mac: ninja-talk logs | grep voice-tools
| תסמין | סיבה ופתרון |
|---|---|
בלוג מופיע voice-tools.json validation failed: ... | ההודעה מציינת את השדה. אף אחד מהכלים שלכם לא נרשם עד שתתקנו. |
בלוג מופיע voice-tools.json is not valid JSON | שגיאת תחביר. בדקו עם jq . voice-tools.json. |
| הסוכן אף פעם לא מפעיל את הכלי | כתבו בתיאור, במדויק, מתי להשתמש בכלי. ודאו שמופיעה השורה התקינה loaded voice-tools.json {"customToolCount":1,...} עם המספר שאתם מצפים לו. |
| "That tool is not configured" | ה-envVar לא מוגדר ב-.env, או שרכיב השיחות לא הופעל מחדש אחרי שהוספתם אותו. |
| "I could not reach that service right now" | הבקשה נכשלה, החזירה סטטוס שאינו 2xx או תוכן שאינו JSON, או לקחה יותר מ-3 שניות. נסו את אותו URL עם curl מהמכונה של רכיב השיחות. |
| הסוכן מקריא בקול שמות של שדות JSON | הוסיפו responseTemplate, ובדקו את הנתיבים שלו מול תשובה אמיתית. |
| ה-API שלכם דוחה את הגוף | משתנה נמצא בתוך מירכאות ב-bodyTemplate. הסירו את המירכאות סביבו. |