- Config.llm_profiles: LLM_PROVIDERS declares names, each group defined by
LLM_<NAME>_BASE_URL/_API_KEY/_MODEL (required) and optional _TIMEOUT_SECONDS/
_MAX_RETRIES/_TEMPERATURE/_EXTRA_BODY; JSON-dict form of LLM_PROVIDERS also
supported. LLM_PROVIDER selects the active group at runtime; empty/fake =
offline scripted model
- removed fixed openai/kimi groups (AGENT_LLM_PROVIDER, KIMI_LLM_*)
- validate(): incomplete groups and unknown LLM_PROVIDER fail fast with
the configured-name list
- OpenAILLMService: http client trust_env from LLM_HTTP_TRUST_ENV (default
False - local gateways must bypass the Windows system proxy)
- build_llm_service(provider): explicit name or LLM_PROVIDER default
- .env/.env.example: deepseek + kimi groups side by side, switch by
LLM_PROVIDER=kimi (thinking off via EXTRA_BODY reasoning_effort=none)
- eval probe scripts updated to the new config interface
Verified: 172 unit tests; live calls succeed on both providers
(deepseek 28s, kimi 6.6s, both emitting valid <state> prefixes)
- merge main (b0ff2e0..c7ca781): workflow/20261022 moves dynamic context
out of fixed prompts - collection/extraction prompts carry no inline vars;
reference time and current form travel in the user message <turn_context>
block; photo/confirm data kept at prompt tails
- prompts: all 8 files regenerated from 20261022 (byte-identity tests
point at the new export)
- collect: user message = <turn_context>(本轮参考时间) + <user_input>;
extraction message adds 当前表单; single trusted clock per turn threads
through reply/extraction/repair
- photo: raw input only (20261022 photo nodes use system_text, no wrapper)
- workflow fake llm: unwraps <user_input> before event parsing
- fields: accident_time normalized to extraction format (YYYY年M月D日H点m分,
strip zero padding) - fixes form assertion mismatch with padded output
- evals/run.py: trust_env=False - bypass Windows system proxy (registry
proxy at 192.168.0.4:7897 was intercepting localhost and returning 503)
- verified: 172 unit tests; ALL golden suites pass on real model
(46/46): collection_skip 12/12, photo_skip 4/4, no_false 2/2,
button_guidance 12/12, non_motor_vehicle 14/14, time_confirmation 2/2
- 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.