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:
@@ -11,6 +11,7 @@ from .config import Config
|
||||
|
||||
_fastgpt_client: AsyncChatClient | None = None
|
||||
_chat_backend: ChatBackend | None = None
|
||||
_langgraph_graph = None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -22,7 +23,11 @@ async def lifespan(app: FastAPI):
|
||||
if Config.AGENT_BACKEND == "langgraph":
|
||||
# 作业 2:LangGraph fake 模式不要求 FastGPT 密钥。
|
||||
# TODO(作业 2): 若语音仍启用,需在此单独初始化并校验 FastGPT 配置。
|
||||
_chat_backend = LangGraphBackend(build_graph())
|
||||
from ..agent.llm import build_llm_service
|
||||
|
||||
global _langgraph_graph
|
||||
_langgraph_graph = build_graph(llm=build_llm_service())
|
||||
_chat_backend = LangGraphBackend(_langgraph_graph)
|
||||
else:
|
||||
_fastgpt_client = AsyncChatClient(
|
||||
api_key=Config.FASTGPT_API_KEY,
|
||||
@@ -39,11 +44,23 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
finally:
|
||||
_chat_backend = None
|
||||
_langgraph_graph = None
|
||||
if _fastgpt_client:
|
||||
await _fastgpt_client.__aexit__(None, None, None)
|
||||
_fastgpt_client = None
|
||||
|
||||
|
||||
async def delete_chat_session(thread_id: str) -> None:
|
||||
"""langgraph 模式下删除会话:清理图 checkpoint(对照计划作业 6 的
|
||||
完整语义——业务字段、事件、路由持久化清理在接入 repository 后补齐)。
|
||||
"""
|
||||
if _langgraph_graph is None:
|
||||
raise RuntimeError("LangGraph backend not initialized")
|
||||
checkpointer = getattr(_langgraph_graph, "checkpointer", None)
|
||||
if checkpointer is not None and hasattr(checkpointer, "delete_thread"):
|
||||
checkpointer.delete_thread(thread_id)
|
||||
|
||||
|
||||
def get_fastgpt_client() -> AsyncChatClient:
|
||||
"""Get the initialized FastGPT client."""
|
||||
if _fastgpt_client is None:
|
||||
|
||||
Reference in New Issue
Block a user