feat: emit real status events from on_chain_start (thinking/generating)

- Add has_tool_activity flag to distinguish first agent pass (thinking)
  from post-tool agent pass (generating)
- Replace on_chain_start debug logging with status SSE emission
- Filter to only graph-level agent nodes via graph:step: tag prefix

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gongzhiyong
2026-04-09 17:52:55 +08:00
co-authored by Claude Sonnet 4.6
parent 0cd19b527b
commit a11cb56962
+16 -7
View File
@@ -81,6 +81,7 @@ async def _stream_response(request: ChatRequest) -> AsyncIterator[bytes]:
full_content: list[str] = []
logger = logging.getLogger(__name__)
tool_start_ts: dict[str, int] = {} # track per-tool start timestamps
has_tool_activity = False
try:
async for event in graph.astream_events(
@@ -104,6 +105,7 @@ async def _stream_response(request: ChatRequest) -> AsyncIterator[bytes]:
tool_input = event.get("data", {}).get("input", {})
ts = int(time.time() * 1000)
tool_start_ts[call_id] = ts
has_tool_activity = True
yield _sse({
"type": "tool_start",
"call_id": call_id,
@@ -144,14 +146,21 @@ async def _stream_response(request: ChatRequest) -> AsyncIterator[bytes]:
elif kind == "on_chain_start":
chain_name = event.get("name", "")
metadata = event.get("metadata", {})
tags = event.get("tags", [])
logger.warning(
"on_chain_start: name=%s, langgraph_node=%s, tags=%s",
chain_name,
metadata.get("langgraph_node", ""),
tags,
)
is_graph_step = any(t.startswith("graph:step:") for t in tags)
if chain_name == "agent" and is_graph_step:
if has_tool_activity:
yield _sse({
"type": "status",
"stage": "generating",
"message": "正在生成回复...",
})
else:
yield _sse({
"type": "status",
"stage": "thinking",
"message": "正在分析...",
})
except Exception as exc:
logger.error("SSE stream error for conversation %s: %s", request.conversation_id, exc, exc_info=True)