feat(api): 收敛对话与记忆流程边界,引入 LLM 网关与专用服务

- MemoryService 异步路径委托 MemoryIngestService / MemoryRetrievalService;富化派发经 MemoryEnrichmentScheduler
- WebSocket pipeline 经 ChatTurnService 与显式 DTO 编排单轮对话;回忆录片段入队由 MemoirIngestScheduler 封装
- 新增 LlmGateway(LlmUseCase),各 agent、任务与适配器对齐 ports
- 补充 memory 提示适配、runtime 类型、memory-retrieval 文档、ai-touchpoints 说明与扫描脚本及配套测试

Made-with: Cursor
This commit is contained in:
Kevin
2026-04-30 09:17:01 +08:00
parent eddb2c3078
commit ac436b87a2
37 changed files with 1400 additions and 199 deletions

View File

@@ -0,0 +1,79 @@
# AI touchpoints (generated)
Regenerate: `uv run python api/scripts/ai_touchpoints_scan.py --markdown api/docs/ai-touchpoints.md`
| File | Tags |
|------|------|
| `api/app/adapters/asr/tencent_asr.py` | `ports_ai` |
| `api/app/adapters/asr/whisper_local.py` | `ports_ai` |
| `api/app/adapters/embedding/zhipu.py` | `embedding` |
| `api/app/adapters/llm/deepseek.py` | `json_llm_helpers`, `langchain`, `ports_ai` |
| `api/app/adapters/llm/deepseek_eval_judge.py` | `langchain` |
| `api/app/adapters/llm/zhipu_eval_judge.py` | `langchain` |
| `api/app/agents/__init__.py` | `agents_layer` |
| `api/app/agents/chat/__init__.py` | `agents_layer` |
| `api/app/agents/chat/helpers.py` | `langchain` |
| `api/app/agents/chat/interview_agent.py` | `agents_layer`, `langchain`, `llm_provider` |
| `api/app/agents/chat/interview_state_hints.py` | `agents_layer`, `langchain` |
| `api/app/agents/chat/interview_turn_plan.py` | `agents_layer` |
| `api/app/agents/chat/occupation_context.py` | `agents_layer` |
| `api/app/agents/chat/orchestrator.py` | `agents_layer`, `embedding`, `langchain`, `llm_provider`, `memory_ai` |
| `api/app/agents/chat/profile_agent.py` | `agents_layer`, `json_llm_helpers`, `langchain`, `llm_call_module`, `llm_provider` |
| `api/app/agents/chat/prompt_context.py` | `agents_layer` |
| `api/app/agents/chat/prompt_layers.py` | `agents_layer` |
| `api/app/agents/chat/prompts.py` | `agents_layer` |
| `api/app/agents/chat/prompts_conversation.py` | `agents_layer` |
| `api/app/agents/chat/prompts_profile.py` | `agents_layer` |
| `api/app/agents/chat/reply_planner.py` | `agents_layer`, `json_llm_helpers`, `langchain` |
| `api/app/agents/chat/slot_question_bank.py` | `agents_layer` |
| `api/app/agents/chat/stage_detection.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/chat/stage_prompts.py` | `agents_layer` |
| `api/app/agents/image_prompt/__init__.py` | `agents_layer` |
| `api/app/agents/image_prompt/orchestrator.py` | `agents_layer`, `langchain`, `llm_provider` |
| `api/app/agents/memoir/__init__.py` | `agents_layer` |
| `api/app/agents/memoir/batch_phase1_prep.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/memoir/classification_agent.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/memoir/extraction_agent.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/memoir/fidelity_check_agent.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/memoir/narrative_agent.py` | `agents_layer`, `json_llm_helpers`, `langchain`, `llm_call_module` |
| `api/app/agents/memoir/orchestrator.py` | `agents_layer` |
| `api/app/agents/memoir/prompts.py` | `agents_layer`, `json_llm_helpers` |
| `api/app/agents/memoir/story_route_agent.py` | `agents_layer`, `json_llm_helpers`, `llm_call_module` |
| `api/app/agents/state_schema.py` | `agents_layer` |
| `api/app/core/config.py` | `json_llm_helpers`, `memory_ai` |
| `api/app/core/dependencies.py` | `embedding`, `llm_provider`, `ports_ai` |
| `api/app/core/langchain_llm.py` | `json_llm_helpers`, `langchain`, `llm_provider` |
| `api/app/core/llm_call.py` | `json_llm_helpers`, `langchain` |
| `api/app/core/text_normalize.py` | `json_llm_helpers`, `langchain` |
| `api/app/features/conversation/ws/pipeline.py` | `agents_layer`, `ports_ai` |
| `api/app/features/conversation/ws/profile_collector.py` | `agents_layer` |
| `api/app/features/conversation/ws/router.py` | `agents_layer` |
| `api/app/features/evaluation/judge_service.py` | `json_llm_helpers`, `llm_call_module` |
| `api/app/features/memoir/_interview_meta_store.py` | `agents_layer` |
| `api/app/features/memoir/deps.py` | `memory_ai` |
| `api/app/features/memoir/memoir_images/prompting.py` | `agents_layer`, `json_llm_helpers`, `langchain` |
| `api/app/features/memoir/service.py` | `memory_ai` |
| `api/app/features/memoir/state_service.py` | `agents_layer` |
| `api/app/features/memoir/story_pipeline_sync.py` | `agents_layer`, `embedding` |
| `api/app/features/memory/curation.py` | `memory_ai` |
| `api/app/features/memory/deps.py` | `embedding`, `memory_ai` |
| `api/app/features/memory/enrichment.py` | `json_llm_helpers`, `langchain`, `llm_provider`, `memory_ai` |
| `api/app/features/memory/evidence.py` | `embedding`, `memory_ai`, `ports_ai` |
| `api/app/features/memory/evidence_format.py` | `memory_ai` |
| `api/app/features/memory/extractor.py` | `json_llm_helpers`, `langchain`, `llm_provider` |
| `api/app/features/memory/llm_schemas.py` | `json_llm_helpers` |
| `api/app/features/memory/repo.py` | `embedding`, `memory_ai`, `ports_ai` |
| `api/app/features/memory/retriever.py` | `embedding`, `memory_ai`, `ports_ai` |
| `api/app/features/memory/router.py` | `memory_ai` |
| `api/app/features/memory/schemas.py` | `memory_ai` |
| `api/app/features/memory/service.py` | `embedding`, `memory_ai`, `ports_ai` |
| `api/app/features/memory/summarizer.py` | `json_llm_helpers`, `langchain` |
| `api/app/features/memory/timeline.py` | `json_llm_helpers`, `langchain`, `llm_provider` |
| `api/app/ports/embedding.py` | `embedding` |
| `api/app/ports/llm.py` | `ports_ai` |
| `api/app/tasks/chapter_cover_tasks.py` | `agents_layer` |
| `api/app/tasks/memoir_quality_pass_tasks.py` | `agents_layer`, `langchain`, `llm_provider` |
| `api/app/tasks/memoir_tasks.py` | `agents_layer`, `langchain`, `llm_provider` |
| `api/app/tasks/memory_enrichment_tasks.py` | `memory_ai` |
| `api/app/tasks/story_image_tasks.py` | `agents_layer` |
| `api/app/tasks/story_title_tasks.py` | `agents_layer`, `langchain`, `llm_provider` |

