feat(agent): LLM-powered collection node with welcome stage and session-scoped delete (assignment 4)
- prompts/collection.md: versioned collection system prompt extracted from workflow node yQaBxfrWjbRr with source/version/porting notes - llm.py: LLMService protocol; FakeLLMService (scripted, records calls); OpenAILLMService (async, wait_for timeout with cancellation, retry only on timeout/429/connection, errors normalized) - collect.py: run_collect reference impl - one LLM call produces state prefix + body, controlled repair (max 1 retry) for missing prefix or illegal stage proposal, whitelist guard, CollectReplyError without raw model content; dialogue history + confirmed fields injected into prompt so the model skips known info (作业4-5) - fields.py / clock.py: three-state field candidates (unknown/ candidate/confirmed), per-type validation (count not inferred from description, future time rejected), injectable Clock - graph.py: welcome node - first message returns fixed 1001 greeting without LLM (mirrors workflow is_first_message -> Assigned Reply#3), route empty stage to welcome - endpoints/delete_session: backend switch - langgraph clears graph checkpoint, fastgpt path unchanged - config: AGENT_LLM_PROVIDER fake|openai with validation - untrack .env (secrets), move template to root .env.example Verified: - pytest 103 passed; smoke turns 1/2/1 - Real LLM e2e: POST /chat multi-turn context works - Golden eval collection_skip_known_info: 12/12 cases pass - Golden eval no_false: 8/10 cases pass; 4 confirm/photo cases fail at final step - model routes on short reply '对' (prompt-adherence gap, photo/confirm stages are assignment 5 scope); recorded in evals/results/20261020-144856 Known limits: field extraction from model reply not wired (stub); photo/confirm nodes still scaffold; clarify counting is approximate.
This commit is contained in:
@@ -46,11 +46,24 @@ async def test_different_threads_are_isolated() -> None:
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reply_uses_fixed_template_with_stage_prefix() -> None:
|
||||
async def test_first_turn_is_welcome_stage() -> None:
|
||||
# 新会话首条消息:welcome 节点固定问候,返回 1001(对照工作流
|
||||
# work_start is_first_message → Assigned Reply#3),不调用模型
|
||||
graph = build_graph()
|
||||
|
||||
result = await ask(graph, "thread-a", "你好")
|
||||
|
||||
assert result["stage_code"] == "1002"
|
||||
assert result["response_text"].startswith("<state>1002</state>")
|
||||
assert "你好" in result["response_text"]
|
||||
assert result["stage_code"] == "1001"
|
||||
assert result["response_text"].startswith("<state>1001</state>")
|
||||
assert result["turn_count"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_second_turn_keeps_stage_without_event() -> None:
|
||||
# 问候后的普通输入:脚手架版无事件则保持 1001,
|
||||
# 【继续办理】等事件经白名单进入 1002(见 test_transitions.py)
|
||||
graph = build_graph()
|
||||
await ask(graph, "thread-a", "你好")
|
||||
result = await ask(graph, "thread-a", "我出车祸了")
|
||||
|
||||
assert result["stage_code"] == "1001"
|
||||
assert "我出车祸了" in result["response_text"]
|
||||
|
||||
Reference in New Issue
Block a user