feat: add workflow action outcomes and tracing

This commit is contained in:
Xin Wang
2026-08-01 11:21:31 +08:00
parent ad5ff061bb
commit b747144ff1
13 changed files with 558 additions and 32 deletions

View File

@@ -5,6 +5,7 @@ conversation_artifacts并把对象存储地址关联到会话或消息。
"""
import asyncio
from copy import deepcopy
from datetime import UTC, datetime
from uuid import uuid4
@@ -13,6 +14,9 @@ from db.session import SessionLocal
from loguru import logger
MAX_WORKFLOW_TRACE_EVENTS = 1000
def _parse_timestamp(value: object) -> datetime:
if not isinstance(value, str) or not value:
return datetime.now(UTC)
@@ -29,6 +33,7 @@ class ConversationRecorder:
def __init__(self, session_id: str):
self.session_id = session_id
self._sequence = 0
self._trace_sequence = 0
self._lock = asyncio.Lock()
self._seen_events: set[str] = set()
@@ -41,6 +46,7 @@ class ConversationRecorder:
channel: str,
runtime_mode: str,
session_id: str | None = None,
extra: dict | None = None,
) -> "ConversationRecorder | None":
session_id = session_id or f"conv_{uuid4().hex[:20]}"
try:
@@ -54,7 +60,7 @@ class ConversationRecorder:
runtime_mode=runtime_mode,
status="active",
message_count=0,
extra={},
extra=deepcopy(extra or {}),
)
)
await db.commit()
@@ -67,6 +73,9 @@ class ConversationRecorder:
if not isinstance(message, dict):
return
event_type = message.get("type")
if event_type == "workflow-event":
await self._append_workflow_event(message)
return
role = ""
content = ""
extra: dict = {}
@@ -98,6 +107,36 @@ class ConversationRecorder:
self._seen_events.add(event_key)
await self._append(role, content, timestamp, extra)
async def _append_workflow_event(self, message: dict) -> None:
"""Persist bounded control-plane history without creating chat rows."""
event_id = str(message.get("eventId") or "")
event_name = str(message.get("event") or "")
event_key = f"workflow:{event_id}"
if not event_id or not event_name or event_key in self._seen_events:
return
self._seen_events.add(event_key)
async with self._lock:
next_sequence = self._trace_sequence + 1
payload = deepcopy(message)
payload["sessionId"] = self.session_id
payload["sequence"] = next_sequence
try:
async with SessionLocal() as db:
conversation = await db.get(ConversationSession, self.session_id)
if not conversation:
return
extra = dict(conversation.extra or {})
trace = list(extra.get("workflowTrace") or [])
trace.append(payload)
extra["workflowTrace"] = trace[-MAX_WORKFLOW_TRACE_EVENTS:]
conversation.extra = extra
await db.commit()
self._trace_sequence = next_sequence
except Exception as exc:
logger.error(f"保存 Workflow 轨迹失败,不影响本次通话: {exc}")
async def _append(
self,
role: str,
@@ -130,13 +169,14 @@ class ConversationRecorder:
logger.error(f"保存对话文本失败,不影响本次通话: {exc}")
async def finish(self, *, status: str = "completed") -> None:
try:
async with SessionLocal() as db:
conversation = await db.get(ConversationSession, self.session_id)
if conversation:
conversation.status = status
conversation.ended_at = datetime.now(UTC)
conversation.message_count = self._sequence
await db.commit()
except Exception as exc:
logger.error(f"结束对话历史会话失败: {exc}")
async with self._lock:
try:
async with SessionLocal() as db:
conversation = await db.get(ConversationSession, self.session_id)
if conversation:
conversation.status = status
conversation.ended_at = datetime.now(UTC)
conversation.message_count = self._sequence
await db.commit()
except Exception as exc:
logger.error(f"结束对话历史会话失败: {exc}")