- time_context.py: server-side observation clocks for relative-time
evidence (anti client-forgery); resolves X分钟前/小时前 against the
observation timestamp of the turn that produced them, never re-basing
history on the current clock; flags historical references without a
reliable baseline
- state: dialogue_timestamps keyed by dialogue message index, persisted
with checkpoint; welcome seeds index 0
- collect: single clock per turn shared by reply/extraction/repair;
time_evidence injected into runtime context; reply_validator hook
rejects time claims lacking evidence (one shared repair budget);
model still interprets facts and chooses actions
- prompts: collection prompt restructured (shorter, state-bound-to-action
table, gatekeeper principle, time source rules); extraction prompt
aligned; workflow@20261021 json updated to match
- evals: prompt_probes / prompt_compare / time_boundary probe scripts
(supplementary checks, original cases untouched)
- docs: collection-prompt-optimization.md (method + baselines),
time-boundary-fix.md (problem, design, verification)
- gitignore: editor artifact dirs (.agents/.claude/.vscode, skills-lock)
Verified: 171 unit tests pass (16 new time-context tests)
- merge main (fb9271e): collection prompt restructured (state-bound-to-action,
gatekeeper principle); fill_missing eval cases relaxed to semantic regexes
- prompts/collection.md regenerated; byte-identity test updated and passing
- rerun golden evals: collection_skip 11/12, time_confirmation 1/2
(inter-run variance; only single_no_inferred_count fails consistently -
model routes without asking vehicle count after time confirmation)
- 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.