feat(server): 会话上下文保留(LLM 历史 + 确定性兜底双保险)
解决用户删除草稿后说'再提交'丢失上下文的问题: - steward.py 新增 _inject_recent_conversation_history:build_plan 前读最近 10 条对话注入 context_json - steward_intent_agent.py 的 _build_messages 把 recent_history 暴露给模型,system prompt 加确认类话术引导 - 新建 steward_context_resume.py:should_resume_recent_task 检测'再提交'类话术 + state 有可恢复 flow,attach_resumed_task 从 state 恢复 task - 两个 plan 入口(/plans 和 /plans/stream)都已接入双保险 - 后端 67 passed,端到端验证'上海出差→再提交'成功恢复 task
This commit is contained in:
@@ -29,6 +29,10 @@ from app.services.agent_conversations import AgentConversationService
|
||||
from app.services.expense_claim_draft_flow import APPROVED_APPLICATION_LINK_STATUSES
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.runtime_chat import RuntimeChatService
|
||||
from app.services.steward_context_resume import (
|
||||
attach_resumed_task,
|
||||
should_resume_recent_task,
|
||||
)
|
||||
from app.services.steward_flow_state import StewardFlowStateService
|
||||
from app.services.steward_graph_action_runtime import StewardGraphActionRuntime
|
||||
from app.services.steward_graph_planner import StewardGraphPlannerService
|
||||
@@ -61,7 +65,9 @@ def create_steward_plan(payload: StewardPlanRequest, db: DbSession) -> StewardPl
|
||||
try:
|
||||
planner = _build_steward_planner(db)
|
||||
hydrated_payload = _hydrate_required_application_gate(db, payload, planner)
|
||||
hydrated_payload = _inject_recent_conversation_history(db, hydrated_payload)
|
||||
plan = planner.build_plan(hydrated_payload)
|
||||
plan = _apply_context_resume(db, hydrated_payload, plan)
|
||||
return _attach_conversation_state(db, hydrated_payload, plan)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
|
||||
@@ -143,7 +149,9 @@ async def _iter_steward_plan_events(
|
||||
|
||||
try:
|
||||
hydrated_payload = _hydrate_required_application_gate(db, payload, planner)
|
||||
hydrated_payload = _inject_recent_conversation_history(db, hydrated_payload)
|
||||
plan = planner.build_plan(hydrated_payload)
|
||||
plan = _apply_context_resume(db, hydrated_payload, plan)
|
||||
plan = _attach_conversation_state(db, hydrated_payload, plan)
|
||||
except ValueError as exc:
|
||||
yield _encode_stream_event("error", {"message": str(exc)})
|
||||
@@ -495,3 +503,66 @@ def _resolve_current_steward_state(
|
||||
return stored_state
|
||||
incoming_state = context_json.get("steward_state") or context_json.get("stewardState")
|
||||
return incoming_state if isinstance(incoming_state, dict) else {}
|
||||
|
||||
|
||||
def _inject_recent_conversation_history(
|
||||
db: Session,
|
||||
payload: StewardPlanRequest,
|
||||
) -> StewardPlanRequest:
|
||||
"""读取会话最近 10 条对话历史,注入 context_json.recent_history 供 LLM 关联上下文。
|
||||
|
||||
历史只给模型用,不返回前端。在 get_or_create_conversation 之前读取,
|
||||
使用前端传入的 conversation_id,避免把本轮消息算进历史。
|
||||
"""
|
||||
context_json = dict(payload.context_json or {})
|
||||
conversation_id = _resolve_conversation_id(context_json)
|
||||
if not conversation_id:
|
||||
return payload
|
||||
try:
|
||||
recent_history = AgentConversationService(db).list_message_history(
|
||||
conversation_id,
|
||||
limit=10,
|
||||
)
|
||||
except Exception:
|
||||
recent_history = []
|
||||
if not recent_history:
|
||||
return payload
|
||||
return payload.model_copy(
|
||||
update={
|
||||
"context_json": {
|
||||
**context_json,
|
||||
"recent_history": recent_history,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _apply_context_resume(
|
||||
db: Session,
|
||||
payload: StewardPlanRequest,
|
||||
plan: StewardPlanResponse,
|
||||
) -> StewardPlanResponse:
|
||||
"""确定性兜底:若 plan 无 task 且用户说"再提交"类话术,从会话状态恢复最近 task。
|
||||
|
||||
不依赖 LLM 理解力,100% 可靠地恢复上下文。LLM 注入历史(保险②)覆盖更模糊话术。
|
||||
"""
|
||||
if plan.tasks or plan.candidate_flows:
|
||||
return plan
|
||||
context_json = dict(payload.context_json or {})
|
||||
conversation_id = _resolve_conversation_id(context_json)
|
||||
if not conversation_id:
|
||||
return plan
|
||||
try:
|
||||
conversation = AgentConversationService(db).get_conversation(conversation_id)
|
||||
except Exception:
|
||||
conversation = None
|
||||
if conversation is None:
|
||||
return plan
|
||||
current_state = _resolve_current_steward_state(
|
||||
conversation.state_json if isinstance(conversation.state_json, dict) else {},
|
||||
context_json,
|
||||
)
|
||||
resume_flow_id = should_resume_recent_task(payload.message, current_state)
|
||||
if not resume_flow_id:
|
||||
return plan
|
||||
return attach_resumed_task(plan, current_state, resume_flow_id)
|
||||
|
||||
Reference in New Issue
Block a user