البنية المعمارية
تفصيل عميق ومُوثّق بالكود المصدري لمحرك كاظمة: العقل المُشرف، ومسار البيانات من نيّة المستخدم إلى تنفيذ الأداة، والأنظمة الفرعية التي تجعله دائمًا وآمنًا ومتعدّد اللغات.
1. الفلسفة: عقلٌ واحد، وأفواهٌ متعدّدة
Section titled “1. الفلسفة: عقلٌ واحد، وأفواهٌ متعدّدة”نُظِّم كاظمة حول فصلٍ صارم بين الاستدلال (الرسم البياني المُشرف في LangGraph) والنقل (مُحوّلات المنصّات). تخدم نسخةٌ واحدة من الرسم البياني — تُبنى مرّةً واحدة عبر build_supervisor_graph() — كلَّ القنوات. لا يطّلع الرسم البياني قطّ على المُعرّفات الخاصّة بالمنصّة؛ فالمُحوّلات تمتلكها ولا تُعيد إرفاقها إلا عند إصدار الردّ.
ينتج عن ذلك ثلاث خصائص يعتمد عليها بقية النظام:
- حرية المُزوّد — يتحدّث العقل إلى أيّ نقطة نهاية متوافقة مع OpenAI عبر
httpx. لا يُستورد أيّ حزمة SDK خاصّة بمورّد. - تكافؤ القنوات — إيقافُ الموافقة البشرية في الحلقة (HITL)، واستدعاءُ الأداة، ورمزُ البثّ متطابقةٌ تمامًا سواءٌ صدرت من Telegram أم من واجهة الويب.
- حالة دائمة — بما أنّ مُعرّفات المنصّة تعيش خارج الرسم البياني، فإنّ حالة الرسم البياني هي حالة حواريّة بحتة يمكن حفظها كنقطة حفظ وإعادة تشغيلها واستئنافها عبر عمليّات إعادة التشغيل.
2. هيكلية الحِزم
Section titled “2. هيكلية الحِزم”كاظمة مستودعٌ موحّد (monorepo) من سبع حزمٍ قابلة للتثبيت (مُعرَّفة في pyproject.toml ضمن [tool.hatch.build.targets.wheel]):
| الحزمة | المسار | المسؤولية |
|---|---|---|
kazma-core | kazma-core/kazma_core/ | مشغّل الوكيل، مُزوّد LLM، سجلّ النماذج، محرّك السرب، ConfigStore، السلامة، الذاكرة، المهارات، MCP، المركز (hub)، التفويض، التكثيف، المجلس (Majlis) |
kazma-gateway | kazma-gateway/kazma_gateway/ | مُحوّلات Telegram/Discord/Slack، معالج الوكيل (جسر الرسم البياني)، أوامر الشرطة المائلة (slash commands)، مخزن الجلسات |
kazma-ui | kazma-ui/kazma_ui/ | مصنع تطبيق FastAPI، محادثة SSE، لوحة السرب، الإعدادات، لوحة التحكم، التدويل (i18n)، الأصول الثابتة |
kazma-tui | kazma-tui/kazma_tui/ | لوحة تحكم TUI قائمة على Textual (مستهلك يقرأ في الغالب نسخ singleton الخاصة بالنواة) |
kazma-memory | kazma-memory/kazma_memory/ | مُجزّئ النصوص العربي + خلفية بحث SQLite/FTS5 |
kazma-skills | kazma-skills/kazma_skills/ | بيانيّات المهارات (بيانات) |
kazma-cli | kazma-cli/kazma_cli/ | سطح أوامر kazma |
سكربتات الطرفية (pyproject.toml:73-76):
kazma = "kazma_cli.main:main"kazma-tui = "kazma_tui.app:main"kazma-web = "kazma_ui.app:main"3. العقل المُشرف (LangGraph)
Section titled “3. العقل المُشرف (LangGraph)”الرسم البياني النواة هو حلقة ReAct مبنية في kazma-core/kazma_core/agent/graph_builder.py.
3.1 هيكلية العُقد
Section titled “3.1 هيكلية العُقد”flowchart LR START([user message]) --> SUP[Supervisor Node] SUP -- "LLM calls tools" --> TW[Tool Worker Node] TW -- "tool results" --> AUTH{ContextAuthority<br/>check & enforce} AUTH -- "compact if ≥80%" --> SUP AUTH -- "under threshold" --> SUP SUP -- "no tool calls" --> RESP[Respond Node] RESP --> END([reply / SSE stream]) TW -- "danger tool + HITL on" --> INT[LangGraph interrupt] INT -- "approval" --> TW INT -- "denial / timeout" --> RESP- عقدة المُشرف (
graph_builder.py:supervisor_node) — تستدعي نموذج LLM النشط مع الأدوات المُسجّلة وسجلّ المحادثة. توجّه بناءً على ما إذا كانت الاستجابة تتضمّن استدعاءات أدوات. - عقدة عامل الأداة (
graph_builder.py:336 tool_worker_node) — تنفّذ استدعاءات الأدوات المعلّقة. هنا تقع بوابة الموافقة البشرية في الحلقة (HITL): إذا زُوِّدhitl_configوكانت الأداة ضمن قائمة الأدوات الخطيرة، تستدعي العقدةinterrupt()من LangGraph (السطر 493) وتتوقّف حتى يُستأنف التنفيذ. - ContextAuthority (
authority.py:37 check_and_enforce) — تُستدعى داخل عقدة المُشرف (graph_builder.py:167) قبل استدعاء نموذج LLM. إذا أعادتshould_compact()القيمة true (عدد الرموز ≥ 80% من النافذة)، فإنها تُلخّص وتعيد بناء قائمة الرسائل. - عقدة الردّ — تُنهي ردّ المساعد تمهيدًا للبثّ.
3.2 حلقة ReAct في الكود
Section titled “3.2 حلقة ReAct في الكود”يُبنى الرسم البياني عبر build_supervisor_graph() (graph_builder.py:661). يستلم الإغلاق الداخلي _tool_worker قيمة hitl_config (السطر 739) — وهذا الربط هو ما يُفعّل البوابة. يمرّرها موقعان فعليّان للبناء (أمّا الثالث فلا يفعل — انظر الأمان والسلامة).
# agent_runner.py — the streaming graph used by the Web UI's SSE endpointdef get_streaming_graph(self): hitl_config = { "enabled": self._config.get("safety.hitl.enabled", True), "require_approval_for": self._config.get( "safety.hitl.require_approval_for", DEFAULT_DANGER_TOOLS, ), "approval_timeout_seconds": self._config.get( "safety.hitl.approval_timeout_seconds", 60 ), } graph = build_supervisor_graph( model=self.model, tools=self.tools, hitl_config=hitl_config, # <-- gate active checkpointer=self._checkpointer, ) return graph3.3 التنفيذ الدائم
Section titled “3.3 التنفيذ الدائم”- حافظ نقاط الحفظ:
AsyncSqliteSaver(منlanggraph-checkpoint-sqlite) علىkazma-data/checkpoints.db(مُهيّأ فيkazma-ui/.../app.py:724-726). - هويّة الخيط (thread): لكلّ محادثة
thread_id(مشتقّ من مُعرّف المُرسِل، مثلgw-telegram-12345، أو UUID4 جديد — انظرagent_handler/store.py:34 _resolve_thread). - الاستعادة بعد الأعطال: تستمرّ إيقافات الموافقة البشرية في الحلقة (HITL) في حافظ نقاط الحفظ. عند إعادة التشغيل، تُعيد
restore_paused_tasks()(swarm/checkpoint_manager.py:182) تحميل مهامّ السرب المُوقَفة وتُسلّح مجدّدًا مهلّ الرفض التلقائي لها. أمّا إيقافات مسار الرسم البياني فتنجو لأنّها تعيش في حافظ نقاط الحفظ. - السفر عبر الزمن:
/replay list | <iter> | compare <a> <b> | clear(أمر slash) وإعدادtime_travel(kazma.yaml:111-114،max_snapshots: 50).
4. تدفّق البيانات من البداية إلى النهاية
Section titled “4. تدفّق البيانات من البداية إلى النهاية”sequenceDiagram participant U as User (Telegram/Web/...) participant A as Platform Adapter participant S as SessionStore participant H as AgentHandler (graph bridge) participant G as Supervisor Graph participant L as LLM Provider (httpx) participant T as ToolRegistry participant M as Bus (HITL)
U->>A: text message A->>S: put(thread_id, {chat_id, user_id, ...}) A->>H: IncomingMessage (no platform IDs in body) H->>G: graph.ainvoke({messages:[...]}, config={thread_id}) loop ReAct G->>L: POST /chat/completions (tools=...) L-->>G: tool_calls or final text alt danger tool G->>M: interrupt(approval_input) Note over G,M: graph SUSPENDED M-->>U: approval request (inline button / SSE event) U->>M: approve M->>G: Command(resume={"approved":true}) end G->>T: execute(tool, args) T-->>G: ToolResult end G-->>H: final state H->>S: get(thread_id) // rehydrate platform IDs H->>A: OutboundMessage(target_id, text) A-->>U: replyالثوابت الأساسية المُطبَّقة على هذا المسار:
| الثابت | المنفِّذ | الموقع |
|---|---|---|
| لا تدخل مُعرّفات المنصّة قطّ حالة الرسم البياني | مجموعة _PLATFORM_KEYS المجمّدة + _build_initial_state | agent_handler/store.py:16,95 |
| يُعاد توجيه الردّ إلى المحادثة الصحيحة | _build_target_id(platform, ctx) | agent_handler/store.py:146 |
| نموذج المُزوّد الخاطئ لا يصل قطّ إلى نقطة النهاية الخاطئة | التصحيح التلقائي في get_client() | model_registry.py:275-303 |
| الأدوات الخطيرة تُوقف ولا تُنفَّذ بصمت | interrupt() + علم _hitl_approved | graph_builder.py:483-506 |
| تبديل المُزوّد يُبطِل العملاء المُستهلكين | set_active_model يُفرغ الذاكرة المؤقتة | model_registry.py:248 |
5. طبقة مُزوّد LLM
Section titled “5. طبقة مُزوّد LLM”kazma-core/kazma_core/llm_provider.py هو عميل httpx رفيع بلا حزمة SDK. يتحدّث بصيغة نقل OpenAI Chat Completions السلكية إلى أيّ نقطة نهاية متوافقة.
5.1 تحديد المُزوّد
Section titled “5.1 تحديد المُزوّد”تُعيد ModelRegistry.get_client(model=None) (model_registry.py:252) نسخة LLMProvider مُخزّنة مؤقتًا للملف النشط. شبكة الأمان الحاسمة:
# model_registry.py:275-303 (paraphrased)if effective_model: owner = self.find_provider_for_model(effective_model) if owner and owner["name"].lower() != provider_name.lower(): # e.g. a DeepSeek model requested while OpenAI is active provider_name = owner["name"] # auto-correct if model is None: self._active_provider = owner_name self._config_store.set("registry.active_provider", owner_name, ...)لهذا السبب يُعدّ «لا تُغيّر النموذج دون مُزوّد» قاعدةً صارمة — انظر تحذيرات تحديد المُزوّد/النموذج في الإعدادات.
5.2 الحلّ الاحتياطي لأدوات NVIDIA NIM
Section titled “5.2 الحلّ الاحتياطي لأدوات NVIDIA NIM”ترفض بعض المُزوّدين (لا سيّما NVIDIA NIM) تعريفات الأدوات بخطأ 404 "Function not found". يكتشف العميل ذلك ويُعيد المحاولة مرّةً واحدة دون أدوات كي يحصل المُستدعي على إجابة نصّيّة على أيّ حال (llm_provider.py:285-300):
nim_function_not_found = status_code == 404 and "function" in detail_lowertool_schema_error = ( status_code in (400, 422) and any(tok in detail_lower for tok in ("tool", "function")))if tools and (nim_function_not_found or tool_schema_error): logger.warning("Provider rejected tool definitions; retrying without tools.") payload.pop("tools", None) payload.pop("tool_choice", None) resp = await client.post("/chat/completions", json=payload)لا تُزل هذا الفرع. إزالته تكسر الوكلاء المستخدمة للأدوات على NVIDIA NIM وغيرها من المُزوّدين الصارمين.
5.3 البثّ
Section titled “5.3 البثّ”البثّ هو مُولّد غير متزامن مستقلّ stream_chat() في streaming.py (وليس تابعًا على LLMProvider). يُرسل طلب POST مع stream: True، ويُحلّل أسطر SSE data:، ويتعامل مع [DONE]، ويُصدر أحداث StreamEvent مُنمَّطة (token، tool_call، done، error). انظر واجهة API ونقاط التوسعة.
5.4 التكلفة وإعادة المحاولة
Section titled “5.4 التكلفة وإعادة المحاولة”| الجانب | الآلية | الموقع |
|---|---|---|
| تكلفة كل استدعاء | (prompt_tokens * in_cost/1M) + (completion_tokens * out_cost/1M) | llm_provider.py:411-422 |
| سقف التكلفة | CostCircuitBreaker (افتراضي $0.50، صمت 5 دقائق) — متغيّرات البيئة KAZMA_MAX_COST، KAZMA_SILENCE_WINDOW | cost_breaker.py |
| إعادة المحاولة | مزخرفات قائمة على tenacity، للأخطاء الشبكيّة/المهلة فقط، لا تشمل 4xx | retry.py:39-109 |
| معالجة تحديد المعدّل (429) | غير مُنفّذة | — |
قاطع الدائرة للتكلفة هو صنف بيانات (dataclass) مستقلّ؛ وعلى طبقة الوكيل أن تقوده عبر
record_cost/should_halt. إنّه غير موصول تلقائيًّا إلىchat().
6. تنسيق السرب (نظرة عامة)
Section titled “6. تنسيق السرب (نظرة عامة)”عندما يحتاج المُشرف إلى أكثر من وكيل، ينتقل التحكّم إلى SwarmEngine (kazma-core/kazma_core/swarm/engine.py:103). يُدعم ستّة أنماط إرسال كعدّ TaskType (swarm/task.py:65): DISPATCH، BROADCAST، PIPELINE، FAN_OUT، CONSULT، CONDITIONAL.
flowchart TB subgraph SwarmEngine DI[dispatch_inner] DI -->|single| DSP[dispatch] DI -->|all| BCAST[broadcast] DI -->|ordered| PIPE[pipeline + blackboard] DI -->|parallel| FAN[fan-out + aggregate] DI -->|opinions| CONS[consult + synthesize] DI -->|router| COND[conditional routes] end DSP & BCAST & PIPE & FAN & CONS & COND --> W[Worker / InProcessWorker] W --> REL[ReliabilityRegistry] REL --> CB[CircuitBreaker] REL --> RT[RetryPolicy] REL --> TO[TimeoutGuard] REL --> OV[OutputValidator] REL --> BC[BoundedConcurrency] PIPE -.->|hitl_checkpoints| CK[CheckpointManager]تُحمى عمليات التسليم بين العمّال من العوديّة اللانهائيّة: MAX_HANDOFF_DEPTH = 5 وMAX_VISITS = 2 (عدد زيارات لكلّ عامل، وليس مجموعة قيم منطقيّة) موجودة في swarm/handoff_guards.py:16-17. انظر تنسيق السرب لفهرس الأنماط الكامل.
7. الأنظمة الفرعيّة للذاكرة (نظرة عامة)
Section titled “7. الأنظمة الفرعيّة للذاكرة (نظرة عامة)”يحتوي كاظمة على ثلاثة أنظمة ذاكرة متباينة. لطالما خلطت الوثائق بينها تاريخيًّا؛ وهذا التعديل يفصل بينها بصدق:
| النظام الفرعي | الخلفية | يُستخدم من وكيل المحادثة؟ | ملاحظات |
|---|---|---|---|
| VectorMemory (أدوات RAG) | ChromaDB، all-MiniLM-L6-v2 (384-أبعاد) | فقط عندما يستدعي نموذج LLM memory_search / memory_store | أداة اختياريّة؛ لا حقن تلقائي |
SQLiteMemoryBackend (self.memory للوكيل) | SQLite + FTS5 (porter unicode61) | لا يُستعلم أثناء استرجاع المحادثة | موصولة على أنّها self.memory لكن دون أيّ مُستدعٍ في مسار المحادثة |
| UnifiedMemoryAdapter (RRF رباعيّ الطبقات) | ChromaDB + NetworkX + FTS5 + sqlite-vec | لا — فقط self_improvement.py وphonebook.py | «الذاكرة رباعيّة الطبقات» من README |
التفاصيل الكاملة، بما في ذلك ثغرات التوصيل ودالّة distance() المعطوبة في search_backend.py، في الذاكرة وRAG → ملاحظات الحالة الصادقة.
8. الطبقة العربيّة والثقافيّة
Section titled “8. الطبقة العربيّة والثقافيّة”كاظمة عربيّ الأصل افتراضيًّا (agent.language: ar، agent.rtl: true). تنفّذ ذلك ثلاثة مكوّنات:
- المُجزّئ النصّي العربي (
kazma-memory/kazma_memory/arabic_tokenizer.py) — إزالة التشكيل، وتطبيع الألف/الياء/التاء المربوطة، وحذف التطويل، وكلمات الإيقاف باللهجة الكويتيّة، وجذّاع أساسي. يُغذّي عمود FTS5 المُسمّىcontent_arabic. - التدويل (i18n) وواجهة RTL (
kazma-ui/kazma_ui/i18n.py،static/css/kazma.css) — قاموسTRANSLATIONSمضمّن (EN/AR)، وdir/langلكلّ طلب، ومكدّس خطوط يبدأ بـ Calibri، وأساس 16px لـ RTL، وأرضيّة قراءة على الفئات الصغيرة. - بروتوكول المجلس (Majlis) (
kazma-core/kazma_core/majlis.py) — تدفّق محادثة ثقافي خليجي من أربع مراحل (GREETING → SOCIAL → TRANSACTION → FAREWELL) مع افتراضيّات باللهجة الكويتيّة ومُعدِّلات ثقافيّة (رمضان، العيد، اليوم الوطني).
انظر الميزات العربيّة والثقافيّة.
9. القابليّة للرصد (الحالة الحاليّة)
Section titled “9. القابليّة للرصد (الحالة الحاليّة)”| الإشارة | المصدر | الحالة |
|---|---|---|
| سجلّات منظّمة | logging (خيار صيغة JSON في kazma.yaml) | نشط |
| مقاييس السرب | MetricsCollector (في الذاكرة + SQLite) — tasks_completed، tasks_failed، avg_latency، total_tokens، total_cost | نشط |
| امتدادات التتبّع | TracingEmitter / Span / InMemorySpanExporter داخليّة | نشط (ليس OTel) |
| قياس عن بُعد لـ SSE | أحداث /api/chat/stream؛ موجّه القياس عن بُعد | نشط |
| Prometheus | — | غير مُنفّذ (لا prometheus_client، ولا نقطة نهاية /metrics) |
| OpenTelemetry | opentelemetry-api/sdk في التبعيّات؛ وإضافة [tracing] تحوي مُصدّرات OTLP | المكتبة موجودة؛ التوصيل ضمن خارطة الطريق |
| Langfuse | langfuse>=2.0.0 في التبعيّات؛ علم إعداد logging.langfuse.enabled | التبعيّة موجودة؛ التكامل ضمن خارطة الطريق |
الحزم
opentelemetry-*مُعلنة كي يتمكّن المستخدمون من توصيل OTel، لكن باعث التتبّع الخاصّ بكاظمة هو نظام امتدادات داخلي مخصّص وليس OTel. انظر خارطة الطريق.
10. مخازن البيانات الشاملة
Section titled “10. مخازن البيانات الشاملة”تتشارك جميع مخازن SQLite في كاظمة نموذج التزامن نفسه، المُمركَز في config_store.py:apply_sqlite_pragmas():
PRAGMA journal_mode=WAL; -- concurrent readers, single writerPRAGMA busy_timeout=5000; -- 5 s wait on lockPRAGMA synchronous=NORMAL; -- WAL-safe, faster than FULL| المخزن | المسار | الغرض |
|---|---|---|
| ConfigStore | kazma-data/settings.db | إعدادات وقت التشغيل (تتجاوز kazma.yaml) |
| حافظ نقاط الحفظ في LangGraph | kazma-data/checkpoints.db | حالة المحادثة، إيقافات الموافقة البشرية في الحلقة (HITL) |
| TaskStore | kazma-data/swarm_tasks.db | مهامّ السرب + مقاييس العمّال |
| لقطات السفر عبر الزمن | kazma-data/snapshots.db | سجلّ /replay |
| سجلّ المركز (hub) | ~/.kazma/hub/registry.db | المهارات المُثبَّتة |
| الذاكرة المُتجهة | ~/.kazma/vector_memory | عميل ChromaDB الدائم |
| مخزن الجلسات (البوابة) | قابل للإعداد | تعيين مُعرّف المنصّة ↔ thread_id |
ملاحظات تدقيق الوثائق
Section titled “ملاحظات تدقيق الوثائق”- تصحيح مُسلَّمة: وضعت الوثائق القديمة ادّعاء «الذاكرة رباعيّة الطبقات» في AGENTS.md. وهو في الواقع ينحدر من
README.mdوswarm/memory/__init__.py. الطبقات الأربع موجودة جميعها ككود لكنّ المُكيِّف غير موصول بوكيل المحادثة (انظر الذاكرة وRAG). - تحديث أرقام أسطر مواقع البناء: استشهد AGENTS.md بـ «app.py ~السطر 966» لإعادة الترجمة عند الإقلاع. الموقع الحقيقي هو
kazma-ui/kazma_ui/app.py:741-751داخل_on_startup()(السطر 721). أمّاgraph_builder.py:966فهوaiosqlite.connectغير ذي صلة. agent_handlerحزمةٌ وليست ملفًّا: فُكّagent_handler.pyالخاصّ بالبوابة إلى حزمةagent_handler/(store.py،graph.py،commands.py، …).UnifiedModelRegistryليست سوى اسمٍ مستعار لـModelRegistry(model_registry.py:950).