fix: harden streaming chat persistence and access control
Persist streaming chat state during generator cleanup, close the SSE inner stream safely, and reject cross-user conversation access while locking the behavior with focused regressions.
This commit is contained in:
@@ -130,34 +130,42 @@ async def chat_stream(
|
|||||||
agent_svc = AgentService(db)
|
agent_svc = AgentService(db)
|
||||||
|
|
||||||
async def stream_generator():
|
async def stream_generator():
|
||||||
|
stream = None
|
||||||
|
msg_id = None
|
||||||
|
should_emit_done = False
|
||||||
try:
|
try:
|
||||||
conv_id, msg_id, stream = await agent_svc.chat(
|
try:
|
||||||
user_id=current_user.id,
|
conv_id, msg_id, stream = await agent_svc.chat(
|
||||||
message=data.message,
|
user_id=current_user.id,
|
||||||
conversation_id=data.conversation_id,
|
message=data.message,
|
||||||
file_ids=data.file_ids,
|
conversation_id=data.conversation_id,
|
||||||
model_name=data.model_name,
|
file_ids=data.file_ids,
|
||||||
)
|
model_name=data.model_name,
|
||||||
except ValueError as exc:
|
)
|
||||||
yield f"event: error\ndata: {json.dumps({'error': str(exc)}, ensure_ascii=False)}\n\n"
|
except ValueError as exc:
|
||||||
return
|
yield f"event: error\ndata: {json.dumps({'error': str(exc)}, ensure_ascii=False)}\n\n"
|
||||||
|
return
|
||||||
|
|
||||||
yield f"event: metadata\ndata: {json.dumps({'conversation_id': conv_id, 'message_id': msg_id})}\n\n"
|
yield f"event: metadata\ndata: {json.dumps({'conversation_id': conv_id, 'message_id': msg_id})}\n\n"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async for event in stream:
|
async for event in stream:
|
||||||
event_type = event.get('type', 'progress')
|
event_type = event.get('type', 'progress')
|
||||||
if event_type == 'chunk':
|
if event_type == 'chunk':
|
||||||
yield f"event: chunk\ndata: {json.dumps({'content': event.get('content', '')}, ensure_ascii=False)}\n\n"
|
yield f"event: chunk\ndata: {json.dumps({'content': event.get('content', '')}, ensure_ascii=False)}\n\n"
|
||||||
elif event_type == 'error':
|
elif event_type == 'error':
|
||||||
yield f"event: error\ndata: {json.dumps({'error': event.get('error', '未知错误')}, ensure_ascii=False)}\n\n"
|
yield f"event: error\ndata: {json.dumps({'error': event.get('error', '未知错误')}, ensure_ascii=False)}\n\n"
|
||||||
else:
|
else:
|
||||||
payload = {k: v for k, v in event.items() if k != 'type'}
|
payload = {k: v for k, v in event.items() if k != 'type'}
|
||||||
yield f"event: progress\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
yield f"event: progress\ndata: {json.dumps(payload, ensure_ascii=False)}\n\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
|
yield f"event: error\ndata: {json.dumps({'error': str(e)}, ensure_ascii=False)}\n\n"
|
||||||
|
should_emit_done = msg_id is not None
|
||||||
|
if should_emit_done:
|
||||||
|
yield f"event: done\ndata: {json.dumps({'message_id': msg_id})}\n\n"
|
||||||
finally:
|
finally:
|
||||||
yield f"event: done\ndata: {json.dumps({'message_id': msg_id})}\n\n"
|
if stream is not None:
|
||||||
|
await stream.aclose()
|
||||||
|
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
stream_generator(),
|
stream_generator(),
|
||||||
|
|||||||
@@ -53,6 +53,73 @@ def _is_streaming_rejection_error(error: Exception, user_llm_config: dict | None
|
|||||||
return any(marker in error_text for marker in markers)
|
return any(marker in error_text for marker in markers)
|
||||||
|
|
||||||
|
|
||||||
|
def _coerce_event_text(content: Any) -> str:
|
||||||
|
if isinstance(content, str):
|
||||||
|
return content
|
||||||
|
if isinstance(content, list):
|
||||||
|
parts: list[str] = []
|
||||||
|
for item in content:
|
||||||
|
if isinstance(item, str):
|
||||||
|
parts.append(item)
|
||||||
|
elif isinstance(item, dict):
|
||||||
|
text = item.get("text")
|
||||||
|
if isinstance(text, str):
|
||||||
|
parts.append(text)
|
||||||
|
return "".join(parts)
|
||||||
|
return str(content) if content else ""
|
||||||
|
|
||||||
|
|
||||||
|
_CONTINUITY_STATE_VERSION = 1
|
||||||
|
_CONTINUITY_SNAPSHOT_FIELDS = (
|
||||||
|
"turn_context",
|
||||||
|
"routing_decision",
|
||||||
|
"continuity_state",
|
||||||
|
"pending_action",
|
||||||
|
"last_completed_action",
|
||||||
|
"clarification_context",
|
||||||
|
"tool_outcomes",
|
||||||
|
"pending_tasks",
|
||||||
|
"completed_tasks",
|
||||||
|
"created_entities",
|
||||||
|
"current_agent",
|
||||||
|
"next_step",
|
||||||
|
"agent_trace",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_continuity_snapshot(state: dict[str, Any]) -> dict[str, Any] | None:
|
||||||
|
snapshot = {
|
||||||
|
field: state.get(field)
|
||||||
|
for field in _CONTINUITY_SNAPSHOT_FIELDS
|
||||||
|
if state.get(field) is not None
|
||||||
|
}
|
||||||
|
if not snapshot:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"version": _CONTINUITY_STATE_VERSION,
|
||||||
|
"state": snapshot,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_continuity_snapshot(payload: Any) -> dict[str, Any] | None:
|
||||||
|
if isinstance(payload, list):
|
||||||
|
for item in payload:
|
||||||
|
snapshot = _extract_continuity_snapshot(item)
|
||||||
|
if snapshot:
|
||||||
|
return snapshot
|
||||||
|
return None
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
return None
|
||||||
|
if payload.get("kind") != "agent_continuity_state":
|
||||||
|
return None
|
||||||
|
if payload.get("version") != _CONTINUITY_STATE_VERSION:
|
||||||
|
return None
|
||||||
|
state = payload.get("state")
|
||||||
|
if isinstance(state, dict):
|
||||||
|
return state
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AgentService:
|
class AgentService:
|
||||||
"""对话 Agent 服务"""
|
"""对话 Agent 服务"""
|
||||||
|
|
||||||
@@ -83,10 +150,23 @@ class AgentService:
|
|||||||
"steps": steps or [],
|
"steps": steps or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _build_current_datetime_context(self) -> tuple[str, dict[str, str]]:
|
||||||
|
now_utc = datetime.now(UTC)
|
||||||
|
reference = {
|
||||||
|
"current_time_iso": now_utc.isoformat(),
|
||||||
|
"current_date_iso": now_utc.date().isoformat(),
|
||||||
|
}
|
||||||
|
context = (
|
||||||
|
"【当前时间】\n"
|
||||||
|
f"- current_time_utc: {reference['current_time_iso']}\n"
|
||||||
|
f"- current_date_utc: {reference['current_date_iso']}\n"
|
||||||
|
"说明:解析‘今天/明天/后天/本周/下周’等相对时间时,请以 current_time_utc 为准。"
|
||||||
|
)
|
||||||
|
return context, reference
|
||||||
|
|
||||||
async def _get_user_llm_config(self, user_id: str, model_name: str | None = None) -> dict | None:
|
async def _get_user_llm_config(self, user_id: str, model_name: str | None = None) -> dict | None:
|
||||||
"""获取用户的 LLM 模型配置"""
|
"""获取用户的 LLM 模型配置"""
|
||||||
result = await self.db.execute(select(User).where(User.id == user_id))
|
user = await self.db.get(User, user_id)
|
||||||
user = result.scalar_one_or_none()
|
|
||||||
if not user or not user.llm_config:
|
if not user or not user.llm_config:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -106,6 +186,47 @@ class AgentService:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
async def _load_continuity_snapshot(self, conversation: Conversation) -> dict[str, Any] | None:
|
||||||
|
snapshot = _extract_continuity_snapshot(conversation.agent_state)
|
||||||
|
if snapshot:
|
||||||
|
return snapshot
|
||||||
|
|
||||||
|
result = await self.db.execute(
|
||||||
|
select(Message)
|
||||||
|
.where(Message.conversation_id == conversation.id, Message.role == "assistant")
|
||||||
|
.order_by(Message.created_at.desc())
|
||||||
|
)
|
||||||
|
for message in result.scalars():
|
||||||
|
snapshot = _extract_continuity_snapshot(message.attachments)
|
||||||
|
if snapshot:
|
||||||
|
return snapshot
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _build_agent_state(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
user_id: str,
|
||||||
|
conversation: Conversation,
|
||||||
|
full_message: str,
|
||||||
|
memory_context: str | None,
|
||||||
|
current_datetime_context: str,
|
||||||
|
current_datetime_reference: dict[str, str],
|
||||||
|
user_llm_config: dict | None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
state = initial_state(user_id, conversation.id)
|
||||||
|
state.update({
|
||||||
|
"messages": [HumanMessage(content=full_message)],
|
||||||
|
"memory_context": memory_context,
|
||||||
|
"current_datetime_context": current_datetime_context,
|
||||||
|
"current_datetime_reference": current_datetime_reference,
|
||||||
|
"user_llm_config": user_llm_config,
|
||||||
|
})
|
||||||
|
previous_snapshot = await self._load_continuity_snapshot(conversation)
|
||||||
|
if previous_snapshot:
|
||||||
|
state.update(previous_snapshot)
|
||||||
|
state["messages"] = [HumanMessage(content=full_message)]
|
||||||
|
return state
|
||||||
|
|
||||||
async def chat(
|
async def chat(
|
||||||
self,
|
self,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
@@ -138,9 +259,14 @@ class AgentService:
|
|||||||
|
|
||||||
if conversation_id:
|
if conversation_id:
|
||||||
result = await self.db.execute(
|
result = await self.db.execute(
|
||||||
select(Conversation).where(Conversation.id == conversation_id)
|
select(Conversation).where(
|
||||||
|
Conversation.id == conversation_id,
|
||||||
|
Conversation.user_id == user_id,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
conv = result.scalar_one_or_none()
|
conv = result.scalar_one_or_none()
|
||||||
|
if conv is None:
|
||||||
|
raise ValueError("会话不存在或无权访问")
|
||||||
else:
|
else:
|
||||||
conv = None
|
conv = None
|
||||||
|
|
||||||
@@ -203,33 +329,38 @@ class AgentService:
|
|||||||
await self.db.commit()
|
await self.db.commit()
|
||||||
await self.db.refresh(assistant_msg)
|
await self.db.refresh(assistant_msg)
|
||||||
|
|
||||||
def _build_current_datetime_context() -> str:
|
def _build_assistant_event_payload(content: str) -> dict[str, Any]:
|
||||||
now_utc = datetime.now(UTC)
|
return {
|
||||||
return (
|
"source_type": "conversation",
|
||||||
"【当前时间】\n"
|
"source_id": conversation_id,
|
||||||
f"- current_time_utc: {now_utc.isoformat()}\n"
|
"event_type": "message_created",
|
||||||
f"- current_date_utc: {now_utc.date().isoformat()}\n"
|
"title": "Assistant message",
|
||||||
"说明:解析‘今天/明天/后天/本周/下周’等相对时间时,请以 current_time_utc 为准。"
|
"content_summary": content[:500],
|
||||||
)
|
"raw_excerpt": content[:2000],
|
||||||
|
"metadata_": {"role": "assistant"},
|
||||||
|
"importance_signal": 0.8,
|
||||||
|
}
|
||||||
|
|
||||||
async def run_agent():
|
async def run_agent():
|
||||||
|
collected = ""
|
||||||
|
state: dict[str, Any] | None = None
|
||||||
set_current_user(user_id)
|
set_current_user(user_id)
|
||||||
try:
|
try:
|
||||||
graph = get_agent_graph()
|
graph = get_agent_graph()
|
||||||
current_datetime_context = _build_current_datetime_context()
|
current_datetime_context, current_datetime_reference = self._build_current_datetime_context()
|
||||||
|
|
||||||
# 使用 initial_state 构建状态
|
state = await self._build_agent_state(
|
||||||
state = initial_state(user_id, conversation_id)
|
user_id=user_id,
|
||||||
state.update({
|
conversation=conv,
|
||||||
"messages": [HumanMessage(content=full_message)],
|
full_message=full_message,
|
||||||
"memory_context": memory_ctx,
|
memory_context=memory_ctx,
|
||||||
"current_datetime_context": current_datetime_context,
|
current_datetime_context=current_datetime_context,
|
||||||
"user_llm_config": user_llm_config,
|
current_datetime_reference=current_datetime_reference,
|
||||||
})
|
user_llm_config=user_llm_config,
|
||||||
|
)
|
||||||
|
|
||||||
yield self._build_progress_event("thinking", "Jarvis 正在分析请求", agent="master", step="理解你的问题")
|
yield self._build_progress_event("thinking", "Jarvis 正在分析请求", agent="master", step="理解你的问题")
|
||||||
|
|
||||||
collected = ""
|
|
||||||
try:
|
try:
|
||||||
async for event in graph.astream_events(state, version="v2"):
|
async for event in graph.astream_events(state, version="v2"):
|
||||||
kind = event.get("event")
|
kind = event.get("event")
|
||||||
@@ -272,49 +403,77 @@ class AgentService:
|
|||||||
|
|
||||||
elif kind == "on_chat_model_stream":
|
elif kind == "on_chat_model_stream":
|
||||||
chunk = data.get("chunk")
|
chunk = data.get("chunk")
|
||||||
content = getattr(chunk, "content", "") if chunk else ""
|
content = _coerce_event_text(getattr(chunk, "content", "") if chunk else "")
|
||||||
if content:
|
if content:
|
||||||
collected += content
|
collected += content
|
||||||
yield {"type": "chunk", "content": content}
|
yield {"type": "chunk", "content": content}
|
||||||
|
|
||||||
elif kind == "on_chain_end" and event_name == "create_agent_graph":
|
elif kind == "on_chain_end":
|
||||||
# 最终输出通常在这里
|
|
||||||
output = data.get("output")
|
output = data.get("output")
|
||||||
if isinstance(output, dict) and "final_response" in output:
|
final_resp = None
|
||||||
final_resp = output["final_response"]
|
if isinstance(output, dict):
|
||||||
# 如果还没流式输出完整,补全它
|
state.update(output)
|
||||||
if final_resp and not collected:
|
final_resp = output.get("final_response")
|
||||||
collected = final_resp
|
if final_resp:
|
||||||
yield {"type": "chunk", "content": collected}
|
final_text = str(final_resp)
|
||||||
|
if final_text != collected:
|
||||||
|
collected = final_text
|
||||||
|
yield {"type": "chunk", "content": final_text}
|
||||||
|
|
||||||
|
elif kind == "on_chat_model_end":
|
||||||
|
output = data.get("output")
|
||||||
|
final_content = _coerce_event_text(getattr(output, "content", "") if output else "")
|
||||||
|
if final_content:
|
||||||
|
final_text = final_content
|
||||||
|
if final_text != collected:
|
||||||
|
collected = final_text
|
||||||
|
yield {"type": "chunk", "content": final_text}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if _is_streaming_rejection_error(e, user_llm_config) and not collected:
|
if _is_streaming_rejection_error(e, user_llm_config) and not collected:
|
||||||
yield self._build_progress_event("responding", "Jarvis 正在生成回复", agent="master", step="fallback")
|
yield self._build_progress_event("responding", "Jarvis 正在生成回复", agent="master", step="fallback")
|
||||||
try:
|
try:
|
||||||
result_state = await graph.ainvoke(state)
|
result_state = await graph.ainvoke(state)
|
||||||
|
if isinstance(result_state, dict):
|
||||||
|
state.update(result_state)
|
||||||
fallback_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
fallback_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
||||||
collected = str(fallback_content)
|
collected = str(fallback_content)
|
||||||
yield {"type": "chunk", "content": collected}
|
yield {"type": "chunk", "content": collected}
|
||||||
except Exception as fallback_error:
|
except Exception:
|
||||||
logger.exception("llm_sync_fallback_failed")
|
logger.exception("llm_sync_fallback_failed")
|
||||||
yield {"type": "error", "error": "模型服务暂不可用。"}
|
safe_error = "模型服务暂不可用,请稍后再试。"
|
||||||
|
yield {"type": "error", "error": safe_error}
|
||||||
|
collected = f"抱歉,发生错误: {safe_error}"
|
||||||
|
yield {"type": "chunk", "content": collected}
|
||||||
else:
|
else:
|
||||||
logger.exception("agent_streaming_failed")
|
logger.exception("agent_streaming_failed")
|
||||||
yield {"type": "error", "error": str(e)}
|
if not collected:
|
||||||
|
safe_error = "模型服务暂不可用,请稍后再试。"
|
||||||
|
yield {"type": "error", "error": safe_error}
|
||||||
|
collected = f"抱歉,发生错误: {safe_error}"
|
||||||
|
yield {"type": "chunk", "content": collected}
|
||||||
|
else:
|
||||||
|
yield {"type": "error", "error": str(e)}
|
||||||
finally:
|
finally:
|
||||||
clear_current_user()
|
clear_current_user()
|
||||||
asyncio.create_task(self._try_auto_summarize_background(user_id, conversation_id))
|
|
||||||
|
|
||||||
if collected:
|
|
||||||
try:
|
try:
|
||||||
async with async_session() as session:
|
if collected:
|
||||||
result2 = await session.execute(select(Message).where(Message.id == assistant_msg.id))
|
assistant_msg.content = collected
|
||||||
msg = result2.scalar_one_or_none()
|
continuity_snapshot = _build_continuity_snapshot(state or {})
|
||||||
if msg:
|
assistant_msg.attachments = ([{
|
||||||
msg.content = collected
|
"kind": "agent_continuity_state",
|
||||||
await session.commit()
|
**continuity_snapshot,
|
||||||
|
}] if continuity_snapshot else None)
|
||||||
|
conv.agent_state = continuity_snapshot
|
||||||
|
await BrainService(self.db).create_event(
|
||||||
|
user_id,
|
||||||
|
**_build_assistant_event_payload(collected),
|
||||||
|
)
|
||||||
|
await self.db.commit()
|
||||||
|
await self.db.refresh(assistant_msg)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("save_assistant_message_failed")
|
logger.exception("save_assistant_message_failed")
|
||||||
|
asyncio.create_task(self._try_auto_summarize_background(user_id, conversation_id))
|
||||||
|
|
||||||
return conversation_id, assistant_msg.id, run_agent()
|
return conversation_id, assistant_msg.id, run_agent()
|
||||||
|
|
||||||
@@ -331,32 +490,74 @@ class AgentService:
|
|||||||
"""
|
"""
|
||||||
user_llm_config = await self._get_user_llm_config(user_id, model_name)
|
user_llm_config = await self._get_user_llm_config(user_id, model_name)
|
||||||
model_name_used = model_name
|
model_name_used = model_name
|
||||||
|
if model_name and not user_llm_config:
|
||||||
|
raise ValueError("所选模型不可用于聊天,请切换到聊天模型")
|
||||||
if user_llm_config:
|
if user_llm_config:
|
||||||
model_name_used = user_llm_config.get("name", model_name)
|
model_name_used = user_llm_config.get("name", model_name)
|
||||||
|
|
||||||
if not conversation_id:
|
if conversation_id:
|
||||||
|
result = await self.db.execute(
|
||||||
|
select(Conversation).where(
|
||||||
|
Conversation.id == conversation_id,
|
||||||
|
Conversation.user_id == user_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conv = result.scalar_one_or_none()
|
||||||
|
if conv is None:
|
||||||
|
raise ValueError("会话不存在或无权访问")
|
||||||
|
else:
|
||||||
|
conv = None
|
||||||
|
|
||||||
|
if not conv:
|
||||||
conv = Conversation(user_id=user_id, title=message[:50])
|
conv = Conversation(user_id=user_id, title=message[:50])
|
||||||
self.db.add(conv)
|
self.db.add(conv)
|
||||||
await self.db.commit()
|
await self.db.commit()
|
||||||
await self.db.refresh(conv)
|
await self.db.refresh(conv)
|
||||||
conversation_id = conv.id
|
conversation_id = conv.id
|
||||||
|
else:
|
||||||
|
conversation_id = conv.id
|
||||||
|
|
||||||
user_msg = Message(conversation_id=conversation_id, role="user", content=message)
|
user_msg = Message(conversation_id=conversation_id, role="user", content=message)
|
||||||
self.db.add(user_msg)
|
self.db.add(user_msg)
|
||||||
|
|
||||||
|
assistant_msg = Message(
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
role="assistant",
|
||||||
|
content="",
|
||||||
|
model=model_name_used or "jarvis",
|
||||||
|
attachments=None,
|
||||||
|
)
|
||||||
|
self.db.add(assistant_msg)
|
||||||
|
|
||||||
|
brain_service = BrainService(self.db)
|
||||||
|
await brain_service.create_event(
|
||||||
|
user_id,
|
||||||
|
source_type="conversation",
|
||||||
|
source_id=conversation_id,
|
||||||
|
event_type="message_created",
|
||||||
|
title="User message",
|
||||||
|
content_summary=message[:500],
|
||||||
|
raw_excerpt=message[:2000],
|
||||||
|
metadata_={"role": "user"},
|
||||||
|
importance_signal=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
memory_ctx = await memory_service.build_memory_context(self.db, user_id, conversation_id, message)
|
memory_ctx = await memory_service.build_memory_context(self.db, user_id, conversation_id, message)
|
||||||
|
|
||||||
set_current_user(user_id)
|
set_current_user(user_id)
|
||||||
try:
|
try:
|
||||||
graph = get_agent_graph()
|
graph = get_agent_graph()
|
||||||
state = initial_state(user_id, conversation_id)
|
current_datetime_context, current_datetime_reference = self._build_current_datetime_context()
|
||||||
state.update({
|
state = await self._build_agent_state(
|
||||||
"messages": [HumanMessage(content=message)],
|
user_id=user_id,
|
||||||
"memory_context": memory_ctx,
|
conversation=conv,
|
||||||
"current_datetime_context": datetime.now(UTC).isoformat(),
|
full_message=message,
|
||||||
"user_llm_config": user_llm_config,
|
memory_context=memory_ctx,
|
||||||
})
|
current_datetime_context=current_datetime_context,
|
||||||
|
current_datetime_reference=current_datetime_reference,
|
||||||
|
user_llm_config=user_llm_config,
|
||||||
|
)
|
||||||
|
|
||||||
result_state = await graph.ainvoke(state)
|
result_state = await graph.ainvoke(state)
|
||||||
response_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
response_content = result_state.get("final_response") or str(result_state.get("messages", [AIMessage(content="")])[-1].content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -365,13 +566,27 @@ class AgentService:
|
|||||||
finally:
|
finally:
|
||||||
clear_current_user()
|
clear_current_user()
|
||||||
|
|
||||||
assistant_msg = Message(
|
brain_service = BrainService(self.db)
|
||||||
conversation_id=conversation_id,
|
await brain_service.create_event(
|
||||||
role="assistant",
|
user_id,
|
||||||
content=response_content,
|
source_type="conversation",
|
||||||
model=model_name_used or "jarvis",
|
source_id=conversation_id,
|
||||||
|
event_type="message_created",
|
||||||
|
title="Assistant message",
|
||||||
|
content_summary=response_content[:500],
|
||||||
|
raw_excerpt=response_content[:2000],
|
||||||
|
metadata_={"role": "assistant"},
|
||||||
|
importance_signal=0.8,
|
||||||
)
|
)
|
||||||
self.db.add(assistant_msg)
|
|
||||||
|
assistant_msg.content = response_content
|
||||||
|
continuity_snapshot = _build_continuity_snapshot(result_state) if 'result_state' in locals() else None
|
||||||
|
assistant_msg.attachments = ([{
|
||||||
|
"kind": "agent_continuity_state",
|
||||||
|
**continuity_snapshot,
|
||||||
|
}] if continuity_snapshot else None)
|
||||||
|
conv.agent_state = continuity_snapshot
|
||||||
await self.db.commit()
|
await self.db.commit()
|
||||||
|
await self.db.refresh(assistant_msg)
|
||||||
|
|
||||||
return conversation_id, assistant_msg.id, response_content, model_name_used
|
return conversation_id, assistant_msg.id, response_content, model_name_used
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user