تخطَّ إلى المحتوى

البنية المعمارية

تفصيل عميق ومُوثّق بالكود المصدري لمحرك كاظمة: العقل المُشرف، ومسار البيانات من نيّة المستخدم إلى تنفيذ الأداة، والأنظمة الفرعية التي تجعله دائمًا وآمنًا ومتعدّد اللغات.


1. الفلسفة: عقلٌ واحد، وأفواهٌ متعدّدة

Section titled “1. الفلسفة: عقلٌ واحد، وأفواهٌ متعدّدة”

نُظِّم كاظمة حول فصلٍ صارم بين الاستدلال (الرسم البياني المُشرف في LangGraph) والنقل (مُحوّلات المنصّات). تخدم نسخةٌ واحدة من الرسم البياني — تُبنى مرّةً واحدة عبر build_supervisor_graph() — كلَّ القنوات. لا يطّلع الرسم البياني قطّ على المُعرّفات الخاصّة بالمنصّة؛ فالمُحوّلات تمتلكها ولا تُعيد إرفاقها إلا عند إصدار الردّ.

ينتج عن ذلك ثلاث خصائص يعتمد عليها بقية النظام:

  1. حرية المُزوّد — يتحدّث العقل إلى أيّ نقطة نهاية متوافقة مع OpenAI عبر httpx. لا يُستورد أيّ حزمة SDK خاصّة بمورّد.
  2. تكافؤ القنوات — إيقافُ الموافقة البشرية في الحلقة (HITL)، واستدعاءُ الأداة، ورمزُ البثّ متطابقةٌ تمامًا سواءٌ صدرت من Telegram أم من واجهة الويب.
  3. حالة دائمة — بما أنّ مُعرّفات المنصّة تعيش خارج الرسم البياني، فإنّ حالة الرسم البياني هي حالة حواريّة بحتة يمكن حفظها كنقطة حفظ وإعادة تشغيلها واستئنافها عبر عمليّات إعادة التشغيل.

كاظمة مستودعٌ موحّد (monorepo) من سبع حزمٍ قابلة للتثبيت (مُعرَّفة في pyproject.toml ضمن [tool.hatch.build.targets.wheel]):

الحزمةالمسارالمسؤولية
kazma-corekazma-core/kazma_core/مشغّل الوكيل، مُزوّد LLM، سجلّ النماذج، محرّك السرب، ConfigStore، السلامة، الذاكرة، المهارات، MCP، المركز (hub)، التفويض، التكثيف، المجلس (Majlis)
kazma-gatewaykazma-gateway/kazma_gateway/مُحوّلات Telegram/Discord/Slack، معالج الوكيل (جسر الرسم البياني)، أوامر الشرطة المائلة (slash commands)، مخزن الجلسات
kazma-uikazma-ui/kazma_ui/مصنع تطبيق FastAPI، محادثة SSE، لوحة السرب، الإعدادات، لوحة التحكم، التدويل (i18n)، الأصول الثابتة
kazma-tuikazma-tui/kazma_tui/لوحة تحكم TUI قائمة على Textual (مستهلك يقرأ في الغالب نسخ singleton الخاصة بالنواة)
kazma-memorykazma-memory/kazma_memory/مُجزّئ النصوص العربي + خلفية بحث SQLite/FTS5
kazma-skillskazma-skills/kazma_skills/بيانيّات المهارات (بيانات)
kazma-clikazma-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"

الرسم البياني النواة هو حلقة ReAct مبنية في kazma-core/kazma_core/agent/graph_builder.py.

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% من النافذة)، فإنها تُلخّص وتعيد بناء قائمة الرسائل.
  • عقدة الردّ — تُنهي ردّ المساعد تمهيدًا للبثّ.

يُبنى الرسم البياني عبر 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 endpoint
def 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 graph
  • حافظ نقاط الحفظ: 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_stateagent_handler/store.py:16,95
يُعاد توجيه الردّ إلى المحادثة الصحيحة_build_target_id(platform, ctx)agent_handler/store.py:146
نموذج المُزوّد الخاطئ لا يصل قطّ إلى نقطة النهاية الخاطئةالتصحيح التلقائي في get_client()model_registry.py:275-303
الأدوات الخطيرة تُوقف ولا تُنفَّذ بصمتinterrupt() + علم _hitl_approvedgraph_builder.py:483-506
تبديل المُزوّد يُبطِل العملاء المُستهلكينset_active_model يُفرغ الذاكرة المؤقتةmodel_registry.py:248

kazma-core/kazma_core/llm_provider.py هو عميل httpx رفيع بلا حزمة SDK. يتحدّث بصيغة نقل OpenAI Chat Completions السلكية إلى أيّ نقطة نهاية متوافقة.

تُعيد 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_lower
tool_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 وغيرها من المُزوّدين الصارمين.

البثّ هو مُولّد غير متزامن مستقلّ 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_WINDOWcost_breaker.py
إعادة المحاولةمزخرفات قائمة على tenacity، للأخطاء الشبكيّة/المهلة فقط، لا تشمل 4xxretry.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). تنفّذ ذلك ثلاثة مكوّنات:

  1. المُجزّئ النصّي العربي (kazma-memory/kazma_memory/arabic_tokenizer.py) — إزالة التشكيل، وتطبيع الألف/الياء/التاء المربوطة، وحذف التطويل، وكلمات الإيقاف باللهجة الكويتيّة، وجذّاع أساسي. يُغذّي عمود FTS5 المُسمّى content_arabic.
  2. التدويل (i18n) وواجهة RTL (kazma-ui/kazma_ui/i18n.py، static/css/kazma.css) — قاموس TRANSLATIONS مضمّن (EN/AR)، وdir/lang لكلّ طلب، ومكدّس خطوط يبدأ بـ Calibri، وأساس 16px لـ RTL، وأرضيّة قراءة على الفئات الصغيرة.
  3. بروتوكول المجلس (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)
OpenTelemetryopentelemetry-api/sdk في التبعيّات؛ وإضافة [tracing] تحوي مُصدّرات OTLPالمكتبة موجودة؛ التوصيل ضمن خارطة الطريق
Langfuselangfuse>=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 writer
PRAGMA busy_timeout=5000; -- 5 s wait on lock
PRAGMA synchronous=NORMAL; -- WAL-safe, faster than FULL
المخزنالمسارالغرض
ConfigStorekazma-data/settings.dbإعدادات وقت التشغيل (تتجاوز kazma.yaml)
حافظ نقاط الحفظ في LangGraphkazma-data/checkpoints.dbحالة المحادثة، إيقافات الموافقة البشرية في الحلقة (HITL)
TaskStorekazma-data/swarm_tasks.dbمهامّ السرب + مقاييس العمّال
لقطات السفر عبر الزمنkazma-data/snapshots.dbسجلّ /replay
سجلّ المركز (hub)~/.kazma/hub/registry.dbالمهارات المُثبَّتة
الذاكرة المُتجهة~/.kazma/vector_memoryعميل ChromaDB الدائم
مخزن الجلسات (البوابة)قابل للإعدادتعيين مُعرّف المنصّة ↔ thread_id

  • تصحيح مُسلَّمة: وضعت الوثائق القديمة ادّعاء «الذاكرة رباعيّة الطبقات» في 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).