Added streaming
This commit is contained in:
@@ -44,6 +44,13 @@ class ProviderClient:
|
||||
temperature: float, max_tokens: int) -> str:
|
||||
return self.call(system, messages, model, temperature, max_tokens)
|
||||
|
||||
async def call_async_streaming(self, system: str, messages: list[dict], model: str,
|
||||
temperature: float, max_tokens: int):
|
||||
"""Async generator yielding ('thinking'|'content', text) chunks.
|
||||
Default: emits the full response as a single 'content' chunk."""
|
||||
response = await self.call_async(system, messages, model, temperature, max_tokens)
|
||||
yield ('content', response)
|
||||
|
||||
|
||||
# --- OpenAI and any OpenAI-compatible endpoint ---
|
||||
# Covers AIPA_PROVIDER / {AGENT}_PROVIDER = openai or openai_compatible.
|
||||
@@ -71,6 +78,29 @@ class OpenAIClient(ProviderClient):
|
||||
self.call_async(system, messages, model, temperature, max_tokens)
|
||||
)
|
||||
|
||||
async def call_async_streaming(self, system, messages, model, temperature, max_tokens):
|
||||
"""Stream via the OpenAI-compatible API, yielding thinking and content chunks.
|
||||
Thinking tokens arrive in delta.reasoning_content (Qwen3, DeepSeek R1 style);
|
||||
regular tokens arrive in delta.content."""
|
||||
full_messages = [{"role": "system", "content": system}] + messages
|
||||
stream = await self.client.chat.completions.create(
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
messages=full_messages,
|
||||
extra_body=self.extra_body or None,
|
||||
stream=True,
|
||||
)
|
||||
async for chunk in stream:
|
||||
if not chunk.choices:
|
||||
continue
|
||||
delta = chunk.choices[0].delta
|
||||
thinking = getattr(delta, 'reasoning_content', None)
|
||||
if thinking:
|
||||
yield ('thinking', thinking)
|
||||
if delta.content:
|
||||
yield ('content', delta.content)
|
||||
|
||||
|
||||
# --- Anthropic ---
|
||||
# Uncomment when ready to use. Install: pip install anthropic
|
||||
@@ -283,6 +313,8 @@ async def call_agent_async(
|
||||
agent: AgentState,
|
||||
user_message: str,
|
||||
dry_run: bool = False,
|
||||
stream: bool = True,
|
||||
stream_mode: str = "deliverable",
|
||||
) -> str:
|
||||
if dry_run:
|
||||
return f"[DRY RUN] Would call {agent.name} ({config.agent_provider_name(agent.name)}) with: {user_message[:100]}..."
|
||||
@@ -290,19 +322,32 @@ async def call_agent_async(
|
||||
agent.add_user(user_message)
|
||||
|
||||
try:
|
||||
response = await agent.client.call_async(
|
||||
system=agent.system_prompt,
|
||||
messages=agent.messages_for_call(),
|
||||
model=agent.model,
|
||||
temperature=agent.temperature,
|
||||
max_tokens=agent.max_tokens,
|
||||
)
|
||||
if stream:
|
||||
response = await ui.stream_agent_output(
|
||||
agent.name,
|
||||
agent.client.call_async_streaming(
|
||||
system=agent.system_prompt,
|
||||
messages=agent.messages_for_call(),
|
||||
model=agent.model,
|
||||
temperature=agent.temperature,
|
||||
max_tokens=agent.max_tokens,
|
||||
),
|
||||
mode=stream_mode,
|
||||
)
|
||||
else:
|
||||
response = await agent.client.call_async(
|
||||
system=agent.system_prompt,
|
||||
messages=agent.messages_for_call(),
|
||||
model=agent.model,
|
||||
temperature=agent.temperature,
|
||||
max_tokens=agent.max_tokens,
|
||||
)
|
||||
except NotImplementedError as e:
|
||||
raise RuntimeError(str(e)) from e
|
||||
|
||||
agent.add_assistant(response)
|
||||
|
||||
if config.DEBUG_PRINT_AGENT_OUTPUTS:
|
||||
if config.DEBUG_PRINT_AGENT_OUTPUTS and not stream:
|
||||
ui.print_agent_panel(agent.name, response)
|
||||
|
||||
return response
|
||||
@@ -357,7 +402,7 @@ async def dispatch_to_lead(
|
||||
if session_id:
|
||||
task_store.update_task(task, session_id)
|
||||
try:
|
||||
output = await call_agent_async(lead, task.brief, dry_run=dry_run)
|
||||
output = await call_agent_async(lead, task.brief, dry_run=dry_run, stream=False)
|
||||
task.output = output
|
||||
task.status = "complete"
|
||||
except Exception as e:
|
||||
@@ -424,7 +469,7 @@ async def run_auditor(
|
||||
f"{deliverable}"
|
||||
)
|
||||
auditor.history = [] # Auditor is stateless — fresh call each time
|
||||
return await call_agent_async(auditor, audit_request, dry_run=dry_run)
|
||||
return await call_agent_async(auditor, audit_request, dry_run=dry_run, stream_mode="audit")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -446,7 +491,7 @@ async def update_standing_brief(
|
||||
f"log entry at the top of Section 9. Archive anything older than "
|
||||
f"{config.SESSION_LOG_RETENTION} sessions."
|
||||
)
|
||||
return await call_agent_async(orchestrator, update_request, dry_run=dry_run)
|
||||
return await call_agent_async(orchestrator, update_request, dry_run=dry_run, stream_mode="background")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -502,7 +547,7 @@ class Session:
|
||||
f"Directive: {directive}"
|
||||
)
|
||||
cos_response = await call_agent_async(
|
||||
self.orchestrator, cos_prompt, dry_run=self.dry_run
|
||||
self.orchestrator, cos_prompt, dry_run=self.dry_run, stream=True
|
||||
)
|
||||
|
||||
# ── Step 2: Parse any task briefs the orchestrator issued ───────────
|
||||
@@ -532,10 +577,9 @@ class Session:
|
||||
f"Please synthesize these into a final deliverable for the Principal.\n\n"
|
||||
f"{lead_outputs_text}"
|
||||
)
|
||||
with ui.synthesis_progress():
|
||||
deliverable = await call_agent_async(
|
||||
self.orchestrator, synthesis_prompt, dry_run=self.dry_run
|
||||
)
|
||||
deliverable = await call_agent_async(
|
||||
self.orchestrator, synthesis_prompt, dry_run=self.dry_run, stream=True
|
||||
)
|
||||
|
||||
dispatched_leads = sorted({t.assigned_to for t in completed_tasks})
|
||||
self.session_log.append(
|
||||
@@ -549,10 +593,9 @@ class Session:
|
||||
audit_memo = ""
|
||||
if self.auto_audit:
|
||||
try:
|
||||
with ui.audit_progress():
|
||||
audit_memo = await run_auditor(
|
||||
self.auditor, deliverable, task_id, dry_run=self.dry_run
|
||||
)
|
||||
audit_memo = await run_auditor(
|
||||
self.auditor, deliverable, task_id, dry_run=self.dry_run
|
||||
)
|
||||
self.session_log.append(f"Audit complete for {task_id}.")
|
||||
except RuntimeError as e:
|
||||
ui.print_warning(f"Audit skipped — {e}")
|
||||
@@ -561,10 +604,9 @@ class Session:
|
||||
|
||||
async def close(self):
|
||||
summary = "\n".join(self.session_log)
|
||||
with ui.brief_update_progress():
|
||||
updated_brief = await update_standing_brief(
|
||||
self.orchestrator, summary, dry_run=self.dry_run
|
||||
)
|
||||
updated_brief = await update_standing_brief(
|
||||
self.orchestrator, summary, dry_run=self.dry_run
|
||||
)
|
||||
if not self.dry_run:
|
||||
save_standing_brief(updated_brief)
|
||||
ui.print_session_footer(self.directive_count)
|
||||
@@ -645,17 +687,26 @@ async def cmd_history(session: Session, args: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@command(["/thinking"], "Show the thinking output from the last response")
|
||||
async def cmd_thinking(session: Session, args: str) -> bool:
|
||||
last = ui.get_last_thinking()
|
||||
if not last:
|
||||
ui.print_system("No thinking output recorded — thinking mode may not be active.")
|
||||
else:
|
||||
ui.print_thinking_expansion(last)
|
||||
return True
|
||||
|
||||
|
||||
@command(["/audit"], "Re-run Vera's audit on the last deliverable")
|
||||
async def cmd_audit(session: Session, args: str) -> bool:
|
||||
if not session.last_deliverable:
|
||||
ui.print_warning("No deliverable yet — issue a directive first.")
|
||||
return True
|
||||
ui.print_system("Running audit on last deliverable…")
|
||||
with ui.audit_progress():
|
||||
memo = await run_auditor(
|
||||
session.auditor, session.last_deliverable,
|
||||
session.last_task_id, dry_run=session.dry_run,
|
||||
)
|
||||
memo = await run_auditor(
|
||||
session.auditor, session.last_deliverable,
|
||||
session.last_task_id, dry_run=session.dry_run,
|
||||
)
|
||||
ui.print_audit_memo(memo, task_id=session.last_task_id)
|
||||
return True
|
||||
|
||||
@@ -731,7 +782,15 @@ async def direct_agent_session(agent_name: str, dry_run: bool = False):
|
||||
if not user_input or user_input.lower() in ("/quit", "/exit", "/q"):
|
||||
break
|
||||
|
||||
response = await call_agent_async(agent, user_input, dry_run=dry_run)
|
||||
if user_input.lower() == "/thinking":
|
||||
last = ui.get_last_thinking()
|
||||
if not last:
|
||||
ui.print_system("No thinking output recorded — thinking mode may not be active.")
|
||||
else:
|
||||
ui.print_thinking_expansion(last)
|
||||
continue
|
||||
|
||||
response = await call_agent_async(agent, user_input, dry_run=dry_run, stream=True)
|
||||
ui.print_agent_panel(agent_name, response)
|
||||
|
||||
ui.print_session_footer(0)
|
||||
|
||||
Reference in New Issue
Block a user