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:
@@ -18,6 +18,15 @@ class Config:
|
||||
# Chat backend switch(作业 2)
|
||||
# "fastgpt" 保持现有行为;"langgraph" 使用内存图 fake 回复。
|
||||
AGENT_BACKEND = os.getenv("AGENT_BACKEND", "fastgpt")
|
||||
|
||||
# LLM provider(作业 4):fake 为脚本化假模型,离线可运行;
|
||||
# openai 为真实 provider(OpenAI 兼容接口)。
|
||||
AGENT_LLM_PROVIDER = os.getenv("AGENT_LLM_PROVIDER", "fake")
|
||||
LLM_MODEL = os.getenv("LLM_MODEL", "fake-reply-model")
|
||||
LLM_BASE_URL = os.getenv("LLM_BASE_URL", "")
|
||||
LLM_API_KEY = os.getenv("LLM_API_KEY", "")
|
||||
LLM_TIMEOUT_SECONDS = float(os.getenv("LLM_TIMEOUT_SECONDS", "30"))
|
||||
LLM_MAX_RETRIES = int(os.getenv("LLM_MAX_RETRIES", "2"))
|
||||
|
||||
@classmethod
|
||||
def validate(cls):
|
||||
@@ -28,6 +37,17 @@ class Config:
|
||||
raise ValueError(
|
||||
f"AGENT_BACKEND must be 'fastgpt' or 'langgraph', got {cls.AGENT_BACKEND!r}"
|
||||
)
|
||||
if cls.AGENT_LLM_PROVIDER not in ("fake", "openai"):
|
||||
raise ValueError(
|
||||
"AGENT_LLM_PROVIDER must be 'fake' or 'openai', "
|
||||
f"got {cls.AGENT_LLM_PROVIDER!r}"
|
||||
)
|
||||
if cls.AGENT_LLM_PROVIDER == "openai" and (
|
||||
not cls.LLM_API_KEY or not cls.LLM_BASE_URL
|
||||
):
|
||||
raise ValueError(
|
||||
"AGENT_LLM_PROVIDER=openai requires LLM_API_KEY and LLM_BASE_URL"
|
||||
)
|
||||
if cls.AGENT_BACKEND != "fastgpt":
|
||||
return
|
||||
required = [
|
||||
|
||||
Reference in New Issue
Block a user