أول ما النموذج يقدر يستدعي أدوات، تصير فيه إغراءات معمارية مو بسيطة. syntax الأداة يبدأ يدخل في token loop، والـretries تصير فروع خاصة داخل decode، وحالة التنفيذ الخارجي تختلط مع KV state. شوي شوي يصير forward pass شايل سياسة تنفيذ ما لها أي علاقة باختيار الـtoken الجاي.
Ember v0.6.7 تعمّد يقفل الحد هذا. استدعاء الأدوات انضاف كـstate machine صريحة فوق مسار الاستدلال والجلسة الموجود. ما تغير attention، ولا تخزين KV، ولا tokenization، ولا model forward، ولا كود الـtensors. الـruntime موجود في شجرة إضافية تحت src/agent/، والـCLI فوقها مجرد واجهة خفيفة. النموذج لسه عنده شغلة وحدة: يولّد tokens.
src/agent/session.rs explicit execution state machine + commit ledger
│
├── protocol.rs model-family render/parse codecs
├── schema.rs raw JSON → ValidatedArguments
├── tool.rs bounded execution, cancellation, timeout
├── trace.rs authoritative ordered event record
└── artifact.rs atomic, hashed run artifacts
│
▼
ChatModelEngine narrow seam over the existing model session
│
▼
inference core tokenizer · forward pass · attention · KV · tensors
unchanged in v0.6.7
الرسم هذا هو التصميم، مو بس ترتيب ملفات. طبقة الـagent تقدر تقرر إن النص المولّد يصف فعل لازم يتنفذ، بس ما تقدر تعيد تعريف الاستدلال عشان تنفذ الفعل.
ما فيه استدعاء عودي يرجع إلى «agent prompt». فيه loop واحد يملك انتقالات الحالة. قبل الحدود الواضحة يفحص عدد أدوار النموذج، وعدد استدعاءات الأدوات، ووقت التشغيل الكلي، ومهلة كل أداة، وعدد tokens في كل رد، وحجم النتيجة اللي بترجع للسياق. الإلغاء نتيجة من الدرجة الأولى، مو نص ثاني نحطه في الـprompt.
Qwen2.5 وLlama 3.x ما يكتبوا استدعاء الأداة بنفس الطريقة. Qwen يستخدم رسالة ChatML فيها <tool_call>...</tool_call>. Llama يستخدم <|python_tag|> وبعده استدعاء JSON custom function، ويتوقع النتيجة تحت دور ipython. حتى stop tokens وطريقة صياغة النتيجة مختلفة.
| العائلة | | شكل فعل المساعد | | شكل النتيجة |
|---|---|---|
| Qwen2.5 | <tool_call>{"name":…,"arguments":…}</tool_call> | رسالة مستخدم ChatML فيها <tool_response> |
| Llama 3.x | <|python_tag|>{"name":…,"parameters":…} | رسالة بدور ipython |
الـloop العام ما يعرف أي وحدة من هذي الصيغ. اللي يوصله واحد من ثلاثة أفعال على مستوى الـprotocol: FinalText، أو ToolCall، أو MalformedToolCall. الـcodecs تصيغ system tool schemas، ورسائل المستخدم، وإطار رد المساعد، ونتائج الأدوات، وبعدها ترجع خرج النموذج إلى النوع المشترك. الاختبارات تثبت الصياغة byte-for-byte.
هنا مكان الحالات الخاصة بكل عائلة. إضافة صيغة جديدة معناها نكتب codec جديد، مو نضيف فروع داخل منطق الجلسة ولا نعلّم KV cache إيش يعني رد أداة.
إننا لقينا جزء يشبه JSON يعني بس إننا تعرفنا على الـsyntax. ما يعني إنه صار مسموح يتنفذ. الـruntime يبحث عن الأداة باسمها، ويحلل كائن المعاملات، ويتحقق منه مقابل الـschema المسجلة، وبعدها فقط يبني ValidatedArguments. الـTool trait يستقبل النوع هذا. ما عنده مدخل يستقبل كلام النموذج الخام.
model bytes
→ protocol parser
→ raw tool call
→ schema validation
→ ValidatedArguments
→ Tool::execute(...)
التحقق صارم ويمشي داخل البنية. يفحص النصوص والأرقام والأعداد الصحيحة والقيم المنطقية والمصفوفات والـenums والكائنات المتداخلة. الحقول المطلوبة الناقصة، والحقول الغريبة، والأنواع الغلط، ومخالفات الـenum، وJSON المشوه، والتداخل الأعمق من الحد كلها تنرفض. فحص الـschema يجمع كل أخطاء الحقول في الاستدعاء بدل ما يوقف عند أول خطأ.
{
"ok": false,
"kind": "invalid_arguments",
"errors": [
{"path":"operation", "reason":"missing required argument"},
{"path":"precision", "reason":"unknown field"},
{"path":"a", "reason":"expected number, got string"}
]
}
الاستدعاءات المشوهة وأخطاء التحقق تنسجل كحالات رفض، وبعدها ترجع للنموذج عبر الـprotocol كـtool result مرفوضة بقيمة ok:false. يقدر النموذج يصلح الاستدعاء في دوره الجاي. بس الاسترجاع يبقى تحت نفس حدود الخطوات والوقت. الاستدعاء الخربان ما يتحول بصمت إلى كلام عادي، وأبداً ما يوصل للأداة.
أصعب حد هنا مو التحليل. هو معنى الـcommit. توليد النموذج يبقى تخمينياً إلى أن يخلص الدور. أما أثر الأداة الخارجي فيصير حقيقي لحظة تنفيذها. هذي حالتين من نوع مختلف، والـruntime ما يتظاهر إنهم نفس الشيء.
cancel during generation
→ truncate speculative KV state
→ commit no assistant turn
tool executes
→ external side effect now exists
cancel before tool-result commit
→ keep the external effect visible
→ do not inject the result into the session
→ emit tool_result_uncommitted
سجل الـcommit يحفظ انتقال المحادثة اللي صار فعلاً: system → user → assistant_tool_call → tool_result → … → final. لو جاء الإلغاء أثناء التوليد، يرجع KV إلى آخر حد ثابت. ولو جاء بعد تنفيذ الأداة، يبقى الجزء المثبت من الجلسة نظيف وما ندخل له نتيجة وهمية، وفي نفس الوقت يقول الـtrace بوضوح إن الأثر الخارجي صار.
الفرق هذا يبان أكثر مع الكتابة. حذف آخر tokens مولّدة ما يقدر يحذف ملف انكتب، أو يعكس تغيير في API، أو يسحب رسالة انرسلت. الـrollback خاص بحالة النموذج التخمينية. مو زر تراجع شامل للعالم الخارجي.
الـtracing في v0.6.7 مو debug logs ملفوفة حول النتيجة الحقيقية. ملف JSONL هو التاريخ المعتمد للتنفيذ. كل سطر يحمل ember.agent.trace.v1، ومعرّف التشغيل، ورقم تسلسلي يزيد دائماً. الوقت الجداري مفيد لقياس التأخير؛ ترتيب الأحداث يجي من seq.
{"schema":"ember.agent.trace.v1","run_id":"run-…","seq":29,
"event_type":"tool_execution_finished","step":"tool-3","phase":"execute",
"data":{"tool":"write_artifact","ok":true,"duration_ms":0.3,
"artifact_ids":["0000-94474f5d7426"]}}
حدث الـprovenance يثبت هوية الـruntime والنموذج والـtokenizer، ولقطة الـprotocol والأدوات، وإعدادات التشغيل. الأحداث اللي بعده تسجل أدوار النموذج، وقرار التحليل، والتحقق، وتنفيذ الأداة، وتغييرات الجلسة، وتثبيت النتائج، وحالات النهاية، وartifact hashes.
كل حدث ينكتب وينعمل له flush مباشرة، عشان لو البرنامج طاح يبقى عندنا جزء JSONL مقروء. والـparser يبلغ عن آخر سطر مقطوع بدل ما يرفض التاريخ كله. إعدادات الخصوصية تقدر تستبدل الـprompts أو النص المولّد بالأطوال والـhashes، وتختصر أو تعمل hash لنتائج الأدوات، وتخلي أحداث كل token مقفلة. قابلية التدقيق ما تعني إننا لازم ننسخ كل محتوى داخل الـtrace.
أكثر فشل فادني في تشغيل نموذج حقيقي ما كان crash. في محاولة، النموذج حكى إنه كتب artifact. بس هو فعلياً ما استدعى write_artifact. ما كان فيه حدث تنفيذ للأداة في الـtrace، ومخزن الـartifacts كان فاضي.
بدون حد التنفيذ، آخر جملة تغريك تعتبر التشغيل ناجح. مع الحد، الادعاء سهل يتفند. الـartifact ما يعتبر موجود إلا إذا الأداة اشتغلت، والمخزن نشر الـbytes، والـtrace سجل هويتها. النص المولّد يقدر يوصف التاريخ؛ ما يقدر يصنعه.
تشغيل Qwen2.5-1.5B Q8_0 مر على نفس الحد ونجح. النموذج قرأ ملخصات تجارب فيها نتيجة baseline تساوي 0.612 ونتيجة إعداد محسّن تساوي 0.744، واستدعى الحاسبة، وكتب Markdown artifact، ورجع DONE 21.57%.
source scores baseline_q4 = 0.612, tuned_q8 = 0.744
calculation (0.744 - 0.612) / 0.612 × 100 = 21.5686…%
artifact 0000-94474f5d7426-best-config.md
sha256 94474f5d742605e55400ce8d35ff27d6ef204c13b8820c02acb89fdef0e01d6e
final answer DONE 21.57%
المهم هنا مو إن النموذج يعرف يقسم. المهم إن القراءة، والحسبة، والكتابة، وcontent hash، وصياغة الجواب النهائي أحداث منفصلة وتُنسب كل وحدة لمصدرها. الـtrace يوضح أي معلومة جت من ملف، وأي رقم طلع من أداة، وأي bytes صارت artifact.
benchmark في وضع release وبمحرك اختباري مخصص قاس الكلفة اللي حول استدلال النموذج: التحليل، والتحقق، وتنفيذ أدوات وهمية، وانتقالات الحالة، والـtracing. هذي الأرقام المبلّغ عنها من 200 إعادة على جهاز التقرير، مو أرقام لسرعة الأدوات ولا لسرعة النموذج.
| الحالة | | الوقت لكل تشغيل |
|---|---|
| أداة وحدة، والـtracing مقفل | 0.494 ms |
| أداة وحدة، وJSONL tracing شغال | 0.700 ms |
| ثلاث أدوات، وmemory trace | 1.898 ms |
تنسيق الـagent مو هو عنق الزجاجة؛ استدلال النموذج لسه يهيمن بفارق مراتب كاملة. تشغيل Qwen الحقيقي أخذ تقريباً 63.7 ثانية في أدوار النموذج و2.8 مللي ثانية في الأدوات. الحد صريح، بس كلفته صغيرة.
هذا مو autonomous-agent framework، ولا browser agent، ولا منفذ shell، ولا تطبيق MCP، ولا نظام multi-agent، ولا إطار موافقات. v0.6.7 طبقة تنفيذ منظمة وصغيرة، بحالة صريحة وتاريخ نقدر ندققه. الأدوات المدمجة حتمية ومحلية؛ ما فيه أداة shell أو شبكة أو متصفح أو حذف.
السطح الحالي ينفذ استدعاء أداة واحد في كل دور للمساعد. الاستدعاءات الإضافية تنعد وتنسجل بدل ما تتوزع بالتوازي. مهلة الأدوات تستخدم watchdog حول شغل متزامن، عشان كذا الـworker اللي تنتهي مهلته ينفصل عن مسار التنفيذ وتُهمل نتيجته بدل ما نحاول نوقفه بطريقة مو آمنة. هذي حدود ظاهرة في العقد، مو تفاصيل مخفية وراء كلمة «agent».
استدعاء الأدوات ما احتاج يصير جزء من الاستدلال. النموذج لسه يولّد tokens. الـruntime اللي فوقه يقرر هل هذي الـtokens تصف جواب نهائي أو فعل صالح، والـtrace يسجل إيش صار فعلاً.