feat: stream intermediate assistant turns over MCP
Install a per-request `after_llm_call` hook that emits each intermediate assistant turn as an MCP `notifications/message`, so users see substantive text from earlier loop iterations instead of only the final `agent.send()` return value. Add tests covering the hook's payload shape, error handling, and lifecycle via `install_for_request`.
This commit is contained in:
@@ -28,6 +28,7 @@ from fast_agent.core.logging.logger import get_logger
|
||||
from fast_agent.mcp.server import AgentMCPServer
|
||||
from fast_agent.types import PromptMessageExtended, RequestParams
|
||||
|
||||
from pallas.assistant_stream import install_for_request as _install_assistant_stream
|
||||
from pallas.progress import EnrichedMCPToolProgressManager
|
||||
from pallas import metrics as _pallas_metrics
|
||||
from fastmcp import Context as MCPContext
|
||||
@@ -220,6 +221,20 @@ class MultimodalAgentMCPServer(AgentMCPServer):
|
||||
agent_context = getattr(agent, "context", None)
|
||||
metrics_start = time.perf_counter()
|
||||
metrics_outcome = "ok"
|
||||
|
||||
# Install per-request after_llm_call hook that ships every
|
||||
# intermediate assistant turn over MCP as a notifications/message.
|
||||
# Without this, only the final ``agent.send()`` return value
|
||||
# crosses the MCP boundary — substantive assistant text emitted
|
||||
# in earlier loop iterations stays trapped inside fast-agent's
|
||||
# ``message_history`` and the user sees a spinner that ends with
|
||||
# a thin wrap-up sentence.
|
||||
restore_hooks = _install_assistant_stream(
|
||||
agent,
|
||||
ctx=ctx,
|
||||
agent_name=agent_name,
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
try:
|
||||
# Seed the freshly-created instance's message_history from the
|
||||
# caller-supplied history so the agent sees the full
|
||||
@@ -313,6 +328,14 @@ class MultimodalAgentMCPServer(AgentMCPServer):
|
||||
_pallas_metrics.send_message_total.labels(
|
||||
agent=agent_name, outcome=metrics_outcome
|
||||
).inc()
|
||||
# Restore the agent's prior tool_runner_hooks before the
|
||||
# instance is released — defensive against any future
|
||||
# shared-instance mode where leaking per-request hooks
|
||||
# across requests would mis-attribute notifications.
|
||||
try:
|
||||
restore_hooks()
|
||||
except Exception:
|
||||
pass
|
||||
await self._release_instance(ctx, instance)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user