181 lines
6.5 KiB
Markdown
181 lines
6.5 KiB
Markdown
|
|
# Orchestration Pseudocode
|
|||
|
|
|
|||
|
|
This document provides pseudocode for the core runtime components of Dynavera.
|
|||
|
|
Source references point to the submitted repository.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Multi-Turn Orchestration Loop
|
|||
|
|
|
|||
|
|
**Source:** `apps/onboarding/consumers/base.py:77–132`
|
|||
|
|
|
|||
|
|
The `orchestrate` method is the central inference loop. It accumulates a message history,
|
|||
|
|
calls the GPU inference endpoint with MCP tool definitions attached, handles any tool calls
|
|||
|
|
the model requests, and only returns once the model produces a final text response (and the
|
|||
|
|
minimum-turn threshold has been met).
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
function ORCHESTRATE(message, config, min_turns, max_turns):
|
|||
|
|
messages ← [ {role: system, content: config.system_prompt},
|
|||
|
|
{role: user, content: message} ]
|
|||
|
|
|
|||
|
|
for turn = 1 to max_turns do
|
|||
|
|
emit THOUGHT status to WebSocket client
|
|||
|
|
|
|||
|
|
response ← POST /v1/chat/completions {
|
|||
|
|
messages: messages,
|
|||
|
|
tools: MCP_ROUTER.get_tool_definitions(),
|
|||
|
|
tool_choice: "auto",
|
|||
|
|
max_tokens: resolved_max_tokens
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ai_msg ← response.choices[0].message
|
|||
|
|
append ai_msg to messages
|
|||
|
|
|
|||
|
|
if ai_msg contains tool_calls then
|
|||
|
|
for each call in ai_msg.tool_calls do
|
|||
|
|
emit TOOL_START {name, args} to client
|
|||
|
|
result ← MCP_ROUTER.handle(call.name, call.args)
|
|||
|
|
emit TOOL_RESULT {result} to client
|
|||
|
|
append {role: tool, name: call.name, content: result} to messages
|
|||
|
|
end for
|
|||
|
|
continue // re-enter loop with updated context
|
|||
|
|
|
|||
|
|
else // model returned a text response
|
|||
|
|
content ← censor(ai_msg.content)
|
|||
|
|
if turn < min_turns then
|
|||
|
|
append force_reasoning_prompt to messages
|
|||
|
|
continue // force at least one reasoning pass
|
|||
|
|
end if
|
|||
|
|
return content
|
|||
|
|
end if
|
|||
|
|
end for
|
|||
|
|
|
|||
|
|
return last_content // fallback if max_turns reached
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Key design points:**
|
|||
|
|
- Tool results are injected back into the message history before the next inference call,
|
|||
|
|
allowing the model to reason over retrieved evidence.
|
|||
|
|
- `min_turns` enforces at least one structured reasoning pass before returning, improving
|
|||
|
|
output quality on complex generation tasks.
|
|||
|
|
- All status events (`THOUGHT`, `TOOL_START`, `TOOL_RESULT`, `COMPLETED`) are streamed to
|
|||
|
|
the client over the WebSocket, making the reasoning process inspectable in the UI.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. MCP Tool Dispatch
|
|||
|
|
|
|||
|
|
**Source:** `apps/onboarding/mcp.py:42–127`
|
|||
|
|
|
|||
|
|
The `MCPRouter` exposes a fixed set of approved tools to the model. Tool definitions are
|
|||
|
|
generated at class load time from method-level `@mcp_tool` decorator metadata.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
function MCP_ROUTER.handle(tool_name, args):
|
|||
|
|
method ← tool_name_to_method_map[tool_name]
|
|||
|
|
if method is None then
|
|||
|
|
return {error: "Tool not found"}
|
|||
|
|
end if
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
return await method(args)
|
|||
|
|
catch Exception as e
|
|||
|
|
return {error: e.message}
|
|||
|
|
end try
|
|||
|
|
|
|||
|
|
// search_knowledge (lines 78–127)
|
|||
|
|
function search_knowledge(args):
|
|||
|
|
query_vector ← POST /v1/embeddings {input: args.query}
|
|||
|
|
chunks ← SELECT content, metadata
|
|||
|
|
FROM KnowledgeChunk
|
|||
|
|
WHERE organization = role.organization
|
|||
|
|
AND (role = args.role_uuid OR role IS NULL)
|
|||
|
|
AND is_active = true
|
|||
|
|
ORDER BY CosineDistance(embedding, query_vector) ASC
|
|||
|
|
LIMIT 5
|
|||
|
|
return [{content, source, relevance: 1 - distance} for chunk in chunks]
|
|||
|
|
|
|||
|
|
// update_progress (lines 129–159)
|
|||
|
|
function update_progress(args):
|
|||
|
|
session ← OnboardingSession.get(uuid=args.session_uuid)
|
|||
|
|
if args.score → session.state.last_score ← args.score
|
|||
|
|
if args.completed → session.state.completed_modules ← append(args.completed_module)
|
|||
|
|
session.save()
|
|||
|
|
return {status: "success", new_state: session.state}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Knowledge Ingestion Pipeline
|
|||
|
|
|
|||
|
|
**Source:** `apps/knowledge/tasks.py:45–117`
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
task ingest_training_file(file_uuid):
|
|||
|
|
file ← TrainingFile.get(uuid=file_uuid)
|
|||
|
|
file.status ← "ingesting"; file.save()
|
|||
|
|
|
|||
|
|
raw_text ← extract_text(file) // PDF / DOCX / TXT
|
|||
|
|
|
|||
|
|
all_chunks ← []
|
|||
|
|
for segment in split(raw_text, size=CHUNK_SIZE) do
|
|||
|
|
response ← POST /v1/semantic-chunk {
|
|||
|
|
text: segment,
|
|||
|
|
threshold: SEMANTIC_CHUNK_THRESHOLD
|
|||
|
|
}
|
|||
|
|
for (chunk_text, embedding) in zip(response.chunks, response.embeddings) do
|
|||
|
|
all_chunks.append(KnowledgeChunk {
|
|||
|
|
content: chunk_text,
|
|||
|
|
embedding: embedding, // 768-dim vector
|
|||
|
|
role: file.role,
|
|||
|
|
metadata: {source: file.file_name}
|
|||
|
|
})
|
|||
|
|
end for
|
|||
|
|
end for
|
|||
|
|
|
|||
|
|
new_chunks ← [c for c in all_chunks if c.hash not in existing_hashes]
|
|||
|
|
KnowledgeChunk.bulk_create(new_chunks)
|
|||
|
|
|
|||
|
|
file.status ← "embedded"; file.save()
|
|||
|
|
trigger update_agent_prompts_from_file(file.role.uuid)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Onboarding Generation Pipeline (CA → KA → AA)
|
|||
|
|
|
|||
|
|
**Source:** `apps/onboarding/consumers/generate.py:34–124`
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
function run_pipeline(role):
|
|||
|
|
// Phase 1 — Curriculum Agent
|
|||
|
|
context ← search_knowledge(role, query=role.name + " responsibilities")
|
|||
|
|
topics ← ORCHESTRATE(curriculum_generation_prompt(role, context), CA_config)
|
|||
|
|
→ parsed as JSON list of topic strings (max 15)
|
|||
|
|
|
|||
|
|
// Phase 2 — Knowledge Agent (one pass per topic)
|
|||
|
|
full_structure ← []
|
|||
|
|
for each topic in topics do
|
|||
|
|
hits ← search_knowledge(role, query=topic)
|
|||
|
|
content ← ORCHESTRATE(knowledge_generation_prompt(topic, hits), KA_config,
|
|||
|
|
min_turns=2, max_tokens=3500)
|
|||
|
|
full_structure.append({title: topic, body: content})
|
|||
|
|
end for
|
|||
|
|
|
|||
|
|
// Phase 3 — Assessment Agent
|
|||
|
|
quiz_fields ← ORCHESTRATE(quiz_generation_prompt(topics, module_briefs), AA_config)
|
|||
|
|
→ sanitised and validated; fallback quiz generated if JSON invalid
|
|||
|
|
|
|||
|
|
full_structure.append({title: "Final Assessment Quiz", fields: quiz_fields,
|
|||
|
|
meta: {pass_mark: 80}})
|
|||
|
|
|
|||
|
|
OnboardingFlow.save(role, full_structure)
|
|||
|
|
emit COMPLETED to client
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Grading strategy:**
|
|||
|
|
- Multiple-choice questions: deterministic string comparison against `correct_option`
|
|||
|
|
- Free-text / textarea responses: agent-graded by the AA at session completion
|
|||
|
|
- Per-question outcomes persisted in session state for audit and feedback rendering
|