View File

@@ -14,6 +14,20 @@
- 未配置 `ZHIPU_API_KEY`(或 provider `_client` 为空chunk 检索为空列表,仍会返回 facts/timeline/summaries/stories按 query ILIKE
- 日志:`HybridRetriever` / `retrieve_evidence_bundle_sync` 在无 provider 或空向量时会打 warning。
## 行为矩阵async / sync 契约)
以下行为应对齐;变更 `evidence.py` 时须同时检视 `HybridRetriever` + `retrieve_evidence_bundle_sync`,并跑 `tests/test_memory_evidence.py` 中的双路径用例。
| 条件 | 同步 `retrieve_evidence_bundle_sync` | 异步 `retrieve_evidence_bundle_async` |
|------|--------------------------------------|---------------------------------------|
| query 空白 | `memory_evidence_empty_query_include_rolling=false` → 与 `EMPTY_EVIDENCE_BUNDLE` 同键、全空列表 | 同上 |
| query 空白 | `memory_evidence_empty_query_include_rolling=true` → 无 chunksrolling 摘要(若有)+ 最近 facts / timeline`relevant_stories` 为空 | 同上(`_empty_query_bundle_*` 对称实现) |
| query 非空 | 本函数内 `embedding_provider.embed_text_sync``search_chunks_vector_sync`;再并行拉取元数据 | chunks 由调用方预计算(`HybridRetriever``search_chunks_vector`);本函数只 `fetch_evidence_metadata_async` 合并 |
| 无 embedding | warningchunks 为空;元数据仍按 ILIKE 等返回 | async 路径若上游无向量则 `merged_chunk_dicts=[]`;元数据仍返回 |
| 输出形状 | `{"relevant_chunks", "relevant_summaries", "relevant_facts", "timeline_hints", "relevant_stories"}` chunk 项为 `id, content, chunk_index`(不含 distance | 非空 query 下 `relevant_chunks` 等于传入的 `merged_chunk_dicts`(已由检索层剥掉 distance |
Facts 状态过滤(如 `confirmed` / 排除 `stale`)与 ILIKE fallback 由 `repo` 查询实现;两条路径共用同一套 sync/async repo 函数族,语义以 `evidence.py` 调用为准。
## 空 query
- 默认:`relevant_*` 均为空(与历史行为一致)。