From 4268d791edf961700d6a0f4e882134dda87e31ee Mon Sep 17 00:00:00 2001 From: Yuuuu <1569752928@qq.com> Date: Thu, 26 Mar 2026 09:40:36 +0800 Subject: [PATCH] Implement structured AI task breakdown and auto scheduling --- .env.example | 26 +- .tmp-feishu-escape.png | Bin 0 -> 259567 bytes .tmp-feishu-menu.png | Bin 0 -> 232793 bytes .tmp-feishu-newtab.png | Bin 0 -> 86894 bytes .tmp-feishu-replymode.png | Bin 0 -> 226740 bytes CLAUDE.md | 65 +- backend/.openclaw/pmflow-routing-rules.json | 75 +- ..._add_project_create_idempotency_indexes.py | 83 + ...add_feishu_reply_outbox_sent_message_id.py | 40 + backend/app/api/llm_providers.py | 36 +- .../app/api/projects_api/routes_mutation.py | 2 +- .../app/api/sstc_api/tasks_ai_breakdown.py | 38 +- backend/app/api/tasks.py | 35 +- backend/app/config/ai_prompt_registry.py | 472 +++++ backend/app/config/ai_prompts.py | 85 +- .../app/config/ai_prompts_catalog/__init__.py | 6 +- .../app/config/ai_prompts_catalog/part_02.py | 110 +- .../app/config/ai_prompts_catalog/part_03.py | 37 +- .../app/config/ai_prompts_catalog/part_04.py | 11 +- .../ai_prompts_catalog/part_scenes_01.py | 38 +- .../ai_prompts_catalog/part_scenes_02.py | 33 +- .../ai_prompts_catalog/part_scenes_03.py | 19 +- .../ai_prompts_catalog/part_scenes_04.py | 7 +- .../ai_prompts_catalog/part_scenes_06.py | 19 +- backend/app/core/startup/model_probe.py | 54 +- backend/app/models/feishu_reply_outbox.py | 1 + backend/app/schemas/common.py | 2 + backend/app/schemas/world_model.py | 11 + .../mixin_answer_retrieval.py | 2 + .../mixin_deep_guard_answer.py | 267 ++- .../mixin_deterministic_query_core.py | 7 +- .../mixin_fast_answer_project_main.py | 116 +- .../adaptive_rag_service/shared_imports.py | 2 + .../adaptive_rag_service/shared_part_01.py | 1 + .../adaptive_rag_service/shared_part_02.py | 2 +- .../ai_orchestration_run/ingress_worker.py | 9 + .../services/ai_orchestration_run/service.py | 4 + .../services/ai_pmo/ai_pmo_decision/policy.py | 59 +- .../services/ai_pmo/ai_pmo_decision/shared.py | 84 + .../ai_pmo/ai_pmo_decision/simulation.py | 38 + .../ai_pmo/ai_pmo_decision/strategic.py | 42 + .../ai_pmo/ai_pmo_decision/tactical.py | 76 + .../ai_pmo/ai_pmo_intent/route_resolver.py | 2 + .../services/ai_pmo/ai_pmo_intent/shared.py | 155 +- .../ai_pmo/ai_pmo_kernel/ai_agent_kernel.py | 40 +- .../ai_pmo_kernel/ai_pmo_reply_renderer.py | 348 +++ .../ai_pmo_typed_executor_common.py | 54 + .../ai_pmo_kernel/ai_pmo_workflow_runtime.py | 33 + .../ai_pmo_kernel_mixins_closure_payload.py | 6 +- ...pmo_kernel_mixins_followup_verification.py | 75 +- .../ai_pmo_kernel_mixins_ingress_render.py | 59 +- .../ai_pmo_kernel_mixins_ingress_storage.py | 43 + .../intent/ai_pmo_kernel_mixins_intent_llm.py | 43 +- .../ai_pmo_kernel_mixins_intent_resolve.py | 12 + .../ai_pmo_kernel_mixins_intent_rules.py | 3 + ...i_pmo_kernel_mixins_world_model_session.py | 42 +- .../ai_pmo_kernel_common_core/imports.py | 58 + .../ai_pmo_kernel_ingress_flow/mixin_auto.py | 3 + .../ai_pmo_kernel_ingress_flow/mixin_entry.py | 120 +- .../mixin_prepare_manual.py | 40 +- .../mixin_builder.py | 26 + .../mixin_execute_mutations.py | 1857 +++++++++++++++++ .../mixin_execute_query_project.py | 25 +- .../mixin_execute_signal_source.py | 354 +++- .../ai_pmo_typed_executor_execute/service.py | 29 + .../services/ai_pmo/draft_confirm_handler.py | 100 +- .../ai_service_core/mixin_business_tasks.py | 19 +- .../ai_service_core/mixin_runtime_core.py | 69 +- .../app/services/ai_service_core/shared.py | 14 +- .../context_cache_service/invalidation.py | 9 +- .../mixin_card_action.py | 118 +- .../mixin_config.py | 34 +- .../mixin_receive.py | 5 + .../mixin_receive_capture_flow.py | 2 + .../mixin_receive_prepare.py | 126 ++ .../mixin_reply_llm_core.py | 42 +- .../mixin_reply_outbox.py | 26 +- .../mixin_lifecycle_connection.py | 31 +- .../mixin_lifecycle_control.py | 4 + .../mixin_runtime.py | 71 +- .../feishu_long_connection_core/shared.py | 39 + .../mixin_command_commit_project.py | 236 ++- .../mixin_command_commit_task.py | 129 +- .../mixin_command_exec_core.py | 4 +- .../mixin_context_prefill_a.py | 5 +- .../mixin_context_prefill_b.py | 11 +- .../mixin_extraction.py | 12 +- .../mixin_followup_a.py | 94 +- .../mixin_followup_b.py | 76 +- .../mixin_gate_dispatch.py | 7 +- .../mixin_gate_project.py | 68 +- .../mixin_intent_gate_core.py | 5 +- .../mixin_item_finalize_routing.py | 11 +- .../mixin_item_processing.py | 9 +- .../mixin_message_utils_a.py | 20 +- .../mixin_message_utils_b.py | 20 +- .../mixin_process_message_core.py | 8 +- .../mixin_process_message_default.py | 17 +- .../mixin_process_message_helpers_b.py | 17 +- .../mixin_queue_confirm_undo.py | 185 +- .../mixin_serialization.py | 2 +- .../feishu_signal_pipeline/shared.py | 20 + .../feishu_signal_pipeline/shared_core.py | 2 +- .../shared_schema_utils.py | 17 +- .../llm_service/mixin_cache_circuit.py | 29 + .../services/llm_service/mixin_chat_openai.py | 37 +- .../llm_service/mixin_openclaw_routing.py | 27 +- .../mixin_prompt_profile_runtime.py | 15 +- .../mixin_tier_engine_selection.py | 43 +- .../llm_service/mixin_tier_escalation.py | 264 ++- .../services/llm_service/shared_part_01.py | 49 +- backend/app/services/model/model_config.py | 238 +++ .../services/model/model_routing/service.py | 10 +- .../mixin_guardrails.py | 72 +- .../project_knowledge_graph_service/shared.py | 1 + .../signal/draft_generator_service.py | 351 +++- .../mixin_capture_entry.py | 19 + .../mixin_pipeline_cache.py | 5 +- .../mixin_task_orchestration_main.py | 202 +- .../mixin_task_orchestration_skill.py | 19 +- .../signal/signal_capture_service/shared.py | 19 +- .../signal/signal_planner/mixin_analyze.py | 6 + .../signal/signal_planner/mixin_loaders.py | 32 +- .../signal/signal_planner/mixin_prompt.py | 2 + .../services/signal/signal_planner/shared.py | 2 + backend/app/services/sstc/charter_data.py | 268 +++ backend/app/services/sstc/charter_payload.py | 155 ++ .../services/sstc/charter_prefill_utils.py | 132 +- backend/app/utils/thread_context.py | 46 + backend/check_drafts.py | 52 + backend/handle_confirm.py | 66 + backend/handle_confirm2.py | 65 + backend/handle_confirm3.py | 67 + backend/handle_draft_confirm.py | 75 + backend/requirements.txt | 1 + .../test_adaptive_rag_mainline_contract.py | 151 ++ .../test_ai_orchestration_run_service.py | 56 + backend/tests/test_ai_pmo_ingress_mainline.py | 770 +++++++ ...t_ai_pmo_signal_source_onboard_contract.py | 112 + .../tests/test_ai_prompt_registry_contract.py | 58 + backend/tests/test_ai_service_core_runtime.py | 238 +++ backend/tests/test_draft_confirm_handler.py | 257 +++ backend/tests/test_draft_generator_service.py | 202 ++ .../tests/test_feishu_card_action_contract.py | 78 + ...est_feishu_long_connection_tls_contract.py | 264 +++ .../test_feishu_pipeline_queue_contract.py | 736 +++++++ ...t_feishu_receive_orchestration_contract.py | 53 + backend/tests/test_signal_planner_contract.py | 206 ++ .../test_sstc_tasks_ai_breakdown_contract.py | 197 ++ frontend/src/pages/ai-steward/constants.ts | 6 +- .../pages/ai-steward/sections/EngineTab.tsx | 6 +- package-lock.json | 168 +- package.json | 5 + 153 files changed, 12200 insertions(+), 857 deletions(-) create mode 100644 .tmp-feishu-escape.png create mode 100644 .tmp-feishu-menu.png create mode 100644 .tmp-feishu-newtab.png create mode 100644 .tmp-feishu-replymode.png create mode 100644 backend/alembic/versions/20260324_add_project_create_idempotency_indexes.py create mode 100644 backend/alembic/versions/20260325_add_feishu_reply_outbox_sent_message_id.py create mode 100644 backend/app/config/ai_prompt_registry.py create mode 100644 backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_mutations.py create mode 100644 backend/app/services/model/model_config.py create mode 100644 backend/app/services/sstc/charter_payload.py create mode 100644 backend/app/utils/thread_context.py create mode 100644 backend/check_drafts.py create mode 100644 backend/handle_confirm.py create mode 100644 backend/handle_confirm2.py create mode 100644 backend/handle_confirm3.py create mode 100644 backend/handle_draft_confirm.py create mode 100644 backend/tests/test_ai_orchestration_run_service.py create mode 100644 backend/tests/test_ai_pmo_signal_source_onboard_contract.py create mode 100644 backend/tests/test_ai_prompt_registry_contract.py create mode 100644 backend/tests/test_draft_generator_service.py create mode 100644 backend/tests/test_feishu_long_connection_tls_contract.py create mode 100644 backend/tests/test_feishu_pipeline_queue_contract.py create mode 100644 backend/tests/test_signal_planner_contract.py create mode 100644 backend/tests/test_sstc_tasks_ai_breakdown_contract.py create mode 100644 package.json diff --git a/.env.example b/.env.example index ee48bbf5..3bc71c66 100644 --- a/.env.example +++ b/.env.example @@ -26,32 +26,10 @@ LLM_ROUTING_DEFAULT_TIER=active LLM_LOW_TO_HIGH_ESCALATION_ENABLED=true LLM_REQUEST_TIMEOUT=90 -# 模型名统一使用裸名,不带供应商前缀 -# GLM-4.7 配 GLM_API_KEY -# GLM-5(经 OpenAI 兼容网关)配 OPENAI_API_KEY -GLM_API_KEY= -GLM_BASE_URL=https://api.edgefn.net/v1 -GLM_MODEL=GLM-4.7 +# 模型默认值统一由 backend/.openclaw/pmflow-routing-rules.json 管理 +DASHSCOPE_API_KEY= OPENAI_API_KEY= -OPENAI_BASE_URL=https://api.edgefn.net/v1 -OPENAI_MODEL=GLM-5 - -LLM_ROUTING_LOW_PROVIDER=glm -LLM_ROUTING_LOW_MODEL=GLM-4.7 -LLM_ROUTING_HIGH_PROVIDER=openai -LLM_ROUTING_HIGH_MODEL=GLM-5 -LLM_ROUTING_SAFETY_PROVIDER=glm -LLM_ROUTING_SAFETY_MODEL=GLM-4.7 - DEEPSEEK_API_KEY= -DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 -DEEPSEEK_MODEL=deepseek-chat - -OLLAMA_HOST=http://host.docker.internal:11434 -OLLAMA_BASE_URL=http://host.docker.internal:11434 -OLLAMA_MODEL=llama3:latest -OLLAMA_CHAT_MODEL=llama3:8b -OLLAMA_EMBEDDING_MODEL=nomic-embed-text # Adaptive RAG 深入思考模式 RAG_DEEP_RETRIEVAL_STEPS=5 diff --git a/.tmp-feishu-escape.png b/.tmp-feishu-escape.png new file mode 100644 index 0000000000000000000000000000000000000000..d80237764cd7518a5dacd079855a07813689446f GIT binary patch literal 259567 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 G`F{bjG=jMR literal 0 HcmV?d00001 diff --git a/.tmp-feishu-menu.png b/.tmp-feishu-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..71eec3380f282ff47e315d69cd3c6bb4c56b4d45 GIT binary patch literal 232793 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 z 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%0A7lomf17m9~n)2MkO#ov9_$vONsP@!S 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 N^ESU$oxOSYzX0D%2qFLg literal 0 HcmV?d00001 diff --git a/.tmp-feishu-replymode.png b/.tmp-feishu-replymode.png new file mode 100644 index 0000000000000000000000000000000000000000..3522a00267b6aaaa0fa695acd3ce0e26041baa32 GIT binary patch literal 226740 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


r_Ce}k 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 literal 0 HcmV?d00001 diff --git a/CLAUDE.md b/CLAUDE.md index 2ca5b438..cff49a11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ backend/ │ ├── schemas/ # Pydantic 模型 │ ├── services/ # 业务服务(AI/SSTC/集成) │ ├── config/ # 配置与 prompt -│ └── utils/ # 工具函数 +│ └── utils/ # 工具函数(timezone / audited_write / thread_context) ├── alembic/ # 数据库迁移 │ └── versions/ │ ├── 20260310_add_feishu_outbox_reply_hash.py # 飞书 outbox 回执哈希幂等字段 @@ -56,7 +56,8 @@ backend/ │ ├── 20260311_expand_user_workload_ratio_precision.py # user_workloads.workload_ratio 精度扩容(10,2) │ ├── 20260311_add_conversation_sessions_table.py # 飞书多轮会话上下文持久化表 │ ├── 20260316_add_ai_orchestration_ingress_async_fields.py # ai_orchestration_runs 入站异步重试字段扩展 -│ └── 20260323_add_work_item_draft_confirmation_fields.py # work_items 草稿确认字段与索引 +│ ├── 20260323_add_work_item_draft_confirmation_fields.py # work_items 草稿确认字段与索引 +│ └── 20260324_add_project_create_idempotency_indexes.py # 项目创建幂等索引:活动项目标题大小写无关唯一 ├── scripts/ # 后端运维/数据修复脚本 │ ├── backfill_orphan_signal_links.py # 回填孤立 signal 的 project_signal_links │ ├── dev/ # 开发辅助脚本(check_users/seed_all) @@ -73,15 +74,22 @@ backend/ │ ├── test_adaptive_rag_mainline_contract.py │ ├── test_ai_pmo_execution_ledger_contract.py │ ├── test_feishu_card_action_contract.py -│ └── test_feishu_receive_orchestration_contract.py +│ ├── test_feishu_pipeline_queue_contract.py +│ ├── test_feishu_long_connection_tls_contract.py +│ ├── test_feishu_receive_orchestration_contract.py +│ ├── test_signal_planner_contract.py +│ ├── test_ai_prompt_registry_contract.py +│ └── test_sstc_tasks_ai_breakdown_contract.py ├── requirements.txt # Python 依赖 └── Dockerfile ``` - 新增:`backend/app/api/llm_providers.py`(AI 管家 LLM provider preset 配置读取接口) +- 新增:`backend/app/services/model/model_config.py`(模型 provider / tier 默认值的唯一配置读取器,读取 `backend/.openclaw/pmflow-routing-rules.json`) - 新增:`backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py`(双 Agent 共享决策内核:复杂度路由/执行门控/修正识别) - 新增:`backend/app/core/startup/ai_pmo_ingress_async.py`(AI PMO 入站异步任务轮询启动循环) - 新增:`backend/app/utils/timezone.py`(统一北京时间工具:`now_cn_aware/now_cn_naive`) - 新增:`backend/app/utils/audited_write.py`(写接口审计上下文、caller 归一、北京时间格式化) +- 新增:`backend/app/utils/thread_context.py`(飞书线程/消息锚点归一:优先 root/thread/reply_to,其次 source_message_id) - 新增:`backend/app/middleware/audit_context.py`(请求级 trace/interface/caller 注入) - 新增:`backend/app/api/audit.py`(审计查询接口:`GET /audit/operations`) - 新增:`backend/app/services/audit_query_service/service.py`(operation_logs 过滤与序列化) @@ -93,6 +101,15 @@ backend/ - 新增:`backend/app/services/signal/draft_generator_service.py`(写操作草稿生成服务:草稿结构化产出 + 24h 过期) - 新增:`backend/app/services/ai_pmo/draft_confirm_handler.py`(草稿确认处理器:确认/修改/取消 + 幂等保护) - 新增:`backend/app/core/startup/draft_expiry_worker.py`(草稿超时取消后台扫描任务) +- 新增:`backend/app/core/startup/model_probe.py`(启动期模型连通性探针:优先走 DB 里的实际引擎配置,失败不污染 provider circuit) +- 新增:`backend/app/services/sstc/charter_payload.py`(轻量宪章上下文提取模块:供 Signal Planner / SSTC 共享,避免导入环) +- 新增:`backend/app/config/ai_prompt_registry.py`(prompt registry / manifest,统一登记 prompt 场景、调用入口与契约) +- 更新:`backend/app/config/ai_prompts.py`(默认角色 Prompt 调整为“产品经理(立项评审)”,强调查重、MVP、前景与需求清单) +- 更新:`backend/app/config/ai_prompts_catalog/part_02.py`(`PROMPT_DRAFT_GENERATOR` 增加 active_projects / similar_projects / MVP / prospect / requirements 评审字段) +- 更新:`backend/app/api/tasks.py`(`/tasks/breakdown` 支持可选 `project_id/charter_id`,与 SSTC 宪章上下文和技能词表统一) +- 更新:`backend/app/schemas/common.py`(`TaskBreakdownRequest` 增加可选 `project_id/charter_id`) +- 更新:`backend/app/services/sstc/charter_data.py`(新增任务拆解上下文/技能词表/ prompt 拼装共享 helper) +- 更新:`backend/app/config/ai_prompts_catalog/part_03.py`(`PROMPT_DECOMPOSE_TASKS` 统一读取 `request/project/charter/modules` 结构化上下文) - 新增:`backend/tests/test_ai_service_core_runtime.py`(AIService 运行时回归:统一收敛 llm_service、缓存命中与流式兼容输出) - 新增:`backend/tests/test_rag_route_contract.py`(RAG 双入口契约回归:`asked_via/action_mode/message_intent/mixed_signal_part` 统一透传) - 新增:`backend/tests/test_ai_pmo_task_intent_contract.py`(AI PMO `task.create` 一等 typed intent 契约回归:意图识别、路由归一与执行器分派) @@ -103,6 +120,7 @@ backend/ - 新增:`backend/app/services/adaptive_rag_service/mixin_answer_mainline.py`(AdaptiveRAG 基础问答主链编排:运行时准备 + 只读主路径执行) - 新增:`backend/tests/test_adaptive_rag_mainline_contract.py`(AdaptiveRAG 入口分层契约回归:read 路径绕过写确认 gate、write 路径命中确认 gate、feishu write 仍走主链) - 新增:`backend/tests/test_ai_pmo_execution_ledger_contract.py`(执行账本/动作参数来源契约回归:`action_contract` 归一、账本幂等 key、状态映射) +- 新增:`backend/tests/test_sstc_tasks_ai_breakdown_contract.py`(SSTC 手动 AI 拆任务契约回归:宪章上下文压缩、prompt 注入、技能词表透传) - 更新:`backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py`(新增统一 `action_contract` 组装、`operation_execution_ledger` upsert、账本状态归一) - 更新:`backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py`(自动执行与草稿待确认路径接入统一执行账本,日志 `operation_code` 对齐 `action_contract`) - 更新:`backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py`(`waiting_manual` 路径接入统一执行账本与动作参数来源约束) @@ -110,7 +128,12 @@ backend/ - 新增:`backend/app/services/integrations/feishu_integration_service/mixin_receive_prepare.py`(飞书消息入口预处理:上下文抽取、空文本分支、附件标准化) - 新增:`backend/app/services/integrations/feishu_integration_service/mixin_receive_capture_flow.py`(飞书消息入口执行流:capture 持久化、异常回执、结果收尾) - 新增:`backend/tests/test_feishu_card_action_contract.py`(飞书卡片回调契约回归:动作解析与事件分发) +- 新增:`backend/tests/test_feishu_pipeline_queue_contract.py`(飞书待确认队列契约回归:action card 元数据与 follow-up 唯一目标约束) - 新增:`backend/tests/test_feishu_receive_orchestration_contract.py`(飞书入口编排契约回归:bot 过滤、空文本分支、capture/finalize 编排) +- 新增:`backend/tests/test_draft_generator_service.py`(草稿生成服务回归:PM 评审草稿包含相似项目、MVP、前景与需求清单) +- 新增:`backend/tests/test_signal_planner_contract.py`(Signal Planner 契约回归:最新宪章加载、prompt 注入、无宪章降级) +- 新增:`backend/tests/test_ai_prompt_registry_contract.py`(Prompt registry 契约回归:覆盖率与元数据完整性) +- 新增:`backend/alembic/versions/20260324_add_project_create_idempotency_indexes.py`(活动项目标题大小写无关唯一索引,收口项目创建重复写入) - 新增:`backend/app/services/signal_planner_service.py`(Signal Planner 异步分析服务) - 新增:`backend/app/services/model/model_routing/service.py`(函数级模型路由配置服务) - 新增:`backend/app/services/task_brief_service.py`(增量任务包中心服务) @@ -188,6 +211,7 @@ backend/ - 新增:`backend/app/api/sstc_api/shared.py`(SSTC 路由层公共依赖导入) - 更新:`backend/app/api/sstc.py`(降级为兼容入口,仅导出聚合 `router`) - 新增:`backend/app/services/sstc/`(SSTC 业务辅助能力拆分:signal/charter/governance/entity_lookup/review) +- 新增:`backend/app/services/sstc/charter_payload.py`(轻量宪章上下文提取模块:供 Signal Planner / SSTC 共享,避免导入环) - 新增:`backend/app/services/llm_service/`(LLM 服务按职责拆分的 mixin 包,所有子文件 <= 300 行) - 更新:`backend/app/services/llm.py`(降级为兼容入口,导出 `LLMService` 与 `llm_service`) - 新增:`backend/app/services/signal/signal_capture_service/`(主动信号捕获服务按入口/编排/分析/新项目拆分) @@ -197,7 +221,7 @@ backend/ - 新增:`backend/app/services/context_cache_service/`(上下文缓存服务按缓存核心/画像/快照/失效钩子拆分) - 更新:`backend/app/services/context_cache.py`(降级为兼容入口) - 新增:`backend/app/config/ai_prompts_catalog/`(Prompt 常量分片目录,按 `part_01..part_04` + `part_scenes_01..part_scenes_06` 管理) -- 更新:`backend/app/config/ai_prompts.py`(兼容聚合入口,保留 `build_default_role_prompt_bundle`) +- 更新:`backend/app/config/ai_prompts.py`(兼容聚合入口,保留 `build_default_role_prompt_bundle` 并显式导出 prompt registry helpers) - 新增:`backend/app/api/signals_api/`(signals 路由拆分为 list/create、ingest/fetch、feedback 子模块) - 新增:`backend/app/api/wecom_api/`(wecom 路由拆分为 config/webhook/messaging 子模块) - 新增:`backend/app/api/endpoints/skill_api/`(人员能力图谱路由拆分为 tags/bindings/workload/recommend 子模块) @@ -422,19 +446,22 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - low/high 双模型(`classify_intent_ai_pmo_kernel`、`classify_intent_complex`)与 `openclaw_command_dispatch` 必须遵循 DDL 接口化约束:禁止直连 SQL DDL,命中时强制输出 `ddl_api_required` - 双模型上下文注入仅允许通过 `ModelContextBundleService` 统一组包:固定三段 `knowledge_graph/task_template/contract_sync`,禁止在业务服务散落拼接 prompt 上下文 - AI PMO `signal.create` 在用户显式要求“先分析再入库 / 自行分析”时,若批量信号结构化分析失败,禁止回退规则标题直接入库,必须返回失败安全态 -- 所有 LLM 调用禁止模型级兜底:禁止高阶升级、Safety 引擎、应急直连、stub 假成功;模型异常、超时或结构化输出不合法时必须显式报错 +- 所有 LLM 调用禁止静默兜底与 stub 假成功;低阶模型失败时必须优先强制升级到高阶 GLM-5 重试并保留低/高两段日志,高阶仍失败时才显式报错,禁止用固定兜底文案冒充成功结果 - 禁止模型兜底回复文案:模型失败、超时、空响应、结构化解析失败时,必须返回真实错误原因;禁止输出“系统处理中/请稍后重试”等固定文案冒充成功结果 +- AdaptiveRAG `adaptive_rag_answer_normal` 空响应必须显式抛错并交由高阶 GLM-5 failover 处理;禁止在生成层直接回落为“补充问题细节后重试”之类的兜底回复 - 用户可见问答链路(`function_name` 含 `answer/reply/qa`)禁止使用 `reasoning_content` 回退,避免思考过程外漏 -- AdaptiveRAG 场景化 Prompt 选择器必须在 `mixin_deep_guard_answer.py` 组装 Prompt 前执行,并遵循固定优先级:成员负载 > 外部趋势 > mixed > AI PMO 入口 > 默认 RAG +- AdaptiveRAG 场景化 Prompt 选择器必须在 `mixin_deep_guard_answer.py` 组装 Prompt 前执行,并遵循固定优先级:成员负载 > 趋势(项目关联 external_trend / 通用 industry_trend) > mixed > AI PMO 入口 > 默认 RAG +- 通用行业趋势问答(如“宠物行业最新动态”)不得注入数据库、项目历史、用户历史或外部实时搜索结果;只有显式带项目上下文时才允许走 `external_trend`,对“不要提项目/别提项目”这类否定语句不能误判为项目上下文 - mixed 场景回复必须强制两段格式输出:`[回答]` 与 `[已收到需求] ...,已入库处理`,禁止把需求段落并入问答段落 - 飞书与 AI PMO 入口(含 `message_intent=mixed`)问答链路必须绕过 deterministic/snapshot/glm_semantic 快路径,统一落到场景化 Prompt 生成,避免旧确定性清单回复覆盖场景格式 - 本地 `ollama` 仅允许知识库问答链路使用,且必须通过 `llm_engine.settings.enable_local_kb_qa` 显式开启;未开启时运行期与连通测试都不可用 - AI Steward 零配置创建任一步 LLM 失败、超时或 JSON 非法时必须直接报错,禁止回退默认 analysis/signal/charter/tasks 草稿 - Signal Source Onboarding 的 planner/worker 任一步失败必须显式报错或标记 failed,禁止 heuristic/fallback 兜底计划与“最小闭环”摘要 - AI Steward 不再暴露 OpenClaw 指挥规划入口,`/ai-steward/command/dispatch` 视为下线路径 -- AI PMO 写操作(`project.create/signal.create/signal_source.onboard`)默认先生成草稿并进入 `draft_pending_confirm`,未确认前禁止自动执行入库 -- 草稿确认必须校验 `draft_version`,并通过 `confirm_message_id` 防重放;草稿超时取消统一由 `draft_expiry_worker` + `feishu_reply_outbox` 处理 +- AI PMO 写操作(`project.create/signal.create/signal_source.onboard`)默认先生成草稿并进入 `draft_pending_confirm`,未确认前禁止自动执行入库;`project.create` 的 Feishu 文本入口先转待确认卡片,点击后才允许执行 +- 草稿确认只认显式 `reply_to_message_id` + 明确草稿动作(`确认/修改/取消`)或显式 `work_item_id`;草稿确认必须校验 `draft_version`,并通过 `confirm_message_id` 防重放;草稿超时取消统一由 `draft_expiry_worker` + `feishu_reply_outbox` 处理 - AI PMO 执行层必须统一产出 `execution_result.action_contract` 与 `action_param_source`,并写入 `operation_execution_ledger`;禁止在执行链路旁路写副作用且不入账 +- AI PMO 写操作幂等以消息级锚点为准:优先使用 `source_message_id` / `feishu_message_id` 作为执行账本幂等键,标题/正文仅用于业务内容,不得参与重复执行判定 ## 9. 新增代码的参考模式 - 新增后端接口:参考 `backend/app/api/sstc.py` 的结构 @@ -446,6 +473,9 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - LLM 引擎设置接口:参考 `backend/app/api/llm_engines.py`(`PUT /ai-steward/llm-engines/settings`,仅管理员可修改本地知识库问答开关) - 飞书捕获模拟接口:`POST /ai-steward/signal/capture-feishu` 在 `direct_reply_required=true` 时会同步写入 `feishu_reply_outbox`,并返回 `reply_outbox_enqueued/reply_outbox_id` 便于链路自检 - 配置读取类接口:参考 `backend/app/api/llm_providers.py`(`GET /ai-steward/llm/providers`) +- 模型配置唯一来源:参考 `backend/app/services/model/model_config.py`(统一读取 `backend/.openclaw/pmflow-routing-rules.json` 内的 `provider_defaults/tier_defaults/rules`) +- 模型配置热更新后,LLM provider circuit 会在下一次调用时自动清空旧熔断状态,避免旧配置失败污染新配置的连通性判断 +- 启动期模型探针只做连通性观测,不应把探针失败写回 provider circuit;否则会误伤真实业务请求 - AI Steward 指挥规划接口已移除:`POST /ai-steward/command/dispatch` - 项目删除接口(`DELETE /projects/{project_id}`):参考 `backend/app/api/projects.py`,先清理强依赖外键、再置空弱依赖外键、最后删除主记录(单事务提交) - 项目成员分配接口(`PUT /projects/{project_id}/members`):参考 `backend/app/api/projects.py` + `backend/app/services/project_member/service.py`,要求显式传入 `member_user_ids` 并写入 `user_projects` @@ -479,19 +509,23 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ## 11. 已知 BOUNDARY(不要幻构行为) - 飞书回调字段可能缺失文件名/大小,仅保证 `attachment_id/key/type` 可用 +- 飞书长连接 websocket 在本地/CI 环境默认使用 `certifi` CA bundle 作为信任链;如需自定义 CA,可通过 `FEISHU_LONG_CONNECTION_SSL_CERT_FILE` 或 `SSL_CERT_FILE` 覆盖 +- 飞书长连接状态分为两层:`last_received_at/last_received_message_id` 表示 SDK 回调已收到事件,`last_event_at` 表示 worker 完成处理;排查“没吃进去”时必须优先看 `last_received_*` +- 飞书长连接会话应复用 `Integration.config` 中已有的 `encrypt_key/verification_token`;配置保存时若未显式修改这两个字段,必须保留现有值,避免 SDK 因缺失事件密钥而拒收消息 - LLM 主链按 `OPENCLAW_ROUTING_ENABLED` 走 OpenClaw 路由规则(`backend/.openclaw/pmflow-routing-rules.json`);请求超时统一以 `LLM_REQUEST_TIMEOUT` 环境变量为准 - LLM 非结构化响应仅在非用户可见链路且显式开启 `LLM_REASONING_CONTENT_FALLBACK` 时,才允许从 `reasoning_content` 回退;`answer/reply/qa` 链路一律禁用该回退 - World Model 静态层已接入 Redis 缓存(key: `wm:static:{tenant_id}:{project_id}`,TTL 600s) - 写操作后主动 invalidate,动态层不缓存 - AI PMO 意图识别已切换 `model-only`:`_resolve_intent` 仅接受 LLM 结构化结果,不再走规则/正则兜底链路 -- 环境变量以根目录 `.env` / `.env.example` 为唯一配置源;`backend/.env` 已移除且不再读取 +- AI PMO / Feishu 执行门已取消置信度阈值拦截与高风险人工复核:`confidence` 仅保留在 `decision_trace` / processing log 中作观测,不再作为自动执行的 reject 条件;当前门禁仅保留 `force_manual` 与 `correction_action` +- 模型默认值与 provider/tier 映射以 `backend/.openclaw/pmflow-routing-rules.json` 为唯一配置源;`.env` / `.env.example` 仅保留 API key 与运行开关,`OPENCLAW_ROUTING_RULES_PATH` 只用于定位这份文件 - Signal Planner 函数路由支持 `integrations(type='model_routing')` 覆盖配置;配置缺失时默认直连 GLM - Signal Planner 结构化调用采用 `25s` 硬超时:超时或结构化不合法时标记失败并通知人工重试,不再写入降级 `ai_planner(fallback_mode=true)` - AI Steward 用户侧已移除 OpenClaw 指挥规划入口;后续项目分析主链以 GLM 严格失败直出为准 - 飞书业务命令命中旧 command-first 路由时,仅返回“已下线”阻断提示,不再触发实际执行 - 模型命名统一使用裸模型名(如 `GLM-4.7`、`MiniMax-M2.5`),不使用供应商前缀模型名 - 双 Agent 路由治理:低/高层函数均可由 OpenClaw 规则命中到指定 provider/model(含 `classify_intent_ai_pmo_kernel`) -- 双模型 DDL 治理图谱的模型来源优先读取显式模型配置;缺失时回退 `LLM_ROUTING_LOW_MODEL/LLM_ROUTING_HIGH_MODEL` +- 双模型 DDL 治理图谱的模型来源统一读取 `backend/.openclaw/pmflow-routing-rules.json` 中的 `provider_defaults/tier_defaults`;不再依赖 `LLM_ROUTING_LOW_MODEL/LLM_ROUTING_HIGH_MODEL` 作为主配置 - 写操作链路统一产出 `decision_trace`(intent/tier/confidence/gate_code/correction_action)用于离线回放与分桶评分 - AI PMO 意图仲裁默认开启(`AI_PMO_INTENT_ARBITRATION_ENABLED=true`):规则与 LLM 写操作冲突时按 `AI_PMO_INTENT_CONFLICT_POLICY=pending_manual` 强制进入人工确认,不直接自动执行 - 飞书 `qa_llm` 回复已增加事实熔断:命中“数据库迁移/DDL/Alembic/让我直接处理/模型失败了所以没分析”等漂移语句时,自动回退固定模板,禁止对外宣称未证实执行动作 @@ -503,9 +537,16 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - 兼容 SQL 查询入口:`ai_pmo_work_items` 为视图别名(底层映射 `work_items`),避免历史脚本直接查旧表名失败 - `signal_source.onboard` 已加入工作流门禁旁路:进入执行阶段后按闭环结果返回 `succeeded/pending_manual/failed_safe`,不再被前置仿真门禁直接拦截为 `waiting_manual` - 飞书入站已拆分“短确认同步快路径 + 非短确认异步队列”:短确认命中会话窗口时直接继承上轮意图并同步执行;其余消息先回“已收到,正在处理”后由 `ai_orchestration_runs` 异步 worker 执行与重试 -- 会话命中 `session.last_intent=draft_pending_confirm` 时,入站优先进入草稿确认处理器,不再走常规意图识别 +- 飞书会话与问答上下文键已改为线程/消息锚点优先:优先 `reply_to_message_id/root_message_id/thread_id/source_message_id`,避免 sender 级最近会话复用串线;草稿确认还需要回查 `feishu_reply_outbox.sent_message_id`,以便把“回复草稿消息”的线程锚点映射回对应 work_item +- 飞书回复线程锚点抽取同时兼容 event 顶层与 message/content 内字段,并对 event/message 的嵌套结构做递归兜底;若 Feishu SDK 返回的 reply 关系字段在更深层对象里,也必须纳入线程锚点,不可只读 `message.content` +- 飞书异步入站队列在 `ai_orchestration_runs.result_payload.task_payload` 中必须保留 `thread_context`,worker 重放时不得仅依赖 `source_message_id`,否则 reply-thread 会在异步链路丢锚点 +- 飞书纯问答直答不再注入群聊级 `project_id` 兜底上下文;只有显式 reply-thread / follow-up 线程锚点命中时,才允许继承上一轮语义上下文,禁止把群里最近项目当成普通新问题的默认场景 +- 成员/负载类问答(如“杨宇现在负载情况如何”)会优先走 `users + user_workloads` 的确定性直查;只有姓名/账号能唯一匹配时才直接返回数据库负载答案,不再把项目列表或历史上下文混入负载结论 +- 会话命中 `session.last_intent=draft_pending_confirm` 不再自动劫持普通消息;只有明确 `reply_to_message_id` 且消息本身是草稿动作,或显式 `work_item_id` 命中挂起草稿时,才进入草稿确认处理器 +- 飞书 follow-up 状态追问不再猜测“最近一条处理项”;只有显式 reply 锚点或唯一业务目标匹配时才允许继续查询,避免多个话题并行时串线 - 草稿确认成功后执行路径复用 `followup` 恢复链路,目标 work_item 在同事务内完成状态迁移与 `decision_records.final_outcome` 更新 - 飞书回复 outbox 已支持 `text + interactive card` 双通道:执行场景命中 `draft_pending_confirm/pending_manual` 时优先入队按钮卡片;卡片消息仍经 outbox 异步派发(不走业务路径同步直发) +- AI PMO 回执渲染器 `ReplyRenderer` 已按 `operation_code` 收口 CRUD 写回执:`project/task/personnel/knowledge/notification` 的成功态与待确认态都必须从 `execution_result.action_contract` / intent 参数生成可读文案,禁止再回落成泛化“已收到并入库”提示 - 飞书入口已按职责拆分为“预处理/捕获执行/结果回执”三层:`mixin_receive.py` 仅保留编排,`mixin_receive_prepare.py` 负责传输解析与上下文抽取,`mixin_receive_capture_flow.py` 负责 capture 持久化与回执收尾 - `ai_orchestration_runs.project_id` 允许为空:用于承载尚未完成项目归属判定的入站异步任务;既有 `signal_source_onboarding` 运行仍按显式 `project_id` 写入 - `/ai-pmo/process-text` 问答链路在 AdaptiveRAG 内通过 `asked_via=pmflow_manual` 识别为 AI PMO 入口;mixed 场景通过 `message_intent=mixed` 透传触发专用 Prompt 与两段输出格式化 @@ -519,6 +560,8 @@ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - [x] AI 入口主链已统一:飞书长连接(`capture_from_feishu`)、`/ai-pmo/process-text` 与 `/rag/ask` 已统一收敛到 `AIPMOKernelService.process_ingress` 主链;`/rag/ask` 通过 `rag_ask_contract.build_kernel_ingress_kwargs` 透传 RAG 上下文(`session_id/new_session/thinking_mode/action_mode/...`),query 执行后由 `build_rag_response_payload` 保持 HTTP 响应兼容,回归由 `test_rag_route_contract.py`、`test_ai_pmo_qa_http_mainline_e2e.py`、`test_ai_pmo_ingress_mainline.py` 锁定 - [x] 飞书入口层耦合已收敛:飞书真实入口已拆为三段职责链(预处理 `mixin_receive_prepare.py` / capture 执行 `mixin_receive_capture_flow.py` / 编排 `mixin_receive.py`);`mixin_receive.py` 不再混合传输解析、附件标准化、capture 异常兜底与收尾回执逻辑,分层边界通过 `test_feishu_receive_orchestration_contract.py` 锁定 - [x] `task.create` 已升级为一等 typed intent:`IntentResolver/RouteResolver` 新增 `task.create` 常量与 `task.create` 路由族;TypedExecutor 已接入任务落库执行(含 `project_id`/任务标题槽位守卫、对齐 Charter 校验、`persist_task` 写入);`WorkflowRegistry`、`ReplyRenderer`、`followup` 校验与草稿确认闭环已纳入 `task.create`;并补 `test_ai_pmo_task_intent_contract.py` 回归锁定 +- [x] `task.create` 已接入消息级幂等账本:Feishu `source_message_id/feishu_message_id` 作为 `task.create` 账本 key,重复确认只回放同一结果,不再重复落库;回归由 `test_feishu_pipeline_queue_contract.py` 锁定 +- [x] `signal_source.onboard` 已接入消息级幂等账本:TypedExecutor 以 `source_system + source_message_id` 作为 onboarding ledger key,重复提交只回放同一结果,不再重复跑信号源接入编排;回归由 `test_ai_pmo_signal_source_onboard_contract.py` 锁定 - [x] AdaptiveRAG 职责已完成首轮收敛:`answer_question` 入口已分层为 `prepare runtime` 与 `base mainline` 两段,写确认逻辑通过 `_should_trigger_write_ack_gate` 独立门控;基础问答主路径不再与写确认判断混写在同一函数体。分层契约由 `test_adaptive_rag_mainline_contract.py` 锁定(read 绕过 ack、write 命中 ack、feishu write 维持主链) - [x] 执行层已形成统一执行账本与参数来源约束:ingress 执行路径统一通过 `KernelIngressStorageMixin` 生成 `execution_result.action_contract`(固定 `param_source=decision.execution_plan.actions+workflow_route+execution_result`)并写入 `operation_execution_ledger`(`applied/pending_manual/pending_confirmation/failed`);`mixin_auto.py`、`mixin_prepare_manual.py` 的 `operation_code` 日志来源已对齐 `action_contract`,回归由 `test_ai_pmo_execution_ledger_contract.py` 锁定 - [x] QA/RAG 路由契约双轨已收敛:`/ai-steward/rag/ask` 兼容入口已移除,问答统一收敛到 `POST /rag/ask`;参数构造与 tenant 解析收口在 `backend/app/api/rag_ask_contract.py`,前端 `FloatingQAChat` 与 Context Cache API 目录均已对齐,`backend/tests/test_rag_route_contract.py` 与 `backend/tests/test_context_cache_profile_catalog.py` 已锁定回归 diff --git a/backend/.openclaw/pmflow-routing-rules.json b/backend/.openclaw/pmflow-routing-rules.json index cd365b48..bfd7fd56 100644 --- a/backend/.openclaw/pmflow-routing-rules.json +++ b/backend/.openclaw/pmflow-routing-rules.json @@ -1,8 +1,54 @@ { - "version": "v2", - "updated_at": "2026-03-11T09:10:00Z", + "version": "v3", + "updated_at": "2026-03-25T00:00:00Z", "source_of_truth": "openclaw", "description": "PMFlow 三引擎统一路由:所有主链路统一经 OpenClaw OpenAI-Compatible 网关。", + "provider_defaults": { + "deepseek": { + "name": "DeepSeek", + "base_url": "https://api.deepseek.com/v1", + "model": "deepseek-chat", + "api_env": "DEEPSEEK_API_KEY" + }, + "glm": { + "name": "Qwen (DashScope)", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY" + }, + "openai": { + "name": "OpenAI", + "base_url": "https://api.edgefn.net/v1", + "model": "MiniMax-M2.5", + "api_env": "OPENAI_API_KEY" + }, + "ollama": { + "name": "Ollama", + "base_url": "http://127.0.0.1:11434", + "model": "llama3:latest", + "api_env": "" + } + }, + "tier_defaults": { + "low": { + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_env": "DASHSCOPE_API_KEY" + }, + "high": { + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.edgefn.net/v1", + "api_env": "OPENAI_API_KEY" + }, + "safety": { + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_env": "DASHSCOPE_API_KEY" + } + }, "rules": [ { "name": "engine_low_project_draft_glm47_fallback", @@ -10,8 +56,9 @@ "priority": 340, "tier": "low", "provider": "glm", - "model": "GLM-4.7", - "api_env": "GLM_API_KEY", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "match_any": [ "project_draft_analysis_glm47_fallback", "project_draft_signal_glm47_fallback", @@ -35,8 +82,19 @@ "priority": 310, "tier": "low", "provider": "glm", - "model": "GLM-4.7", - "api_env": "GLM_API_KEY", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "function_exact": "classify_intent_ai_pmo_kernel" + }, + { + "name": "engine_high_intent_kernel_exact", + "enabled": true, + "priority": 305, + "tier": "high", + "provider": "openai", + "model": "GLM-5", + "api_env": "OPENAI_API_KEY", "function_exact": "classify_intent_ai_pmo_kernel" }, { @@ -45,8 +103,9 @@ "priority": 300, "tier": "low", "provider": "glm", - "model": "GLM-4.7", - "api_env": "GLM_API_KEY", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "match_any": [ "feishu_level_classification", "feishu_signal_extraction", diff --git a/backend/alembic/versions/20260324_add_project_create_idempotency_indexes.py b/backend/alembic/versions/20260324_add_project_create_idempotency_indexes.py new file mode 100644 index 00000000..bf43e2d5 --- /dev/null +++ b/backend/alembic/versions/20260324_add_project_create_idempotency_indexes.py @@ -0,0 +1,83 @@ +"""add project create idempotency indexes + +Revision ID: add_project_create_idempotency_indexes +Revises: add_work_item_draft_confirmation_fields +Create Date: 2026-03-24 +""" + +from __future__ import annotations + +from alembic import op +import sqlalchemy as sa + + +revision = "add_project_create_idempotency_indexes" +down_revision = "add_work_item_draft_confirmation_fields" +branch_labels = None +depends_on = None + + +def _table_exists(bind, table_name: str) -> bool: + return sa.inspect(bind).has_table(table_name) + + +def _duplicate_active_titles(bind) -> list[tuple[int, str, int]]: + rows = bind.execute( + sa.text( + """ + SELECT + tenant_id, + lower(coalesce(nullif(title, ''), nullif(name, ''))) AS normalized_title, + count(*) AS duplicate_count + FROM projects + WHERE status <> 'inactive' + AND coalesce(nullif(title, ''), nullif(name, '')) IS NOT NULL + GROUP BY tenant_id, lower(coalesce(nullif(title, ''), nullif(name, ''))) + HAVING count(*) > 1 + ORDER BY duplicate_count DESC, tenant_id ASC + LIMIT 10 + """ + ) + ).fetchall() + return [ + (int(row[0]), str(row[1] or ""), int(row[2])) + for row in rows + ] + + +def upgrade() -> None: + bind = op.get_bind() + if not _table_exists(bind, "projects"): + return + + duplicates = _duplicate_active_titles(bind) + if duplicates: + sample = ", ".join( + f"tenant={tenant_id}, title={title}, count={count}" + for tenant_id, title, count in duplicates[:3] + ) + raise RuntimeError( + "cannot create uq_projects_tenant_active_title_ci because duplicate active project titles exist: " + + sample + ) + + op.execute( + sa.text( + """ + CREATE UNIQUE INDEX IF NOT EXISTS uq_projects_tenant_active_title_ci + ON projects ( + tenant_id, + lower(coalesce(nullif(title, ''), nullif(name, ''))) + ) + WHERE status <> 'inactive' + AND coalesce(nullif(title, ''), nullif(name, '')) IS NOT NULL + """ + ) + ) + + +def downgrade() -> None: + bind = op.get_bind() + if not _table_exists(bind, "projects"): + return + op.execute(sa.text("DROP INDEX IF EXISTS uq_projects_tenant_active_title_ci")) diff --git a/backend/alembic/versions/20260325_add_feishu_reply_outbox_sent_message_id.py b/backend/alembic/versions/20260325_add_feishu_reply_outbox_sent_message_id.py new file mode 100644 index 00000000..1b3f10d4 --- /dev/null +++ b/backend/alembic/versions/20260325_add_feishu_reply_outbox_sent_message_id.py @@ -0,0 +1,40 @@ +"""add feishu reply outbox sent_message_id + +Revision ID: 20260325_add_feishu_reply_outbox_sent_message_id +Revises: f38d00f6fee4 +Create Date: 2026-03-25 10:00:00.000000 + +""" +from __future__ import annotations + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "20260325_add_feishu_reply_outbox_sent_message_id" +down_revision: Union[str, Sequence[str], None] = "f38d00f6fee4" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + op.add_column( + "feishu_reply_outbox", + sa.Column("sent_message_id", sa.VARCHAR(length=128), nullable=True), + ) + op.create_index( + "ix_feishu_reply_outbox_sent_message_id", + "feishu_reply_outbox", + ["sent_message_id"], + unique=False, + ) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_index("ix_feishu_reply_outbox_sent_message_id", table_name="feishu_reply_outbox") + op.drop_column("feishu_reply_outbox", "sent_message_id") diff --git a/backend/app/api/llm_providers.py b/backend/app/api/llm_providers.py index 9022510c..c6222c5f 100644 --- a/backend/app/api/llm_providers.py +++ b/backend/app/api/llm_providers.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os from typing import Any from fastapi import APIRouter, Depends @@ -10,40 +9,39 @@ from fastapi import APIRouter, Depends from ..core.response import success_response from ..dependencies.auth import get_current_user from ..models import User +from ..services.model.model_config import get_provider_defaults, get_tier_defaults router = APIRouter(prefix="/ai-steward/llm", tags=["AI Steward"]) def _build_provider_payload() -> dict[str, Any]: - low_model = str(os.getenv("LLM_ROUTING_LOW_MODEL", "GLM-4.7") or "GLM-4.7").strip() - high_model = str(os.getenv("LLM_ROUTING_HIGH_MODEL", "MiniMax-M2.5") or "MiniMax-M2.5").strip() - base_url = str(os.getenv("OPENAI_BASE_URL", "https://api.edgefn.net/v1") or "https://api.edgefn.net/v1").strip() - ollama_base_url = str( - os.getenv("OLLAMA_BASE_URL", os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434")) or "http://127.0.0.1:11434" - ).strip() + provider_defaults = get_provider_defaults() + tier_defaults = get_tier_defaults() + low_tier = dict(tier_defaults.get("low") or {}) + high_tier = dict(tier_defaults.get("high") or {}) return { - "low": low_model, - "high": high_model, - "base_url": base_url, + "low": str(low_tier.get("model") or provider_defaults.get("glm", {}).get("model") or "qwen-plus").strip(), + "high": str(high_tier.get("model") or "GLM-5").strip(), + "base_url": str(high_tier.get("base_url") or provider_defaults.get("openai", {}).get("base_url") or "https://api.edgefn.net/v1").strip(), "providers": { "glm": { - "model": str(os.getenv("GLM_MODEL", low_model) or low_model).strip(), - "base_url": str(os.getenv("GLM_BASE_URL", base_url) or base_url).strip(), - "api_env": "GLM_API_KEY", + "model": str(provider_defaults.get("glm", {}).get("model") or "qwen-plus").strip(), + "base_url": str(provider_defaults.get("glm", {}).get("base_url") or "https://dashscope.aliyuncs.com/compatible-mode/v1").strip(), + "api_env": "DASHSCOPE_API_KEY", }, "openai": { - "model": str(os.getenv("OPENAI_MODEL", high_model) or high_model).strip(), - "base_url": base_url, + "model": str(provider_defaults.get("openai", {}).get("model") or "MiniMax-M2.5").strip(), + "base_url": str(provider_defaults.get("openai", {}).get("base_url") or "https://api.edgefn.net/v1").strip(), "api_env": "OPENAI_API_KEY", }, "deepseek": { - "model": str(os.getenv("DEEPSEEK_MODEL", "deepseek-chat") or "deepseek-chat").strip(), - "base_url": str(os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1") or "").strip(), + "model": str(provider_defaults.get("deepseek", {}).get("model") or "deepseek-chat").strip(), + "base_url": str(provider_defaults.get("deepseek", {}).get("base_url") or "https://api.deepseek.com/v1").strip(), "api_env": "DEEPSEEK_API_KEY", }, "ollama": { - "model": str(os.getenv("OLLAMA_MODEL", "llama3:latest") or "llama3:latest").strip(), - "base_url": ollama_base_url, + "model": str(provider_defaults.get("ollama", {}).get("model") or "llama3:latest").strip(), + "base_url": str(provider_defaults.get("ollama", {}).get("base_url") or "http://127.0.0.1:11434").strip(), "api_env": "", }, }, diff --git a/backend/app/api/projects_api/routes_mutation.py b/backend/app/api/projects_api/routes_mutation.py index 304d0a7a..064a6533 100644 --- a/backend/app/api/projects_api/routes_mutation.py +++ b/backend/app/api/projects_api/routes_mutation.py @@ -1,7 +1,7 @@ from __future__ import annotations from .shared import * -from .helpers import _cleanup_project_dependencies, _fetch_project_member_user_ids, _get_project_or_404 +from .helpers import _cleanup_project_dependencies, _fetch_project_member_user_ids, _get_project_or_404, _serialize_project_row from .schemas import ProjectCreateRequest, ProjectMembersAssignRequest, ProjectUpdateRequest @router.post("/") diff --git a/backend/app/api/sstc_api/tasks_ai_breakdown.py b/backend/app/api/sstc_api/tasks_ai_breakdown.py index 592485a1..7be1b9f8 100644 --- a/backend/app/api/sstc_api/tasks_ai_breakdown.py +++ b/backend/app/api/sstc_api/tasks_ai_breakdown.py @@ -4,9 +4,14 @@ import re from .shared import * +from ...services.sstc.charter_data import ( + _build_task_breakdown_context, + _build_task_breakdown_prompt, + _load_available_skill_names, +) from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project from ...services.sstc.governance import _assert_charter_aligned, _require_manager -from ...services.sstc.signal_core import _as_dict, _parse_node_id, _parse_project_id, _sync_pmflow_rag_async +from ...services.sstc.signal_core import _parse_node_id, _parse_project_id, _sync_pmflow_rag_async router = APIRouter(tags=["sstc"]) @@ -17,6 +22,7 @@ def _task_title_key(value: str) -> str: text = str(value or "").strip().lower() return _TITLE_NORMALIZE_RE.sub("", text) + @router.post("/projects/{project_id}/charters/{charter_id}/tasks/ai-breakdown") async def ai_breakdown_tasks( project_id: str, @@ -30,25 +36,32 @@ async def ai_breakdown_tasks( pid = _parse_project_id(project_id) cid = _parse_node_id(charter_id, "charter_id") tenant_id = int(current_user.tenant_id) - await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid) + project = await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid) charter = await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid) _assert_charter_aligned(charter) - prompt = f"""请基于以下需求宪章拆解 5-8 条任务,返回 JSON 数组: -[ - {{"task_name":"", "type":"backend/frontend/ai/devops", "estimated_hours": 4.0, "priority":"P0/P1/P2"}} -] - -宪章标题:{charter.title} -宪章内容:{_as_dict(charter.content)} -""" + charter_context = _build_task_breakdown_context( + description=str(charter.title or project.title or ""), + project=project, + charter=charter, + ) + available_skills = await _load_available_skill_names(db, tenant_id=tenant_id) + prompt = _build_task_breakdown_prompt( + project_id=pid, + charter_context=charter_context, + available_skills=available_skills, + ) try: - rows = await llm_service.generate_task_breakdown_async( + raw_text = await llm_service.generate( prompt, db=db, tenant_id=tenant_id, project_id=pid, + function_name="sstc_ai_breakdown", ) + rows = llm_service._parse_json_array(raw_text) + if not rows: + raise ValueError("AI 拆解返回空结果或无法解析为 JSON 数组") except RuntimeError as exc: raise HTTPException(status_code=403, detail=str(exc)) from exc except Exception as exc: @@ -73,7 +86,7 @@ async def ai_breakdown_tasks( created = [] skipped_duplicates = 0 prompt_tokens = max(len(prompt) // 4, 1) - completion_tokens = 0 + completion_tokens = max(len(raw_text) // 4, 0) for item in rows or []: title = compact_title((item.get("task_name") or item.get("title") or ""), max_len=32) if not title: @@ -108,7 +121,6 @@ async def ai_breakdown_tasks( refresh_workload=False, ) created.append(task) - completion_tokens += max(len(title) // 2, 1) db.add( AICallLog( diff --git a/backend/app/api/tasks.py b/backend/app/api/tasks.py index 5ea31d76..0b69e944 100644 --- a/backend/app/api/tasks.py +++ b/backend/app/api/tasks.py @@ -11,6 +11,11 @@ from ..schemas import TaskBreakdownRequest, TaskBreakdownResponse from ..services.llm_service import llm_service from ..services.integrations.integration_core.plane_service import plane_service from ..services.skill import recommend_personnel +from ..services.sstc.charter_data import ( + _build_task_breakdown_prompt, + _load_available_skill_names, + _load_task_breakdown_context, +) router = APIRouter(prefix="/tasks", tags=["tasks"]) @@ -23,16 +28,38 @@ async def breakdown_tasks( ): """AI 任务拆解""" tenant_id = int(current_user.tenant_id) + context = await _load_task_breakdown_context( + db, + tenant_id=tenant_id, + description=request.description, + project_id=request.project_id, + charter_id=request.charter_id, + ) + if request.project_id is not None and not context.get("project"): + raise HTTPException(status_code=404, detail="Project not found") + if request.charter_id is not None and not context.get("charter"): + raise HTTPException(status_code=404, detail="Charter not found") + + available_skills = await _load_available_skill_names(db, tenant_id=tenant_id) + prompt = _build_task_breakdown_prompt( + project_id=request.project_id, + context=context, + available_skills=available_skills, + ) try: - tasks = await llm_service.generate_task_breakdown_async( - request.description, + raw_text = await llm_service.generate( + prompt, db=db, tenant_id=tenant_id, + function_name="generate_task_breakdown", ) + tasks = llm_service._parse_json_array(raw_text) + if not tasks: + raise ValueError("AI 拆解返回空结果或无法解析为 JSON 数组") except RuntimeError: raise HTTPException(status_code=403, detail="预算超限,AI 调用被阻止(hard limit)") - if not tasks: - raise HTTPException(status_code=500, detail="Failed to generate task breakdown") + except Exception as exc: + raise HTTPException(status_code=500, detail=f"Failed to generate task breakdown: {exc}") sync_result = None if request.sync_to_plane: diff --git a/backend/app/config/ai_prompt_registry.py b/backend/app/config/ai_prompt_registry.py new file mode 100644 index 00000000..76202a37 --- /dev/null +++ b/backend/app/config/ai_prompt_registry.py @@ -0,0 +1,472 @@ +from __future__ import annotations + +from typing import Any + +from .ai_prompts_catalog import __all__ as PROMPT_CATALOG_EXPORTS + + +def _entry( + prompt_key: str, + *, + module: str, + business_scenario: str, + input_contract: str, + output_contract: str, + call_sites: tuple[str, ...] = (), +) -> dict[str, Any]: + return { + "prompt_key": prompt_key, + "module": module, + "business_scenario": business_scenario, + "call_sites": tuple(str(site) for site in call_sites if str(site or "").strip()), + "input_contract": input_contract, + "output_contract": output_contract, + } + + +JSON_INPUT = "输入为结构化 JSON 上下文。" +JSON_OUTPUT = "输出严格 JSON 对象,不要 Markdown。" +JSON_LIST_OUTPUT = "输出严格 JSON 数组,不要 Markdown。" +QA_INPUT = "输入为问题文本 + 业务上下文。" +QA_OUTPUT = "输出自然语言答复。" +PLAN_INPUT = "输入为信号 / 项目 / 宪章 / 成员上下文。" +PLAN_OUTPUT = "输出可执行规划 JSON。" +SCORE_INPUT = "输入为对比对象 + 评分上下文。" +SCORE_OUTPUT = "输出评分或建议 JSON。" +FRAGMENT_INPUT = "输入为 prompt 片段占位内容。" +FRAGMENT_OUTPUT = "输出为可拼接的 prompt 片段。" + +DEEP_GUARD_CALL_SITES = ("backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py",) +AI_BUSINESS_TASKS_CALL_SITES = ("backend/app/services/ai_service_core/mixin_business_tasks.py",) +AI_BUSINESS_ANALYSIS_CALL_SITES = ("backend/app/services/ai_service_core/mixin_business_analysis.py",) +FEISHU_EXTRACT_CALL_SITES = ("backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py",) +FEISHU_REPLY_CALL_SITES = ("backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py",) +FEISHU_PIPELINE_CALL_SITES = ("backend/app/services/integrations/feishu_signal_pipeline/mixin_task_orchestration_main.py",) +FEISHU_SIGNAL_CLASSIFY_CALL_SITES = ("backend/app/services/signal/signal_capture_service/mixin_analysis_normalize.py",) +SIGNAL_SOURCE_CALL_SITES = ("backend/app/services/signal/signal_source_orchestration/mixin_registry_execute.py",) +DRAFT_GENERATOR_CALL_SITES = ("backend/app/services/signal/draft_generator_service.py",) +RAG_INTENT_CALL_SITES = ("backend/app/services/adaptive_rag_service/mixin_core_intent.py",) +RAG_DB_CALL_SITES = ("backend/app/services/adaptive_rag_service/mixin_fast_answer_semantic_p1.py",) +SIGNAL_PLANNER_CALL_SITES = ("backend/app/services/signal/signal_planner/mixin_prompt.py", "backend/app/services/signal/signal_planner/mixin_analyze.py") +SKILL_SCORE_CALL_SITES = ("backend/app/services/skill/skill_score_service.py",) +WORKLOAD_CALL_SITES = ("backend/app/services/workload/ai_optimize.py",) +RECIPE_CALL_SITES = ("backend/app/services/recipe/service.py",) +PMO_INTENT_CALL_SITES = ("backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py",) +PMO_BATCH_CALL_SITES = ("backend/app/services/ai_pmo/ai_pmo_typed_executor_discovery/mixin_analysis.py",) + + +def _part_01_entries() -> tuple[dict[str, Any], ...]: + return ( + _entry( + "PROMPT_IMPACT_ANALYSIS", + module="part_01", + business_scenario="需求变更影响分析", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含影响结论、风险、建议。", + ), + _entry( + "PROMPT_TASK_BREAKDOWN", + module="part_01", + business_scenario="任务拆解基础片段", + input_contract=FRAGMENT_INPUT, + output_contract=FRAGMENT_OUTPUT, + ), + _entry( + "PROMPT_STRATEGIC_ANALYSIS_HEADER", + module="part_01", + business_scenario="战略分析上下文片段", + input_contract=FRAGMENT_INPUT, + output_contract=FRAGMENT_OUTPUT, + ), + _entry( + "PROMPT_RAG_CONTEXT_HEADER", + module="part_01", + business_scenario="RAG 上下文 header 片段", + input_contract=FRAGMENT_INPUT, + output_contract=FRAGMENT_OUTPUT, + ), + _entry( + "PROMPT_RAG_CONTEXT_ITEM", + module="part_01", + business_scenario="RAG 上下文 item 片段", + input_contract=FRAGMENT_INPUT, + output_contract=FRAGMENT_OUTPUT, + ), + _entry( + "PROMPT_CODE_AUDIT", + module="part_01", + business_scenario="代码审计", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含问题、证据、风险与修复建议。", + ), + _entry( + "PROMPT_CONTEXT_INSIGHTS", + module="part_01", + business_scenario="上下文洞察", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含关键洞察与下一步建议。", + ), + _entry( + "PROMPT_FEISHU_REQUIREMENT_ANALYSIS", + module="part_01", + business_scenario="飞书需求解析", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含需求抽取、风险与建议。", + call_sites=FEISHU_EXTRACT_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_HUMANIZED_REPLY", + module="part_01", + business_scenario="飞书回复润色", + input_contract=QA_INPUT, + output_contract=QA_OUTPUT, + call_sites=FEISHU_REPLY_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION", + module="part_01", + business_scenario="项目信号分类", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含分类、优先级与推荐动作。", + call_sites=FEISHU_SIGNAL_CLASSIFY_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_LEVEL_CLASSIFICATION", + module="part_01", + business_scenario="飞书消息层级分类", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含层级与判断原因。", + call_sites=FEISHU_EXTRACT_CALL_SITES, + ), + ) + + +def _part_02_entries() -> tuple[dict[str, Any], ...]: + return ( + _entry( + "PROMPT_FEISHU_SIGNAL_EXTRACTION", + module="part_02", + business_scenario="飞书信号结构化抽取", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含标题、摘要、优先级、匹配项目和建议动作。", + call_sites=FEISHU_EXTRACT_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_PROJECT_PREFILL", + module="part_02", + business_scenario="飞书项目预填", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含项目名、描述、负责人、时间线与置信度。", + call_sites=FEISHU_EXTRACT_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_TASK_ORCHESTRATION", + module="part_02", + business_scenario="飞书任务编排与负责人建议", + input_contract="输入 project_json / charter_json / signal_json / members_json。", + output_contract="输出 JSON 对象,包含任务列表、负责人建议与置信度。", + call_sites=FEISHU_PIPELINE_CALL_SITES, + ), + _entry( + "PROMPT_SIGNAL_SOURCE_ONBOARDING", + module="part_02", + business_scenario="信号源接入规划", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含是否接入、信号源信息、角色分工与执行指令。", + call_sites=SIGNAL_SOURCE_CALL_SITES, + ), + _entry( + "PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION", + module="part_02", + business_scenario="信号源接入 worker 执行", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含交付物、风险与下一步。", + call_sites=SIGNAL_SOURCE_CALL_SITES, + ), + _entry( + "PROMPT_AI_PMO_INTENT_CLASSIFICATION", + module="part_02", + business_scenario="AI PMO 意图识别", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含 family / action / intent / confidence。", + call_sites=PMO_INTENT_CALL_SITES, + ), + _entry( + "PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS", + module="part_02", + business_scenario="AI PMO 批量信号分析", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含 signals 数组。", + call_sites=PMO_BATCH_CALL_SITES, + ), + _entry( + "PROMPT_DRAFT_GENERATOR", + module="part_02", + business_scenario="立项草案生成", + input_contract="输入 request / project / active_projects / similar_projects / charter / members / recent_signals。", + output_contract="输出 JSON 对象,包含项目草案、重复建设判断与风险。", + call_sites=DRAFT_GENERATOR_CALL_SITES, + ), + _entry( + "PROMPT_RAG_INTENT_CLASSIFICATION", + module="part_02", + business_scenario="RAG 意图识别", + input_contract=QA_INPUT, + output_contract="输出 JSON 对象,包含 intent / confidence / reason。", + call_sites=RAG_INTENT_CALL_SITES, + ), + ) + + +def _part_03_entries() -> tuple[dict[str, Any], ...]: + return ( + _entry( + "PROMPT_RAG_ANSWER", + module="part_03", + business_scenario="RAG 问答生成", + input_contract=QA_INPUT, + output_contract=QA_OUTPUT, + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_DECISION_QA", + module="part_03", + business_scenario="决策类问答", + input_contract=QA_INPUT, + output_contract="输出自然语言结论,必要时包含取舍理由。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_RAG_DB_PROJECT_QA", + module="part_03", + business_scenario="项目知识库问答", + input_contract=JSON_INPUT, + output_contract=QA_OUTPUT, + call_sites=RAG_DB_CALL_SITES, + ), + _entry( + "PROMPT_GENERATE_CHARTER", + module="part_03", + business_scenario="宪章草案生成", + input_contract="输入 request / project / active_projects / similar_projects / signal_context。", + output_contract="输出 JSON 对象,包含项目宪章草案与风险。", + call_sites=("backend/app/services/ai_service_core/mixin_business_tasks.py", "backend/app/services/sstc/charter_prefill_utils.py"), + ), + _entry( + "PROMPT_DECOMPOSE_TASKS", + module="part_03", + business_scenario="任务拆解", + input_contract="输入 request / project / charter / modules 的结构化上下文。", + output_contract="输出 JSON 数组,包含任务名称、描述、工时、优先级和技能标签。", + call_sites=("backend/app/services/ai_service_core/mixin_business_tasks.py", "backend/app/api/tasks.py", "backend/app/api/sstc_api/tasks_ai_breakdown.py"), + ), + _entry( + "PROMPT_RECOMMEND_ASSIGNEES", + module="part_03", + business_scenario="负责人推荐", + input_contract=JSON_INPUT, + output_contract="输出 JSON 数组,包含候选人、匹配理由和负载信息。", + call_sites=AI_BUSINESS_TASKS_CALL_SITES, + ), + _entry( + "PROMPT_ALIGNMENT_SCORE", + module="part_03", + business_scenario="对齐度评分", + input_contract=SCORE_INPUT, + output_contract=SCORE_OUTPUT, + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + _entry( + "PROMPT_COGNITIVE_CONFLICTS", + module="part_03", + business_scenario="认知冲突识别", + input_contract=SCORE_INPUT, + output_contract=SCORE_OUTPUT, + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + _entry( + "PROMPT_EVALUATE_MODEL_REQUIREMENT", + module="part_03", + business_scenario="模型需求评估", + input_contract=SCORE_INPUT, + output_contract=SCORE_OUTPUT, + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + _entry( + "PROMPT_ANALYZE_CAPABILITY_GAP", + module="part_03", + business_scenario="能力缺口分析", + input_contract=SCORE_INPUT, + output_contract=SCORE_OUTPUT, + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + _entry( + "PROMPT_ANALYZE_COST_ROI", + module="part_03", + business_scenario="成本收益分析", + input_contract=SCORE_INPUT, + output_contract=SCORE_OUTPUT, + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + ) + + +def _part_04_entries() -> tuple[dict[str, Any], ...]: + return ( + _entry( + "PROMPT_ANALYZE_COST_OPTIMIZATION", + module="part_04", + business_scenario="成本优化分析", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含降级、缓存、批处理与节省测算。", + call_sites=AI_BUSINESS_ANALYSIS_CALL_SITES, + ), + _entry( + "PROMPT_ASSESS_MODEL_UPDATE_IMPACT", + module="part_04", + business_scenario="模型升级影响评估", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含成本变化、兼容风险与建议。", + call_sites=("backend/app/services/model/model_capability/service.py",), + ), + _entry( + "PROMPT_EXPERIMENT_SYNC_DECISION", + module="part_04", + business_scenario="实验结果同步决策", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含置信度、是否自动同步与原因。", + call_sites=("backend/app/services/ai_pmo/ai_pmo_cadence/mixin_builders.py",), + ), + _entry( + "PROMPT_SKILL_SCORE_UPDATE", + module="part_04", + business_scenario="技能评分更新", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含技能增量列表。", + call_sites=SKILL_SCORE_CALL_SITES, + ), + _entry( + "PROMPT_WORKLOAD_OPTIMIZATION", + module="part_04", + business_scenario="任务负载优化", + input_contract=JSON_INPUT, + output_contract="输出 JSON 对象,包含重分配建议。", + call_sites=WORKLOAD_CALL_SITES, + ), + _entry( + "PROMPT_ANALYZE_FOOD_CALORIES", + module="part_04", + business_scenario="食物热量估算", + input_contract=QA_INPUT, + output_contract="输出 JSON 对象,包含食物名、热量、营养和不确定因素。", + call_sites=RECIPE_CALL_SITES, + ), + _entry( + "PROMPT_SIGNAL_PLANNER", + module="part_04", + business_scenario="信号规划 / 任务编排建议", + input_contract="输入 signal / project / charter_json / recent_signals / member_profiles。", + output_contract="输出 JSON 对象,包含 summary、priority、负责人建议、执行计划和风险。", + call_sites=SIGNAL_PLANNER_CALL_SITES, + ), + ) + + +def _scene_entries() -> tuple[dict[str, Any], ...]: + return ( + _entry( + "PROMPT_FEISHU_DIRECT_QA", + module="part_scenes_01", + business_scenario="飞书直接问答", + input_contract="输入 question / system_snapshot / user_snapshot / context_blocks。", + output_contract=QA_OUTPUT, + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_FEISHU_MIXED_QA", + module="part_scenes_02", + business_scenario="飞书混合问答", + input_contract="输入 project_id / message / question_part / signal_part / system_snapshot / context_blocks。", + output_contract="输出先结论后入库说明的自然语言答复。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_PROJECT_STATUS_QA", + module="part_scenes_03", + business_scenario="项目状态问答", + input_contract="输入 question / project_id / system_snapshot / user_snapshot / context_blocks。", + output_contract="输出项目状态结论、进度与下一步建议。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_MEMBER_WORKLOAD_QA", + module="part_scenes_04", + business_scenario="成员负载问答", + input_contract="输入 question / system_snapshot / user_snapshot / context_blocks。", + output_contract="输出成员负载与推荐建议。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_AI_PMO_QA", + module="part_scenes_05", + business_scenario="AI PMO 问答", + input_contract="输入 question / project_id / system_snapshot / user_snapshot / context_blocks。", + output_contract="输出 AI PMO 视角的结论与建议。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_EXTERNAL_TREND_QA", + module="part_scenes_06", + business_scenario="外部趋势问答", + input_contract="输入 question / project_id / system_snapshot / user_snapshot / context_blocks。", + output_contract="输出行业/市场趋势判断与建议。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + _entry( + "PROMPT_INDUSTRY_TREND_QA", + module="part_scenes_06", + business_scenario="行业趋势问答", + input_contract="输入 question / project_id / system_snapshot / user_snapshot / context_blocks。", + output_contract="输出行业趋势判断与建议。", + call_sites=DEEP_GUARD_CALL_SITES, + ), + ) + + +PROMPT_REGISTRY: tuple[dict[str, Any], ...] = ( + *_part_01_entries(), + *_part_02_entries(), + *_part_03_entries(), + *_part_04_entries(), + *_scene_entries(), +) + +PROMPT_REGISTRY_MAP: dict[str, dict[str, Any]] = { + entry["prompt_key"]: entry for entry in PROMPT_REGISTRY +} + + +def get_prompt_registry() -> list[dict[str, Any]]: + return [dict(entry) for entry in PROMPT_REGISTRY] + + +def get_prompt_registry_entry(prompt_key: str) -> dict[str, Any] | None: + key = str(prompt_key or "").strip() + if not key: + return None + entry = PROMPT_REGISTRY_MAP.get(key) + return dict(entry) if entry is not None else None + + +def iter_prompt_registry_keys() -> tuple[str, ...]: + return tuple(entry["prompt_key"] for entry in PROMPT_REGISTRY) + + +__all__ = [ + "PROMPT_CATALOG_EXPORTS", + "PROMPT_REGISTRY", + "PROMPT_REGISTRY_MAP", + "get_prompt_registry", + "get_prompt_registry_entry", + "iter_prompt_registry_keys", +] diff --git a/backend/app/config/ai_prompts.py b/backend/app/config/ai_prompts.py index 686b037a..a67226c1 100644 --- a/backend/app/config/ai_prompts.py +++ b/backend/app/config/ai_prompts.py @@ -7,7 +7,60 @@ AI Prompt 集中管理 使用 .format() 或 f-string 时需保证占位符与调用处一致。 """ -from .ai_prompts_catalog import * +from .ai_prompts_catalog import ( + PROMPT_IMPACT_ANALYSIS, + PROMPT_TASK_BREAKDOWN, + PROMPT_STRATEGIC_ANALYSIS_HEADER, + PROMPT_RAG_CONTEXT_HEADER, + PROMPT_RAG_CONTEXT_ITEM, + PROMPT_CODE_AUDIT, + PROMPT_CONTEXT_INSIGHTS, + PROMPT_FEISHU_REQUIREMENT_ANALYSIS, + PROMPT_FEISHU_HUMANIZED_REPLY, + PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION, + PROMPT_FEISHU_LEVEL_CLASSIFICATION, + PROMPT_FEISHU_SIGNAL_EXTRACTION, + PROMPT_FEISHU_PROJECT_PREFILL, + PROMPT_FEISHU_TASK_ORCHESTRATION, + PROMPT_SIGNAL_SOURCE_ONBOARDING, + PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION, + PROMPT_AI_PMO_INTENT_CLASSIFICATION, + PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS, + PROMPT_DRAFT_GENERATOR, + PROMPT_RAG_INTENT_CLASSIFICATION, + PROMPT_RAG_ANSWER, + PROMPT_DECISION_QA, + PROMPT_RAG_DB_PROJECT_QA, + PROMPT_GENERATE_CHARTER, + PROMPT_DECOMPOSE_TASKS, + PROMPT_RECOMMEND_ASSIGNEES, + PROMPT_ALIGNMENT_SCORE, + PROMPT_COGNITIVE_CONFLICTS, + PROMPT_EVALUATE_MODEL_REQUIREMENT, + PROMPT_ANALYZE_CAPABILITY_GAP, + PROMPT_ANALYZE_COST_ROI, + PROMPT_ANALYZE_COST_OPTIMIZATION, + PROMPT_ASSESS_MODEL_UPDATE_IMPACT, + PROMPT_EXPERIMENT_SYNC_DECISION, + PROMPT_SKILL_SCORE_UPDATE, + PROMPT_WORKLOAD_OPTIMIZATION, + PROMPT_ANALYZE_FOOD_CALORIES, + PROMPT_SIGNAL_PLANNER, + PROMPT_FEISHU_DIRECT_QA, + PROMPT_FEISHU_MIXED_QA, + PROMPT_PROJECT_STATUS_QA, + PROMPT_MEMBER_WORKLOAD_QA, + PROMPT_AI_PMO_QA, + PROMPT_EXTERNAL_TREND_QA, + PROMPT_INDUSTRY_TREND_QA, +) +from .ai_prompt_registry import ( + PROMPT_REGISTRY, + PROMPT_REGISTRY_MAP, + get_prompt_registry, + get_prompt_registry_entry, + iter_prompt_registry_keys, +) def build_default_role_prompt_bundle() -> dict: """返回默认角色 Prompt 配置(每次调用返回新对象)。""" @@ -17,25 +70,28 @@ def build_default_role_prompt_bundle() -> dict: "roles": [ { "role_key": "pm_dispatcher", - "role_name": "产品经理(任务分派)", - "role_description": "负责澄清需求并给出可执行任务分派建议", + "role_name": "产品经理(立项评审)", + "role_description": "负责查重、评审、起草立项建议并给出确认前的产品方案", "enabled": True, "system_prompt": """ -你是 PMFlow 的任务分派产品经理。 +你是 PMFlow 的产品经理助手。 职责定位: -1. 先理解需求背景,再输出执行计划。 -2. 重点给出可落地的任务分派建议,避免空泛讨论。 -3. 回复风格要专业、直接、有人味,优先给结论。 +1. 先判断是否已有同名或相似项目,再给出是否复用、合并或新建的建议。 +2. 先输出产品评审结论,再展开项目定位、MVP、前景、需求清单、风险与负责人建议。 +3. 回复风格要专业、清晰、像真实产品经理评审,允许给出取舍与备选方案。 +4. 只有在用户明确确认后,才进入写入或继续执行流程。 输出要求: -1. 先用 1-2 句话复述需求并确认目标。 -2. 再给 3 条以内“下一步动作”,每条都要可执行。 -3. 若信息不足,只提关键澄清问题(最多 2 个),不要臆测。 -4. 如果涉及风险、依赖或排期冲突,要明确提醒。 +1. 先用 1-2 句话复述需求并判断立项方向。 +2. 再给出是否有同名/相似项目的判断。 +3. 再展开项目定位、MVP、前景、需求清单、风险与待补充信息。 +4. 如果信息不足,只提关键澄清问题(最多 3 个),不要臆测。 +5. 如果涉及风险、依赖、排期冲突或重复建设,要明确提醒。 禁止项: - 不要输出与项目无关的泛化建议。 +- 不要只给机械化模板,不要把评审写成简单入库文案。 - 不要使用命令式生硬语气。 - 不要编造不存在的数据或结论。 """.strip(), @@ -101,6 +157,7 @@ __all__ = [ "PROMPT_DRAFT_GENERATOR", "PROMPT_RAG_INTENT_CLASSIFICATION", "PROMPT_RAG_ANSWER", + "PROMPT_DECISION_QA", "PROMPT_RAG_DB_PROJECT_QA", "PROMPT_GENERATE_CHARTER", "PROMPT_DECOMPOSE_TASKS", @@ -123,5 +180,11 @@ __all__ = [ "PROMPT_MEMBER_WORKLOAD_QA", "PROMPT_AI_PMO_QA", "PROMPT_EXTERNAL_TREND_QA", + "PROMPT_INDUSTRY_TREND_QA", + "PROMPT_REGISTRY", + "PROMPT_REGISTRY_MAP", + "get_prompt_registry", + "get_prompt_registry_entry", + "iter_prompt_registry_keys", "build_default_role_prompt_bundle", ] diff --git a/backend/app/config/ai_prompts_catalog/__init__.py b/backend/app/config/ai_prompts_catalog/__init__.py index 6daddc2b..3686b8a6 100644 --- a/backend/app/config/ai_prompts_catalog/__init__.py +++ b/backend/app/config/ai_prompts_catalog/__init__.py @@ -2,14 +2,14 @@ from __future__ import annotations from .part_01 import PROMPT_IMPACT_ANALYSIS, PROMPT_TASK_BREAKDOWN, PROMPT_STRATEGIC_ANALYSIS_HEADER, PROMPT_RAG_CONTEXT_HEADER, PROMPT_RAG_CONTEXT_ITEM, PROMPT_CODE_AUDIT, PROMPT_CONTEXT_INSIGHTS, PROMPT_FEISHU_REQUIREMENT_ANALYSIS, PROMPT_FEISHU_HUMANIZED_REPLY, PROMPT_FEISHU_PROJECT_SIGNAL_CLASSIFICATION, PROMPT_FEISHU_LEVEL_CLASSIFICATION from .part_02 import PROMPT_FEISHU_SIGNAL_EXTRACTION, PROMPT_FEISHU_PROJECT_PREFILL, PROMPT_FEISHU_TASK_ORCHESTRATION, PROMPT_SIGNAL_SOURCE_ONBOARDING, PROMPT_AI_WORKER_SIGNAL_SOURCE_EXECUTION, PROMPT_AI_PMO_INTENT_CLASSIFICATION, PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS, PROMPT_DRAFT_GENERATOR, PROMPT_RAG_INTENT_CLASSIFICATION -from .part_03 import PROMPT_RAG_ANSWER, PROMPT_RAG_DB_PROJECT_QA, PROMPT_GENERATE_CHARTER, PROMPT_DECOMPOSE_TASKS, PROMPT_RECOMMEND_ASSIGNEES, PROMPT_ALIGNMENT_SCORE, PROMPT_COGNITIVE_CONFLICTS, PROMPT_EVALUATE_MODEL_REQUIREMENT, PROMPT_ANALYZE_CAPABILITY_GAP, PROMPT_ANALYZE_COST_ROI +from .part_03 import PROMPT_RAG_ANSWER, PROMPT_DECISION_QA, PROMPT_RAG_DB_PROJECT_QA, PROMPT_GENERATE_CHARTER, PROMPT_DECOMPOSE_TASKS, PROMPT_RECOMMEND_ASSIGNEES, PROMPT_ALIGNMENT_SCORE, PROMPT_COGNITIVE_CONFLICTS, PROMPT_EVALUATE_MODEL_REQUIREMENT, PROMPT_ANALYZE_CAPABILITY_GAP, PROMPT_ANALYZE_COST_ROI from .part_04 import PROMPT_ANALYZE_COST_OPTIMIZATION, PROMPT_ASSESS_MODEL_UPDATE_IMPACT, PROMPT_EXPERIMENT_SYNC_DECISION, PROMPT_SKILL_SCORE_UPDATE, PROMPT_WORKLOAD_OPTIMIZATION, PROMPT_ANALYZE_FOOD_CALORIES, PROMPT_SIGNAL_PLANNER from .part_scenes_01 import PROMPT_FEISHU_DIRECT_QA from .part_scenes_02 import PROMPT_FEISHU_MIXED_QA from .part_scenes_03 import PROMPT_PROJECT_STATUS_QA from .part_scenes_04 import PROMPT_MEMBER_WORKLOAD_QA from .part_scenes_05 import PROMPT_AI_PMO_QA -from .part_scenes_06 import PROMPT_EXTERNAL_TREND_QA +from .part_scenes_06 import PROMPT_EXTERNAL_TREND_QA, PROMPT_INDUSTRY_TREND_QA __all__ = [ "PROMPT_IMPACT_ANALYSIS", @@ -33,6 +33,7 @@ __all__ = [ "PROMPT_DRAFT_GENERATOR", "PROMPT_RAG_INTENT_CLASSIFICATION", "PROMPT_RAG_ANSWER", + "PROMPT_DECISION_QA", "PROMPT_RAG_DB_PROJECT_QA", "PROMPT_GENERATE_CHARTER", "PROMPT_DECOMPOSE_TASKS", @@ -55,4 +56,5 @@ __all__ = [ "PROMPT_MEMBER_WORKLOAD_QA", "PROMPT_AI_PMO_QA", "PROMPT_EXTERNAL_TREND_QA", + "PROMPT_INDUSTRY_TREND_QA", ] diff --git a/backend/app/config/ai_prompts_catalog/part_02.py b/backend/app/config/ai_prompts_catalog/part_02.py index 8efecd84..3796668d 100644 --- a/backend/app/config/ai_prompts_catalog/part_02.py +++ b/backend/app/config/ai_prompts_catalog/part_02.py @@ -35,16 +35,18 @@ PROMPT_FEISHU_PROJECT_PREFILL = """ {{ "suggested_name": "项目名", "suggested_description": "1-2句描述", - "suggested_owner": "负责人名或角色", - "suggested_timeline": "截止日期或周期", + "suggested_owner": "负责人名或角色;不明确时返回 null", + "suggested_timeline": "截止日期或周期;不明确时返回 null", "confidence": 0.0-1.0, "reasoning": ["依据1","依据2","依据3"] }} 约束: 1. reasoning 必须是 3 条简短 bullet。 -2. 没有明确字段时可合理推断并在 reasoning 说明。 -3. 只输出 JSON。 +2. 只有在消息里明确出现负责人、角色、截止时间、周期时才填写对应字段。 +3. 不要为了“填满字段”去猜负责人或时间线;不明确时返回 null。 +4. confidence 低于 0.6 时,suggested_owner 和 suggested_timeline 优先返回 null。 +5. 只输出 JSON。 """ PROMPT_FEISHU_TASK_ORCHESTRATION = """ @@ -53,6 +55,7 @@ PROMPT_FEISHU_TASK_ORCHESTRATION = """ 输入: - project_json: {project_json} +- charter_json: {charter_json} - signal_json: {signal_json} - members_json: {members_json} @@ -74,11 +77,13 @@ PROMPT_FEISHU_TASK_ORCHESTRATION = """ }} 约束: -1. 任务数量 2-8 条,不要空数组。 -2. 优先覆盖“立即可执行”的任务,避免泛化管理话术。 -3. `recommended_assignee_id` 必须来自 members_json.user_id;无法确认时填 null。 -4. 推荐负责人时同时考虑技能匹配与 workload_ratio,避免把任务继续压给高负载成员。 -5. 只输出 JSON,不要 Markdown,不要解释。 +1. 宪章是最高业务约束,任务必须围绕 charter_json 中的 objectives / scope / success_criteria / constraints 展开。 +2. 如果 charter_json 为空,先依据 project_json 与 signal_json 做最小可执行拆解,不要自行编造宪章。 +3. 任务数量 2-8 条,不要空数组。 +4. 优先覆盖“立即可执行”的任务,避免泛化管理话术。 +5. `recommended_assignee_id` 必须来自 members_json.user_id;无法确认时填 null。 +6. 推荐负责人时同时考虑技能匹配与 workload_ratio,避免把任务继续压给高负载成员。 +7. 只输出 JSON,不要 Markdown,不要解释。 """ PROMPT_SIGNAL_SOURCE_ONBOARDING = """ @@ -166,13 +171,41 @@ PROMPT_AI_PMO_INTENT_CLASSIFICATION = """ 只输出一个 JSON 对象(不要解释、不要 Markdown): {{ - "family": "query|project_create|task|project_signal|signal_source", - "action": "answer|create|discover|onboard", - "intent": "query.read_only|project.create|task.create|signal.create|signal_source.onboard|followup.continue", + "family": "query|project_create|project|task|personnel|knowledge|notification|project_signal|signal_source|followup", + "action": "answer|create|update|archive|reopen|delete|assign|prioritize|status_transition|role_update|workload_update|availability_update|doc_upsert|chunk_upsert|link_upsert|reindex|ack|close|discover|onboard|continue|confirm", + "intent": "query.read_only|project.create|project.update|project.archive|project.reopen|project.delete|task.create|task.update|task.assign|task.prioritize|task.status_transition|task.delete|personnel.role_update|personnel.workload_update|personnel.availability_update|knowledge.doc_upsert|knowledge.chunk_upsert|knowledge.link_upsert|knowledge.reindex|notification.create|notification.ack|notification.close|signal.create|signal_source.onboard|followup.continue", "confidence": 0.0, "project_id": 0, "requested_count": 1, "requires_confirmation": false, + "parameters": { + "project_id": 0, + "project_name": "可选", + "task_id": 0, + "task_title": "可选", + "user_id": 0, + "user_name": "可选", + "notification_id": 0, + "doc_id": 0, + "doc_title": "可选", + "chunk_id": 0, + "scope": "tenant|project|document", + "title": "可选", + "description": "可选", + "status": "可选", + "priority": "可选", + "assignee_id": 0, + "role": "可选", + "max_weekly_hours": 0, + "content": "可选", + "chunk_text": "可选", + "source_type": "可选", + "source_id": 0, + "target_type": "可选", + "target_id": 0, + "url": "可选", + "path": "可选" + }, "reasoning": "一句话简述判定原因" }} @@ -180,22 +213,28 @@ PROMPT_AI_PMO_INTENT_CLASSIFICATION = """ 1. query.read_only 定义:仅当用户是在“查询 / 了解 / 分析已有内容”时,才判定为 query.read_only。 2. 若消息同时包含“新增 / 创建 / 记录 / 提交”等写操作意图,优先判定为对应写操作意图,不要误判为 query.read_only。 3. project.create:当用户明确表达“创建一个新项目”时,返回 family=project_create, action=create, intent=project.create。 -4. task.create:当用户明确表达“创建/新增/安排任务”时,返回 family=task, action=create, intent=task.create。 -5. signal.create 定义:当用户想创建 / 新增 / 录入 / 提交一条新的需求、功能、信号、想法或问题时,返回 family=project_signal, action=create, intent=signal.create。核心特征是“要产生一条新记录”,不管用户用什么词表达。 -6. signal.create few-shot: +4. project.update / archive / reopen / delete:当用户明确表达“修改项目 / 归档项目 / 重新打开项目 / 删除项目”时,返回 family=project, action=对应动作, intent=对应 operation_code。 +5. task.create:当用户明确表达“创建/新增/安排任务”时,返回 family=task, action=create, intent=task.create。 +6. task.update / assign / prioritize / status_transition / delete:当用户明确表达“改任务内容 / 指派负责人 / 调整优先级 / 切换状态 / 删除任务”时,返回 family=task, action=对应动作, intent=对应 operation_code。 +7. personnel.*:当用户明确表达“更新人员角色 / 工作量 / 可用性”时,返回 family=personnel, action=对应动作, intent=对应 operation_code。 +8. knowledge.*:当用户明确表达“新增/更新文档、分片、链接或重建知识索引”时,返回 family=knowledge, action=对应动作, intent=对应 operation_code。 +9. notification.*:当用户明确表达“创建通知 / 确认通知 / 关闭通知”时,返回 family=notification, action=对应动作, intent=对应 operation_code。 +10. signal.create 定义:当用户想创建 / 新增 / 录入 / 提交一条新的需求、功能、信号、想法或问题时,返回 family=project_signal, action=create, intent=signal.create。核心特征是“要产生一条新记录”,不管用户用什么词表达。 +11. signal.create few-shot: - “给XX项目加个需求” -> signal.create - “新增一个功能需求” -> signal.create - “帮我记录这个想法” -> signal.create - “提个需求:支持导出” -> signal.create -7. project_signal discover:当用户表达“先分析候选 / 拆分候选并确认”时,返回 family=project_signal, action=discover, intent=signal.create, requires_confirmation=true。 -8. signal_source.onboard:当用户表达“信号源 / 来源 / webhook / rss / github 接入”时,返回 family=signal_source, action=onboard, intent=signal_source.onboard。 -9. 若 session_last_intent=draft_pending_confirm 且消息含“确认/好的/就这样/行/ok”,优先返回 followup.continue(用于草稿确认执行)。 -10. 若 session_last_intent=draft_pending_confirm 且消息含“修改/改成/换成”,返回 followup.continue(用于草稿重起草)。 -11. 若 session_last_intent=draft_pending_confirm 且消息含“取消/不要/算了”,返回 followup.continue(用于草稿取消)。 -12. 若消息为短确认语气,可结合 session_last_intent;无把握时返回 query.answer。 -13. project_id 不确定返回 null;requested_count 默认 1(仅 project_signal 有效,范围 1-5)。 -14. family/action/intent 必须严格使用上面 JSON 示例给定的枚举值,不能自造。 -15. 若请求 DDL(建表/改表/删表),必须返回 query.answer + query.read_only,reasoning 含 ddl_api_required。 +12. project_signal discover:当用户表达“先分析候选 / 拆分候选并确认”时,返回 family=project_signal, action=discover, intent=signal.create, requires_confirmation=true。 +13. signal_source.onboard:当用户表达“信号源 / 来源 / webhook / rss / github 接入”时,返回 family=signal_source, action=onboard, intent=signal_source.onboard。 +14. 仅当消息明确 reply_to 一条挂起草稿(reply_to_message_id 命中待确认草稿)且含“确认/好的/就这样/行/ok”,优先返回 followup.continue(用于草稿确认执行)。 +15. 仅当消息明确 reply_to 一条挂起草稿且含“修改/改成/换成”,返回 followup.continue(用于草稿重起草)。 +16. 仅当消息明确 reply_to 一条挂起草稿且含“取消/不要/算了”,返回 followup.continue(用于草稿取消)。 +17. 若没有 reply_to 挂起草稿锚点,不要因为确认词直接进入 followup.continue;无把握时返回 query.read_only。 +18. project_id 不确定返回 null;requested_count 默认 1(仅 project_signal 有效,范围 1-5)。 +19. family/action/intent 必须严格使用上面 JSON 示例给定的枚举值,不能自造。 +20. 对所有写操作,parameters 必须尽可能填出已识别的 id / title / name / status / priority / content 等字段;不确定时返回 null,不要猜。 +21. 若请求 DDL(建表/改表/删表),必须返回 query.read_only,reasoning 含 ddl_api_required。 """ PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS = """ @@ -231,10 +270,12 @@ PROMPT_AI_PMO_SIGNAL_BATCH_ANALYSIS = """ PROMPT_DRAFT_GENERATOR = """ 首字符必须是 {{,末字符必须是 }}。 你是 PMFlow 的资深产品经理。 -收到一条新的业务需求,请起草分析报告。 +收到一条新的业务需求,请先按产品经理视角做立项评审,再起草详细方案。 输入: -- 项目信息:{project_title} - {project_description} +- 当前上下文项目:{project_title} - {project_description} +- 现有活跃项目(JSON):{active_projects_json} +- 候选相似项目(JSON):{similar_project_candidates_json} - 团队成员(按负载排序):{members_json} - 最近同类需求:{recent_signals} - 用户需求:{user_message} @@ -243,7 +284,12 @@ PROMPT_DRAFT_GENERATOR = """ 请输出 JSON,包含: - title: 语义化标题(10字以内,必须概括目标,不得复述原话) -- summary: 需求摘要(50字以内) +- summary: 项目定位与需求摘要(80字以内) +- similar_projects: 同名/相似项目列表,元素包含 project_id、title、difference、risk、reuse_suggestion +- mvp_items: MVP 建议数组,3-5 条 +- prospect_assessment: 前景判断(1-3 句) +- requirement_items: 需求清单数组,3-8 条 +- missing_info: 还缺的信息数组,0-5 条 - feasibility: 合理|待讨论|不合理 - feasibility_reason: 判断理由(30字以内) - recommended_assignee_id: 推荐成员ID @@ -257,10 +303,12 @@ PROMPT_DRAFT_GENERATOR = """ - 明确的功能需求且技术可实现 => 合理 - 需求模糊、边界冲突或依赖不清 => 待讨论 - 明显不可行或超出系统范围 => 不合理 -4. 推荐负责人优先选择负载低于 80% 的成员。 -5. 若所有成员负载都 >= 80%,推荐负载最低者,并在 recommended_assignee_reason 明确写出负载风险。 -6. followup_plan 最少 1 项,最多 3 项。 -7. 若无法确定推荐人,recommended_assignee_id 返回 null。 +4. 如果存在同名或高度相似项目,必须写出差异与复用/合并建议。 +5. MVP 建议优先写“最小可验证闭环”,不要写成泛化愿景。 +6. 推荐负责人优先选择负载低于 80% 的成员。 +7. 若所有成员负载都 >= 80%,推荐负载最低者,并在 recommended_assignee_reason 明确写出负载风险。 +8. followup_plan 最少 1 项,最多 3 项。 +9. 若无法确定推荐人,recommended_assignee_id 返回 null。 """.strip() PROMPT_RAG_INTENT_CLASSIFICATION = """ diff --git a/backend/app/config/ai_prompts_catalog/part_03.py b/backend/app/config/ai_prompts_catalog/part_03.py index 6ae50fb4..97173e32 100644 --- a/backend/app/config/ai_prompts_catalog/part_03.py +++ b/backend/app/config/ai_prompts_catalog/part_03.py @@ -34,6 +34,19 @@ User Snapshot(当前用户): {context_blocks} """ +PROMPT_DECISION_QA = """ +你是 PMFlow 的产品顾问,负责处理产品取舍、优先级、二选一和方案比较类问题。 + +规则: +1. 直接给出明确选择,不要回避,不要反问。 +2. 如果用户给出两个或多个选项,必须明确选一个。 +3. 不依赖数据库、历史记录或外部实时数据,基于常识与办公协作场景给出最稳妥建议即可。 +4. 先给结论,再给 1-2 条理由,回答要简洁。 +5. 不输出思考过程,不要使用“让我/我来/我先/我需要”开头。 + +问题:{question} +""" + PROMPT_RAG_DB_PROJECT_QA = """ 你是 PMFlow 的项目数据语义检索助手。 你会收到“用户问题”和“项目索引(JSON)”,请基于索引做语义匹配后回答。 @@ -61,10 +74,20 @@ PROMPT_RAG_DB_PROJECT_QA = """ """ PROMPT_GENERATE_CHARTER = """ -你是一位资深产品经理。根据以下市场/技术信号,生成一份结构化的需求宪章草案。 +首字符必须是 {{,末字符必须是 }}。 +你是一位 PMFlow 的资深产品经理。根据当前项目上下文与输入信号,生成一份结构化的需求宪章草案。 -信号内容: -{signal_content} +输入: +- 当前项目上下文:{project_context} +- 现有活跃项目(JSON):{active_projects_json} +- 信号内容:{signal_content} + +请先判断这条需求更适合作为: +1. 当前项目的延展; +2. 现有活跃项目的复用/合并; +3. 必须新建项目。 + +如果存在同名或高度相似项目,必须在背景、范围和风险里明确写出差异点、复用点与合并建议。 请输出 JSON 格式,包含以下字段: {{ @@ -81,7 +104,7 @@ PROMPT_GENERATE_CHARTER = """ """ PROMPT_DECOMPOSE_TASKS = """ -你是一位 AI 技术项目经理。根据以下需求宪章内容,拆解为可执行的任务列表。 +你是一位 AI 技术项目经理。根据以下需求宪章与项目上下文,拆解为可执行的任务列表。 项目 ID:{project_id}。 宪章内容: @@ -89,6 +112,12 @@ PROMPT_DECOMPOSE_TASKS = """ 请识别每个任务所需的 AI 开发技能标签(仅从以下系统技能中匹配:{available_skills})。 +规则: +1. 如果宪章内容是结构化 JSON,请优先读取其中的 `request` / `project` / `charter` / `modules` 字段。 +2. 优先围绕 `objectives`、`scope`、`success_criteria`、`constraints`、`milestones`、`risks` 拆解任务。 +3. 任务要尽量贴近可执行产出,不要输出空泛的管理话术。 +4. 如果上下文同时给出项目与宪章信息,以宪章约束为主,项目信息用于补充边界。 + 输出 JSON 格式: {{ "tasks": [ diff --git a/backend/app/config/ai_prompts_catalog/part_04.py b/backend/app/config/ai_prompts_catalog/part_04.py index e951e42e..b89c62a4 100644 --- a/backend/app/config/ai_prompts_catalog/part_04.py +++ b/backend/app/config/ai_prompts_catalog/part_04.py @@ -164,6 +164,7 @@ PROMPT_SIGNAL_PLANNER = """ - 项目ID:{project_id} - 项目标题:{project_title} - 项目归属判定:{project_scope} +- 宪章内容(JSON):{charter_json} - 最近相关信号(JSON):{recent_signals} - 项目成员能力画像(JSON):{member_profiles} - 当前时间(北京时间):{now_cn} @@ -186,10 +187,12 @@ PROMPT_SIGNAL_PLANNER = """ }} 规则: -1. 若无法确定负责人,assignee_user_id 返回 null,且在 assignee_reason 中说明原因。 -2. execution_plan 必须是 2-5 条可执行动作,不要空泛描述。 -3. confidence 范围 0-1,不能省略。 -4. 只输出 JSON,不要输出 Markdown 或解释文本。 +1. 宪章是最高业务约束。若 `charter_json` 非空,优先依据其中的 `objectives`、`scope`、`success_criteria`、`constraints`、`milestones`、`risks` 做规划。 +2. 若 `charter_json` 为空,则继续基于项目、近期信号与成员画像做最小可执行规划,不要编造不存在的宪章内容。 +3. 若无法确定负责人,assignee_user_id 返回 null,且在 assignee_reason 中说明原因。 +4. execution_plan 必须是 2-5 条可执行动作,不要空泛描述。 +5. confidence 范围 0-1,不能省略。 +6. 只输出 JSON,不要输出 Markdown 或解释文本。 """ __all__ = [ diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_01.py b/backend/app/config/ai_prompts_catalog/part_scenes_01.py index e4b3022a..940d9d04 100644 --- a/backend/app/config/ai_prompts_catalog/part_scenes_01.py +++ b/backend/app/config/ai_prompts_catalog/part_scenes_01.py @@ -1,15 +1,29 @@ from __future__ import annotations -PROMPT_FEISHU_DIRECT_QA = """ -你是 PMFlow 的项目管理助手。 -用户通过飞书直接提问。 +FEISHU_QA_RESPONSE_STYLE = """ +回复风格要求: +1. 先给结论,再给关键依据,最后再给下一步。 +2. 用正式、克制、面向协作的中文,像资深项目经理而不是聊天机器人。 +3. 如果能用数字、项目名、负责人、时间点表达,请直接写明。 +4. 若信息不足,必须明确说明缺少什么,最多列 3 项。 +5. 禁止自我介绍、寒暄、思考过程、数据库/模型/接口实现细节。 +""".strip() -规则: -1. 只基于给定上下文回答 -2. 证据不足直接说"信息不足,缺少:XXX" -3. 不输出思考过程,直接给答案 -4. 回答简洁,适合飞书消息阅读(不超过200字) -5. 涉及项目数据优先用数字说话 +PROMPT_FEISHU_DIRECT_QA = "\n\n".join( + [ + """ +你是 PMFlow 的飞书项目管理顾问,负责直接问答。 +""".strip(), + FEISHU_QA_RESPONSE_STYLE, + """ +回答要求: +1. 仅基于给定上下文回答,优先使用结构化数据。 +2. 如果问题涉及项目状态、进展、成员或任务,请优先给出可验证的事实和数字。 +3. 如果问题涉及选择、建议或判断,直接给出明确结论和 1-2 条理由。 +4. 信息不足时,直接写“信息不足,缺少:...”并列出关键缺失项。 +5. 控制在 220 字以内,适合飞书消息阅读。 +6. 不输出 Markdown、JSON 或思考过程。 +7. 禁止以“让我先/我来看看/我需要先”等句式开头。 当前项目ID:{project_id} 问题:{question} @@ -17,6 +31,8 @@ System Snapshot:{system_snapshot} User Snapshot:{user_snapshot} 最近会话:{conversation} 检索上下文:{context_blocks} -""".strip() +""".strip(), + ] +) -__all__ = ["PROMPT_FEISHU_DIRECT_QA"] +__all__ = ["FEISHU_QA_RESPONSE_STYLE", "PROMPT_FEISHU_DIRECT_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_02.py b/backend/app/config/ai_prompts_catalog/part_scenes_02.py index 75a83cbd..698f6ce9 100644 --- a/backend/app/config/ai_prompts_catalog/part_scenes_02.py +++ b/backend/app/config/ai_prompts_catalog/part_scenes_02.py @@ -1,20 +1,23 @@ from __future__ import annotations -PROMPT_FEISHU_MIXED_QA = """ -你是 PMFlow 的项目管理助手。 -用户消息同时包含问题和需求。 +from .part_scenes_01 import FEISHU_QA_RESPONSE_STYLE +PROMPT_FEISHU_MIXED_QA = "\n\n".join( + [ + """ +你是 PMFlow 的飞书项目管理顾问,负责处理“问题 + 需求”混合消息。 +""".strip(), + FEISHU_QA_RESPONSE_STYLE, + """ 处理规则: -1. 先回答问题部分(简洁,不超过100字) -2. 确认已收到需求部分 -3. 格式: - [回答] xxx - [已收到需求] xxx,已入库处理 - -禁止: -- 输出思考过程 -- 把需求部分当问题回答 -- 合并两部分混着说 +1. 先回答问题部分,使用 1 句到 2 句的专业结论,最多 100 字。 +2. 再单独确认需求部分,格式必须是: + [回答] ... + [已收到需求] ...,已入库处理 +3. [已收到需求] 里的内容要像项目经理确认记录,简洁、明确、可执行,不要空话。 +4. 若问题部分信息不足,先写缺失项,再给最稳妥的结论。 +5. 不要把需求部分当问题回答,也不要混成一段。 +6. 不输出 Markdown、JSON 或思考过程。 当前项目ID:{project_id} 消息:{message} @@ -22,6 +25,8 @@ PROMPT_FEISHU_MIXED_QA = """ 需求部分:{signal_part} System Snapshot:{system_snapshot} 检索上下文:{context_blocks} -""".strip() +""".strip(), + ] +) __all__ = ["PROMPT_FEISHU_MIXED_QA"] diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_03.py b/backend/app/config/ai_prompts_catalog/part_scenes_03.py index 78977a30..e8fdcc33 100644 --- a/backend/app/config/ai_prompts_catalog/part_scenes_03.py +++ b/backend/app/config/ai_prompts_catalog/part_scenes_03.py @@ -1,17 +1,20 @@ from __future__ import annotations PROMPT_PROJECT_STATUS_QA = """ -你是 PMFlow 的项目状态语义检索助手。 -用户关注项目进度、状态、完成率。 +你是 PMFlow 的项目状态分析助手。 +用户关注项目进度、状态、完成率、阶段和卡点。 规则: -1. 只基于给定上下文回答 -2. 禁止输出思考过程 -3. 用数字量化回答: - 完成率X%,已完成X个任务,进行中X个,待办X个 -4. 涉及多个项目时用列表格式 -5. 证据不足直接说: +1. 只基于给定上下文回答,不要编造项目现状。 +2. 先给当前结论,再给量化进展,再给卡点或下一步。 +3. 优先用数字回答: + 完成率X%,已完成X个任务,进行中X个,待办X个。 +4. 如果能识别出当前阶段,请明确写出“项目阶段”。 +5. 如果上下文里有负责人或关键角色,请补充“当前负责人”。 +6. 涉及多个项目时,用列表分别说明。 +7. 证据不足时直接写: 信息不足,缺少:XXX +8. 禁止输出思考过程、系统实现细节或空泛套话。 当前项目ID:{project_id} 问题:{question} diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_04.py b/backend/app/config/ai_prompts_catalog/part_scenes_04.py index 43f22ad5..f7eede9d 100644 --- a/backend/app/config/ai_prompts_catalog/part_scenes_04.py +++ b/backend/app/config/ai_prompts_catalog/part_scenes_04.py @@ -8,9 +8,10 @@ PROMPT_MEMBER_WORKLOAD_QA = """ 1. 优先使用 System Snapshot.team_members 和 User Snapshot 数据 2. 禁止默认返回项目列表 3. 涉及负载用百分比表示 -4. 推荐负责人时说明推荐理由 -5. 禁止输出思考过程 -6. 不超过150字 +4. 忽略与成员负载无关的项目列表和历史项目详情 +5. 推荐负责人时说明推荐理由 +6. 禁止输出思考过程 +7. 不超过150字 问题:{question} System Snapshot:{system_snapshot} diff --git a/backend/app/config/ai_prompts_catalog/part_scenes_06.py b/backend/app/config/ai_prompts_catalog/part_scenes_06.py index 8b7a1d07..108d6e68 100644 --- a/backend/app/config/ai_prompts_catalog/part_scenes_06.py +++ b/backend/app/config/ai_prompts_catalog/part_scenes_06.py @@ -7,7 +7,7 @@ PROMPT_EXTERNAL_TREND_QA = """ 规则: 1. 若有外部搜索结果优先使用 2. 若无外部数据明确说: - "当前未接入实时数据,以下基于训练数据(截止XXXX年)" + "当前未接入实时数据,以下基于通用行业认知。" 3. 结合项目实际给出应用建议: "对你们[项目名]的影响是..." 4. 禁止输出思考过程 @@ -22,4 +22,19 @@ PROMPT_EXTERNAL_TREND_QA = """ 外部搜索结果:{external_results} """.strip() -__all__ = ["PROMPT_EXTERNAL_TREND_QA"] +PROMPT_INDUSTRY_TREND_QA = """ +你是 PMFlow 的行业趋势助手。 +用户在询问某个行业、市场或产品方向的通用趋势判断。 + +规则: +1. 不要使用数据库、项目历史、用户历史或外部实时搜索结果。 +2. 如果用户问“最新”,必须明确说明当前未接入实时数据,只能基于通用认知给出趋势判断。 +3. 不要把回答强行绑定到某个项目。 +4. 先给结论,再给 2 条以内理由与建议。 +5. 控制总长度不超过 500 字,回答要适合飞书消息阅读。 +6. 不要输出思考过程,不要使用“让我/我来/我先/我需要”开头。 + +问题:{question} +""".strip() + +__all__ = ["PROMPT_EXTERNAL_TREND_QA", "PROMPT_INDUSTRY_TREND_QA"] diff --git a/backend/app/core/startup/model_probe.py b/backend/app/core/startup/model_probe.py index a7a9c482..277507a0 100644 --- a/backend/app/core/startup/model_probe.py +++ b/backend/app/core/startup/model_probe.py @@ -3,10 +3,12 @@ from __future__ import annotations import asyncio -import os import time +from copy import deepcopy from ..logging import get_logger +from ...core.database import AsyncSessionLocal +from ...services.model.model_config import build_tier_runtime logger = get_logger(__name__) @@ -18,11 +20,12 @@ def startup_log(message: str, **fields) -> None: async def probe_model_connectivity_on_startup() -> None: from ...services.llm_service import llm_service - low_provider = str(os.getenv("LLM_ROUTING_LOW_PROVIDER", os.getenv("LLM_PROVIDER", "glm")) or "").strip() or "glm" - low_model = str(os.getenv("LLM_ROUTING_LOW_MODEL", os.getenv("GLM_MODEL", "GLM-4.7")) or "").strip() or "GLM-4.7" - - high_provider = str(os.getenv("LLM_ROUTING_HIGH_PROVIDER", os.getenv("LLM_PROVIDER", "openai")) or "").strip() or "openai" - high_model = str(os.getenv("LLM_ROUTING_HIGH_MODEL", os.getenv("OPENAI_MODEL", "GLM-5")) or "").strip() or "GLM-5" + low_runtime = build_tier_runtime("low") + high_runtime = build_tier_runtime("high") + low_provider = str(low_runtime.get("provider") or "glm").strip() or "glm" + low_model = str(low_runtime.get("model") or "qwen-plus").strip() or "qwen-plus" + high_provider = str(high_runtime.get("provider") or "openai").strip() or "openai" + high_model = str(high_runtime.get("model") or "GLM-5").strip() or "GLM-5" probe_targets: list[tuple[str, str, str]] = [ ("LOW", low_provider, low_model), @@ -44,8 +47,33 @@ async def probe_model_connectivity_on_startup() -> None: for level, provider, model in deduped_targets: try: - runtime = llm_service._env_provider_runtime(provider) - runtime["model"] = model + runtime = None + async with AsyncSessionLocal() as db: + try: + resolved_function_name = ( + "classify_intent_ai_pmo_kernel" if level == "LOW" else "classify_intent_complex" + ) + runtime = await llm_service._resolve_engine_for_call( + db, + tenant_id=1, + function_name=resolved_function_name, + ) + except Exception as resolve_exc: + logger.warning( + "startup_model_probe_runtime_resolve_failed", + level=level, + provider=provider, + model=model, + error=str(resolve_exc), + ) + if not isinstance(runtime, dict) or not runtime: + runtime = llm_service._env_provider_runtime(provider) + if level == "LOW": + runtime["model"] = low_model + runtime["base_url"] = str(low_runtime.get("base_url") or runtime.get("base_url") or "") + elif level == "HIGH": + runtime["model"] = high_model + runtime["base_url"] = str(high_runtime.get("base_url") or runtime.get("base_url") or "") except Exception as exc: logger.error( "startup_model_probe_runtime_invalid", @@ -60,6 +88,8 @@ async def probe_model_connectivity_on_startup() -> None: started_at = time.perf_counter() ok = False error_text = "" + circuit_cls = type(llm_service) + saved_circuit_state = deepcopy(getattr(circuit_cls, "_provider_circuit_state", {})) try: result = await asyncio.wait_for( llm_service._chat_completion( @@ -85,9 +115,11 @@ async def probe_model_connectivity_on_startup() -> None: "startup_model_probe_failed", level=level, provider=provider, - model=model, - error=error_text, - ) + model=model, + error=error_text, + ) + finally: + circuit_cls._provider_circuit_state = saved_circuit_state elapsed = time.perf_counter() - started_at if ok: diff --git a/backend/app/models/feishu_reply_outbox.py b/backend/app/models/feishu_reply_outbox.py index 064b5ec1..3473e7ac 100644 --- a/backend/app/models/feishu_reply_outbox.py +++ b/backend/app/models/feishu_reply_outbox.py @@ -16,6 +16,7 @@ class FeishuReplyOutbox(Base): id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) trace_id: Mapped[Optional[str]] = mapped_column(VARCHAR(64), nullable=True, index=True) source_message_id: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True, index=True) + sent_message_id: Mapped[Optional[str]] = mapped_column(VARCHAR(128), nullable=True, index=True) chat_id: Mapped[str] = mapped_column(VARCHAR(128), nullable=False, index=True) message_text: Mapped[str] = mapped_column(TEXT, nullable=False) reply_hash: Mapped[Optional[str]] = mapped_column(VARCHAR(32), nullable=True, index=True) diff --git a/backend/app/schemas/common.py b/backend/app/schemas/common.py index 86f8f438..294d588d 100644 --- a/backend/app/schemas/common.py +++ b/backend/app/schemas/common.py @@ -16,6 +16,8 @@ class TaskBreakdownRequest(BaseModel): """任务拆解请求""" description: str + project_id: Optional[int] = None + charter_id: Optional[int] = None sync_to_plane: bool = False diff --git a/backend/app/schemas/world_model.py b/backend/app/schemas/world_model.py index f002aa23..09893898 100644 --- a/backend/app/schemas/world_model.py +++ b/backend/app/schemas/world_model.py @@ -91,6 +91,15 @@ class RecentSignalItem(TypedDict): signal_type: str +class ActiveProjectSnapshot(TypedDict): + id: int + name: str + title: str + description: str + status: str + owner_id: Optional[int] + + class DecisionContextSnapshot(TypedDict): captured_at: str project_id: Optional[int] @@ -116,6 +125,7 @@ class WorldModel(TypedDict): dependencies: DependencySummary recent_signals: list[RecentSignalItem] decision_context_snapshot: DecisionContextSnapshot + active_projects: NotRequired[list[ActiveProjectSnapshot]] __all__ = [ @@ -131,6 +141,7 @@ __all__ = [ "RiskSummary", "DependencySummary", "RecentSignalItem", + "ActiveProjectSnapshot", "DecisionContextSnapshot", "WorldModel", ] diff --git a/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py b/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py index c6c361f4..c07a197d 100644 --- a/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py +++ b/backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py @@ -81,6 +81,7 @@ class AdaptiveRAGAnswerRetrievalMixin: asked_via=asked_via, message_intent=message_intent, mixed_signal_part=mixed_signal_part, + query_type=query_type, ) used_sources = self._dedupe_sources(retrieved) if not used_sources: @@ -142,6 +143,7 @@ class AdaptiveRAGAnswerRetrievalMixin: asked_via=asked_via, message_intent=message_intent, mixed_signal_part=mixed_signal_part, + query_type=query_type, ) deep_sources = self._dedupe_sources(deep_retrieved) if not deep_sources: diff --git a/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py b/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py index 19d1c41f..b11e0e6c 100644 --- a/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py +++ b/backend/app/services/adaptive_rag_service/mixin_deep_guard_answer.py @@ -1,10 +1,52 @@ """Adaptive RAG mixin module.""" from __future__ import annotations +from ...config.ai_prompts import PROMPT_PROJECT_STATUS_QA from .shared import * class AdaptiveRAGDeepGuardAnswerMixin: + @staticmethod + def _compact_member_workload_user_snapshot(user_snapshot: Optional[dict[str, Any]]) -> dict[str, Any]: + if not isinstance(user_snapshot, dict): + return {} + allowed_keys = ( + "team_members", + "members", + "workloads", + "current_user", + "user_profile", + "load_summary", + "workload_summary", + ) + compact = {key: user_snapshot.get(key) for key in allowed_keys if key in user_snapshot} + return compact if compact else {} + + @staticmethod + def _filter_member_workload_context_blocks(context_blocks_text: str) -> str: + raw = str(context_blocks_text or "").strip() + if not raw or raw == "无": + return "无" + blocks = re.split(r"\n(?=\[(?:\d+|M)\])", raw) + keywords = ( + "team_members", + "workload", + "负载", + "成员", + "负责人", + "用户", + "工时", + "可用工时", + ) + kept: list[str] = [] + for block in blocks: + text = str(block or "").strip() + if not text: + continue + if any(keyword in text for keyword in keywords): + kept.append(text) + return "\n".join(kept) if kept else "无" + @staticmethod def _is_ai_pmo_process_entry(asked_via: Optional[str]) -> bool: channel = str(asked_via or "").strip().lower() @@ -20,6 +62,53 @@ class AdaptiveRAGDeepGuardAnswerMixin: def _contains_any_keyword(text: str, keywords: tuple[str, ...]) -> bool: return any(token in text for token in keywords) + @staticmethod + def _is_decision_choice_question(question: str) -> bool: + text = re.sub(r"\s+", "", str(question or "").lower()) + if not text: + return False + decision_markers = ( + "二选一", + "三选一", + "只允许一种", + "只能选一种", + "如果", + "你会", + "你选", + "优先", + "更应该", + "更适合", + "还是", + ) + if not any(marker in text for marker in decision_markers): + return False + return any(marker in text for marker in ("还是", "二选一", "三选一", "只允许一种", "只能选一种", "优先", "更应该", "更适合", "你会", "你选")) + + @staticmethod + def _is_project_status_question(question: str, query_type: Optional[str]) -> bool: + text = re.sub(r"\s+", "", str(question or "").lower()) + if not text: + return False + if str(query_type or "").strip().lower() == QUERY_TYPE_STATUS: + return True + return bool( + PROJECT_PROGRESS_RE.search(text) + or any( + token in text + for token in ( + "进展", + "进度", + "阶段", + "完成率", + "完成度", + "推进", + "里程碑", + "状态", + "情况", + ) + ) + ) + @staticmethod def _format_mixed_two_block_output( *, @@ -66,37 +155,23 @@ class AdaptiveRAGDeepGuardAnswerMixin: system_snapshot_text: str, user_snapshot_text: str, relevance_max: float, + query_type: Optional[str] = None, ) -> tuple[str, str]: normalized_question = re.sub(r"\s+", "", str(question or "").lower()) member_keywords = ("谁", "成员", "负责人", "负载") trend_keywords = ("最新", "行业", "市场", "前景", "趋势", "动态") if self._contains_any_keyword(normalized_question, member_keywords): + filtered_context_blocks = self._filter_member_workload_context_blocks(context_blocks_text) + compact_user_snapshot = self._compact_member_workload_user_snapshot(user_snapshot) + compact_user_snapshot_text = json.dumps(compact_user_snapshot or {}, ensure_ascii=False)[:6000] return ( "member_workload", PROMPT_MEMBER_WORKLOAD_QA.format( question=question, system_snapshot=system_snapshot_text or "{}", - user_snapshot=user_snapshot_text or "{}", - context_blocks=context_blocks_text or "无", - ).strip(), - ) - - if self._contains_any_keyword(normalized_question, trend_keywords): - project_context_text = json.dumps( - { - "project_id": project_id if project_id is not None else "unknown", - "system_snapshot": system_snapshot or {}, - "user_snapshot": user_snapshot or {}, - }, - ensure_ascii=False, - )[:16000] - return ( - "external_trend", - PROMPT_EXTERNAL_TREND_QA.format( - question=question, - project_context=project_context_text or "{}", - external_results=context_blocks_text or "无", + user_snapshot=compact_user_snapshot_text or "{}", + context_blocks=filtered_context_blocks, ).strip(), ) @@ -115,6 +190,68 @@ class AdaptiveRAGDeepGuardAnswerMixin: ).strip(), ) + if self._is_project_status_question(question, query_type): + return ( + "project_status", + PROMPT_PROJECT_STATUS_QA.format( + project_id=project_id if project_id is not None else "unknown", + question=question, + system_snapshot=system_snapshot_text or "{}", + user_snapshot=user_snapshot_text or "{}", + context_blocks=context_blocks_text or "无", + ).strip(), + ) + + if self._contains_any_keyword(normalized_question, trend_keywords): + explicit_project_context_markers = ( + token in normalized_question + for token in ("当前项目", "这个项目", "该项目", "项目里", "项目中", "你们项目", "我们项目") + ) + negated_project_context_markers = ( + "不要提项目", + "别提项目", + "不提项目", + "无项目", + "不涉及项目", + "不要绑定项目", + "不要关联项目", + "无需项目", + ) + has_project_context = any(explicit_project_context_markers) and not any( + token in normalized_question for token in negated_project_context_markers + ) + if has_project_context: + project_context_text = json.dumps( + { + "project_id": project_id if project_id is not None else "unknown", + "system_snapshot": system_snapshot or {}, + "user_snapshot": user_snapshot or {}, + }, + ensure_ascii=False, + )[:16000] + return ( + "external_trend", + PROMPT_EXTERNAL_TREND_QA.format( + question=question, + project_context=project_context_text or "{}", + external_results=context_blocks_text or "无", + ).strip(), + ) + return ( + "industry_trend", + PROMPT_INDUSTRY_TREND_QA.format( + question=question, + ).strip(), + ) + + if self._is_decision_choice_question(question): + return ( + "decision_choice", + PROMPT_DECISION_QA.format( + question=question, + ).strip(), + ) + if self._is_ai_pmo_process_entry(asked_via): world_model_text = json.dumps( { @@ -264,6 +401,89 @@ class AdaptiveRAGDeepGuardAnswerMixin: lines.append("来源: System Snapshot / User Snapshot") return "\n".join(lines)[:3200] + async def _build_member_workload_direct_answer(self, question: str) -> Optional[str]: + text = str(question or "").strip() + if not text or not self._contains_any_keyword(text, ("负载", "工作量", "工时")): + return None + user_rows = ( + await self.db.execute( + select(User.id, User.name, User.username) + .where( + and_( + User.tenant_id == self.tenant_id, + User.is_active.is_(True), + ) + ) + .order_by(User.id.asc()) + .limit(500) + ) + ).all() + if not user_rows: + return None + matched_rows: list[tuple[int, str, str, int]] = [] + for row in user_rows: + name = str(row.name or "").strip() + username = str(row.username or "").strip() + score = 0 + if name and name in text: + score = max(score, len(name)) + if username and username in text: + score = max(score, len(username)) + if score > 0: + matched_rows.append((int(row.id), name, username, score)) + if not matched_rows: + return None + matched_rows.sort(key=lambda item: (-item[3], item[0])) + user_id = int(matched_rows[0][0]) + user_name = str(matched_rows[0][1] or matched_rows[0][2] or "该成员") + workload_row = ( + await self.db.execute( + select(UserWorkload.workload_ratio, UserWorkload.current_assigned_hours, UserWorkload.max_available_hours_per_week).where( + and_( + UserWorkload.tenant_id == self.tenant_id, + UserWorkload.user_id == user_id, + ) + ) + ) + ).first() + if workload_row is None: + user_row = ( + await self.db.execute( + select(User.workload).where( + and_( + User.tenant_id == self.tenant_id, + User.id == user_id, + ) + ) + ) + ).scalar_one_or_none() + workload_ratio = round(float(user_row or 0) / 100.0, 2) + assigned_hours = None + max_hours = None + else: + workload_ratio = float(workload_row[0] or 0.0) + assigned_hours = float(workload_row[1] or 0.0) if workload_row[1] is not None else None + max_hours = float(workload_row[2] or 0.0) if workload_row[2] is not None else None + + percent = workload_ratio + if percent > 10: + percent = round(percent, 2) + else: + percent = round(percent * 100.0, 2) + + if percent >= 100: + status_text = "严重超载,今日无法再承接新任务" + elif percent >= 80: + status_text = "负载偏高,建议优先控制新增任务" + else: + status_text = "负载正常" + + detail_bits = [f"{user_name}当前负载{percent:.2f}%"] + if assigned_hours is not None and max_hours is not None: + detail_bits.append(f"(已分配{assigned_hours:.2f}h / 最大{max_hours:.2f}h)") + detail_bits.append(f",{status_text}。") + return "".join(detail_bits) + async def _generate_answer( self, question: str, @@ -280,9 +500,13 @@ class AdaptiveRAGDeepGuardAnswerMixin: asked_via: str = "pmflow", message_intent: Optional[str] = None, mixed_signal_part: Optional[str] = None, + query_type: Optional[str] = None, ) -> str: active_thinking_mode = self._normalize_thinking_mode(thinking_mode) deep_thinking = active_thinking_mode == THINKING_MODE_DEEP + member_workload_direct_answer = await self._build_member_workload_direct_answer(question) + if member_workload_direct_answer: + return member_workload_direct_answer[:3200] context_blocks: list[str] = [] used_tokens = 0 max_blocks = max(4, min(int(context_block_limit or DEFAULT_TOP_K * 2), 32)) @@ -324,6 +548,7 @@ class AdaptiveRAGDeepGuardAnswerMixin: system_snapshot_text=system_snapshot_text, user_snapshot_text=user_snapshot_text, relevance_max=relevance_max, + query_type=query_type, ) if deep_thinking: prompt = ( @@ -354,7 +579,7 @@ class AdaptiveRAGDeepGuardAnswerMixin: ) text = str(answer or "").strip() if not text: - return "我未能从当前上下文生成有效答案,请补充问题细节后重试。" + raise ValueError("adaptive_rag_answer_empty_response") if scene_key == "mixed": return self._format_mixed_two_block_output( answer_text=text, diff --git a/backend/app/services/adaptive_rag_service/mixin_deterministic_query_core.py b/backend/app/services/adaptive_rag_service/mixin_deterministic_query_core.py index 528d5c82..d3ac037d 100644 --- a/backend/app/services/adaptive_rag_service/mixin_deterministic_query_core.py +++ b/backend/app/services/adaptive_rag_service/mixin_deterministic_query_core.py @@ -33,6 +33,12 @@ class AdaptiveRAGDeterministicQueryCoreMixin: if q_type == QUERY_TYPE_LIST: return await self._run_list_query(question=question, project_id=project_id) if q_type == QUERY_TYPE_STATUS: + created_at = await self._try_fast_project_created_at_answer(clean_q=question, project_id=project_id) + if created_at: + created_at["query_type"] = QUERY_TYPE_STATUS + created_at.setdefault("confidence", 0.97) + created_at.setdefault("missing_fields", []) + return created_at progress = await self._try_fast_project_progress_answer(clean_q=question, project_id=project_id) if progress: progress["query_type"] = QUERY_TYPE_STATUS @@ -140,4 +146,3 @@ class AdaptiveRAGDeterministicQueryCoreMixin: best_name = str(row.title or row.name or f"项目#{int(row.id)}") return (best_id, best_name) - diff --git a/backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py b/backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py index 673df46c..1f57c79e 100644 --- a/backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py +++ b/backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py @@ -5,6 +5,11 @@ from .shared import * class AdaptiveRAGFastAnswerProjectMainMixin: + PROJECT_CREATED_AT_RE = re.compile( + r"(什么时候.*创建|何时.*创建|创建时间|立项时间|成立时间|创建于|何时立项|什么时候立项)", + re.IGNORECASE, + ) + async def _try_fast_project_progress_answer( self, *, @@ -138,6 +143,116 @@ class AdaptiveRAGFastAnswerProjectMainMixin: "response_time_ms": db_query_ms, } + async def _try_fast_project_created_at_answer( + self, + *, + clean_q: str, + project_id: Optional[int], + ) -> Optional[dict[str, Any]]: + question = str(clean_q or "").strip() + if not question or not self.PROJECT_CREATED_AT_RE.search(question): + return None + if "项目" not in question and "系统" not in question: + return None + + primary_keyword = self._extract_project_lookup_keyword(question) + query_terms = self._extract_query_terms(question, preferred=primary_keyword) + started = time.perf_counter() + + target_project_id, target_name, used_keyword = await self._resolve_project_scope_for_question( + question=question, + project_id=project_id, + ) + if target_project_id is None and query_terms: + for term in query_terms: + rows = await self._search_projects_by_keyword(keyword=term, project_id=project_id, limit=3) + if rows: + target_project_id = int(rows[0].id) + target_name = str(rows[0].title or rows[0].name or f"项目#{int(rows[0].id)}") + used_keyword = term + break + + if target_project_id is None: + db_query_ms = int((time.perf_counter() - started) * 1000) + return { + "answer": f"未检索到可确认创建时间的项目,无法判断“{question}”对应对象。", + "sources": [ + { + "source": "db", + "label": "Project database", + "score": 0.99, + "metadata": { + "table": "projects", + "query_keyword": used_keyword, + "match_count": 0, + }, + } + ], + "db_query_ms": db_query_ms, + "response_time_ms": db_query_ms, + } + + project_row = ( + await self.db.execute( + select(Project).where( + and_( + Project.tenant_id == self.tenant_id, + Project.id == int(target_project_id), + ) + ) + ) + ).scalar_one_or_none() + db_query_ms = int((time.perf_counter() - started) * 1000) + if project_row is None: + return { + "answer": f"未检索到项目#{int(target_project_id)} 的创建时间。", + "sources": [ + { + "source": "db", + "label": "Project database", + "score": 0.99, + "metadata": { + "table": "projects", + "project_id": int(target_project_id), + "query_keyword": used_keyword, + "match_count": 1, + }, + } + ], + "db_query_ms": db_query_ms, + "response_time_ms": db_query_ms, + } + + created_at = project_row.created_at + if created_at is None: + answer = f"项目「{target_name or str(project_row.title or project_row.name or f'项目#{int(project_row.id)}')}」已找到,但未记录创建时间。" + else: + if created_at.tzinfo is not None: + created_at = created_at.astimezone(CN_TZ) + answer = ( + f"项目「{target_name or str(project_row.title or project_row.name or f'项目#{int(project_row.id)}')}」" + f"创建于 {created_at.strftime('%Y-%m-%d %H:%M')}。" + ) + + return { + "answer": answer, + "sources": [ + { + "source": "db", + "label": "Project database", + "score": 0.99, + "metadata": { + "table": "projects", + "project_id": int(project_row.id), + "query_keyword": used_keyword, + "created_at": project_row.created_at.isoformat() if project_row.created_at else None, + }, + } + ], + "db_query_ms": db_query_ms, + "response_time_ms": db_query_ms, + } + async def _try_fast_project_existence_answer( self, *, @@ -224,4 +339,3 @@ class AdaptiveRAGFastAnswerProjectMainMixin: ) ) return (await self.db.execute(stmt.limit(max(1, int(limit))))).scalars().all() - diff --git a/backend/app/services/adaptive_rag_service/shared_imports.py b/backend/app/services/adaptive_rag_service/shared_imports.py index 5f173957..db0b00b9 100644 --- a/backend/app/services/adaptive_rag_service/shared_imports.py +++ b/backend/app/services/adaptive_rag_service/shared_imports.py @@ -29,7 +29,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from ...config.ai_prompts import ( PROMPT_AI_PMO_QA, + PROMPT_DECISION_QA, PROMPT_EXTERNAL_TREND_QA, + PROMPT_INDUSTRY_TREND_QA, PROMPT_FEISHU_DIRECT_QA, PROMPT_FEISHU_MIXED_QA, PROMPT_MEMBER_WORKLOAD_QA, diff --git a/backend/app/services/adaptive_rag_service/shared_part_01.py b/backend/app/services/adaptive_rag_service/shared_part_01.py index e3d628a3..8c933be9 100644 --- a/backend/app/services/adaptive_rag_service/shared_part_01.py +++ b/backend/app/services/adaptive_rag_service/shared_part_01.py @@ -190,6 +190,7 @@ FOLLOWUP_ENUMERATION_KEYWORDS = ( "都有哪些", "具体有哪些", "列出来", + "列一下", "清单", "名单", ) diff --git a/backend/app/services/adaptive_rag_service/shared_part_02.py b/backend/app/services/adaptive_rag_service/shared_part_02.py index 3773ab4f..f9179382 100644 --- a/backend/app/services/adaptive_rag_service/shared_part_02.py +++ b/backend/app/services/adaptive_rag_service/shared_part_02.py @@ -94,7 +94,7 @@ WRITE_EXPRESSIONS = ( "麻烦", ) COUNT_HINTS = ("多少", "几个", "数量", "总数", "count") -LIST_HINTS = ("哪些", "列表", "清单", "列出", "分别", "名单", "都有哪些") +LIST_HINTS = ("哪些", "列表", "清单", "列出", "列一下", "分别", "名单", "都有哪些") STATUS_HINTS = ("进度", "状态", "阶段", "完成率", "完成度", "情况") ENTITY_ALIAS_MAP: dict[str, str] = { "能力": "skill", diff --git a/backend/app/services/ai_orchestration_run/ingress_worker.py b/backend/app/services/ai_orchestration_run/ingress_worker.py index 262a866f..9e9ab352 100644 --- a/backend/app/services/ai_orchestration_run/ingress_worker.py +++ b/backend/app/services/ai_orchestration_run/ingress_worker.py @@ -145,6 +145,14 @@ async def _dispatch_claimed_ingress_run(*, run_id: int) -> None: task_payload_value = result_payload_value.get("task_payload") if isinstance(result_payload_value.get("task_payload"), dict) else {} message_sent_at_value = task_payload_value.get("message_sent_at") session_snapshot_value = task_payload_value.get("session_snapshot") if isinstance(task_payload_value.get("session_snapshot"), dict) else None + thread_context_value = task_payload_value.get("thread_context") if isinstance(task_payload_value.get("thread_context"), dict) else None + logger.info( + "ingress_worker_thread_context_snapshot", + run_id=run_id_value, + trace_id=trace_id_value, + source_message_id=source_message_id_value, + thread_context={k: str(v)[:128] for k, v in (thread_context_value or {}).items()} if isinstance(thread_context_value, dict) else None, + ) try: capture_service = ProactiveSignalCaptureService(db) @@ -183,6 +191,7 @@ async def _dispatch_claimed_ingress_run(*, run_id: int) -> None: force_sync=True, message_sent_at=message_sent_at_value, session_snapshot=session_snapshot_value, + thread_context=thread_context_value, ) finally: stop_heartbeat.set() diff --git a/backend/app/services/ai_orchestration_run/service.py b/backend/app/services/ai_orchestration_run/service.py index e2e350de..a4ccb9a8 100644 --- a/backend/app/services/ai_orchestration_run/service.py +++ b/backend/app/services/ai_orchestration_run/service.py @@ -121,6 +121,7 @@ class AIOrchestrationRunService: chat_type: Optional[str], captured_at: Optional[datetime], project_hint_id: Optional[int], + thread_context: Optional[dict[str, Any]] = None, max_retries: int = 3, ) -> tuple[AIOrchestrationRun, bool]: trace_id = self.build_ingress_trace_id( @@ -136,6 +137,9 @@ class AIOrchestrationRunService: "source_system": str(source_system or "").strip()[:32], "source_message_id": str(source_message_id or "").strip()[:128], "queued_at": _utc_now_naive().isoformat(), + "task_payload": { + "thread_context": thread_context if isinstance(thread_context, dict) else None, + }, } row = AIOrchestrationRun( tenant_id=int(tenant_id), diff --git a/backend/app/services/ai_pmo/ai_pmo_decision/policy.py b/backend/app/services/ai_pmo/ai_pmo_decision/policy.py index a3b8c6e1..4e42c3d6 100644 --- a/backend/app/services/ai_pmo/ai_pmo_decision/policy.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/policy.py @@ -7,7 +7,27 @@ import os from .shared import ( CanonicalIntent, INTENT_QUERY, + INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, re, @@ -35,6 +55,22 @@ class ExecutionPolicyBrain: projected_capacity_ratio = float(simulation.get("projected_capacity_peak_ratio") or 0.0) capacity_warning: dict[str, object] | None = None normalized = re.sub(r"\s+", "", str(message_text or "")).lower() + destructive_intents = { + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_DELETE, + INTENT_TASK_DELETE, + } + if intent.name in destructive_intents: + return { + "auto_execute": False, + "policy_gate_hit": True, + "policy_gate_code": "destructive_intent", + "approval_requirement": { + "required": True, + "reason": "destructive_intent", + "message": "检测到删除或归档类写操作,必须进入显式审批/人工确认。", + }, + } for gate_code, tokens in self.policy_gate_rules: if any(token.lower().replace(" ", "") in normalized for token in tokens): return { @@ -60,7 +96,28 @@ class ExecutionPolicyBrain: }, } - if intent.name in {INTENT_QUERY, INTENT_TASK_CREATE, INTENT_SIGNAL_CREATE}: + if intent.name in { + INTENT_QUERY, + INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_SIGNAL_CREATE, + INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_REOPEN, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, + }: return { "auto_execute": True, "policy_gate_hit": False, diff --git a/backend/app/services/ai_pmo/ai_pmo_decision/shared.py b/backend/app/services/ai_pmo/ai_pmo_decision/shared.py index caf99027..5c60fe89 100644 --- a/backend/app/services/ai_pmo/ai_pmo_decision/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/shared.py @@ -9,14 +9,56 @@ from ..ai_pmo_intent import ( INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE, INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_QUERY, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, + ROUTE_FAMILY_KNOWLEDGE, + ROUTE_FAMILY_NOTIFICATION, + ROUTE_FAMILY_PERSONNEL, + ROUTE_FAMILY_PROJECT, ROUTE_PROJECT_SIGNAL_CANCEL, ROUTE_PROJECT_SIGNAL_COMMIT, ROUTE_PROJECT_SIGNAL_DISCOVER, ROUTE_PROJECT_SIGNAL_REFRESH, + ROUTE_PROJECT_UPDATE, + ROUTE_PROJECT_ARCHIVE, + ROUTE_PROJECT_REOPEN, + ROUTE_PROJECT_DELETE, + ROUTE_TASK_UPDATE, + ROUTE_TASK_ASSIGN, + ROUTE_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION, + ROUTE_TASK_DELETE, + ROUTE_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX, + ROUTE_NOTIFICATION_CREATE, + ROUTE_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE, CanonicalIntent, ) @@ -46,14 +88,56 @@ __all__ = [ "INTENT_FOLLOWUP_CONFIRM", "INTENT_FOLLOWUP_CONTINUE", "INTENT_PROJECT_CREATE", + "INTENT_PROJECT_UPDATE", + "INTENT_PROJECT_ARCHIVE", + "INTENT_PROJECT_REOPEN", + "INTENT_PROJECT_DELETE", "INTENT_QUERY", "INTENT_TASK_CREATE", + "INTENT_TASK_UPDATE", + "INTENT_TASK_ASSIGN", + "INTENT_TASK_PRIORITIZE", + "INTENT_TASK_STATUS_TRANSITION", + "INTENT_TASK_DELETE", + "INTENT_PERSONNEL_ROLE_UPDATE", + "INTENT_PERSONNEL_WORKLOAD_UPDATE", + "INTENT_PERSONNEL_AVAILABILITY_UPDATE", + "INTENT_KNOWLEDGE_DOC_UPSERT", + "INTENT_KNOWLEDGE_CHUNK_UPSERT", + "INTENT_KNOWLEDGE_LINK_UPSERT", + "INTENT_KNOWLEDGE_REINDEX", + "INTENT_NOTIFICATION_CREATE", + "INTENT_NOTIFICATION_ACK", + "INTENT_NOTIFICATION_CLOSE", "INTENT_SIGNAL_CREATE", "INTENT_SIGNAL_SOURCE_ONBOARD", + "ROUTE_FAMILY_KNOWLEDGE", + "ROUTE_FAMILY_NOTIFICATION", + "ROUTE_FAMILY_PERSONNEL", + "ROUTE_FAMILY_PROJECT", "ROUTE_PROJECT_SIGNAL_CANCEL", "ROUTE_PROJECT_SIGNAL_COMMIT", "ROUTE_PROJECT_SIGNAL_DISCOVER", "ROUTE_PROJECT_SIGNAL_REFRESH", + "ROUTE_PROJECT_UPDATE", + "ROUTE_PROJECT_ARCHIVE", + "ROUTE_PROJECT_REOPEN", + "ROUTE_PROJECT_DELETE", + "ROUTE_TASK_UPDATE", + "ROUTE_TASK_ASSIGN", + "ROUTE_TASK_PRIORITIZE", + "ROUTE_TASK_STATUS_TRANSITION", + "ROUTE_TASK_DELETE", + "ROUTE_PERSONNEL_ROLE_UPDATE", + "ROUTE_PERSONNEL_WORKLOAD_UPDATE", + "ROUTE_PERSONNEL_AVAILABILITY_UPDATE", + "ROUTE_KNOWLEDGE_DOC_UPSERT", + "ROUTE_KNOWLEDGE_CHUNK_UPSERT", + "ROUTE_KNOWLEDGE_LINK_UPSERT", + "ROUTE_KNOWLEDGE_REINDEX", + "ROUTE_NOTIFICATION_CREATE", + "ROUTE_NOTIFICATION_ACK", + "ROUTE_NOTIFICATION_CLOSE", "normalize_workload_ratio", "re", "safe_int", diff --git a/backend/app/services/ai_pmo/ai_pmo_decision/simulation.py b/backend/app/services/ai_pmo/ai_pmo_decision/simulation.py index ffc9f7cb..2ec1d40e 100644 --- a/backend/app/services/ai_pmo/ai_pmo_decision/simulation.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/simulation.py @@ -19,7 +19,26 @@ class SimulationEngine: BUDGET_COST_BY_OPERATION = { "query.answer": 1.0, "project.create": 120.0, + "project.update": 12.0, + "project.archive": 6.0, + "project.reopen": 6.0, + "project.delete": 8.0, "task.create": 14.0, + "task.update": 4.0, + "task.assign": 3.0, + "task.prioritize": 2.0, + "task.status_transition": 2.0, + "task.delete": 3.0, + "personnel.role_update": 4.0, + "personnel.workload_update": 2.0, + "personnel.availability_update": 2.0, + "knowledge.doc_upsert": 12.0, + "knowledge.chunk_upsert": 6.0, + "knowledge.link_upsert": 4.0, + "knowledge.reindex": 20.0, + "notification.create": 1.0, + "notification.ack": 0.3, + "notification.close": 0.5, "signal.enter": 8.0, "signal_source.onboard": 40.0, ROUTE_PROJECT_SIGNAL_DISCOVER: 3.0, @@ -30,7 +49,26 @@ class SimulationEngine: HOURS_BY_OPERATION = { "query.answer": 0.2, "project.create": 12.0, + "project.update": 1.2, + "project.archive": 0.6, + "project.reopen": 0.6, + "project.delete": 0.8, "task.create": 2.5, + "task.update": 0.8, + "task.assign": 0.5, + "task.prioritize": 0.4, + "task.status_transition": 0.4, + "task.delete": 0.5, + "personnel.role_update": 0.5, + "personnel.workload_update": 0.3, + "personnel.availability_update": 0.3, + "knowledge.doc_upsert": 1.8, + "knowledge.chunk_upsert": 0.8, + "knowledge.link_upsert": 0.5, + "knowledge.reindex": 3.0, + "notification.create": 0.2, + "notification.ack": 0.1, + "notification.close": 0.1, "signal.enter": 1.5, "signal_source.onboard": 6.0, ROUTE_PROJECT_SIGNAL_DISCOVER: 0.8, diff --git a/backend/app/services/ai_pmo/ai_pmo_decision/strategic.py b/backend/app/services/ai_pmo/ai_pmo_decision/strategic.py index 5cb031e7..e201d521 100644 --- a/backend/app/services/ai_pmo/ai_pmo_decision/strategic.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/strategic.py @@ -5,7 +5,26 @@ from __future__ import annotations from .shared import ( CanonicalIntent, INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_SOURCE_ONBOARD, Any, normalize_workload_ratio, @@ -29,8 +48,31 @@ class StrategicBrain: risk_score = min(1.0, 0.18 + risk_count * 0.08 + budget_ratio * 0.25 + len(overloaded_people) * 0.1) if intent.name == INTENT_PROJECT_CREATE: risk_score = max(risk_score, 0.62) + if intent.name in {INTENT_PROJECT_UPDATE, INTENT_PROJECT_ARCHIVE, INTENT_PROJECT_REOPEN}: + risk_score = max(risk_score, 0.48) + if intent.name == INTENT_PROJECT_DELETE: + risk_score = max(risk_score, 0.82) if intent.name == INTENT_TASK_CREATE: risk_score = max(risk_score, 0.55) + if intent.name in {INTENT_TASK_UPDATE, INTENT_TASK_ASSIGN, INTENT_TASK_PRIORITIZE, INTENT_TASK_STATUS_TRANSITION}: + risk_score = max(risk_score, 0.42) + if intent.name == INTENT_TASK_DELETE: + risk_score = max(risk_score, 0.7) + if intent.name in { + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + }: + risk_score = max(risk_score, 0.46) + if intent.name in { + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + }: + risk_score = max(risk_score, 0.38) + if intent.name in {INTENT_NOTIFICATION_CREATE, INTENT_NOTIFICATION_ACK, INTENT_NOTIFICATION_CLOSE}: + risk_score = max(risk_score, 0.28) if intent.name == INTENT_SIGNAL_SOURCE_ONBOARD: risk_score = max(risk_score, 0.58) diff --git a/backend/app/services/ai_pmo/ai_pmo_decision/tactical.py b/backend/app/services/ai_pmo/ai_pmo_decision/tactical.py index f1d402ca..76ac09c5 100644 --- a/backend/app/services/ai_pmo/ai_pmo_decision/tactical.py +++ b/backend/app/services/ai_pmo/ai_pmo_decision/tactical.py @@ -7,14 +7,52 @@ from .shared import ( INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE, INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_QUERY, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, + ROUTE_PROJECT_ARCHIVE, ROUTE_PROJECT_SIGNAL_CANCEL, ROUTE_PROJECT_SIGNAL_COMMIT, ROUTE_PROJECT_SIGNAL_DISCOVER, ROUTE_PROJECT_SIGNAL_REFRESH, + ROUTE_PROJECT_DELETE, + ROUTE_PROJECT_REOPEN, + ROUTE_PROJECT_UPDATE, + ROUTE_TASK_ASSIGN, + ROUTE_TASK_DELETE, + ROUTE_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION, + ROUTE_TASK_UPDATE, + ROUTE_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX, + ROUTE_NOTIFICATION_CREATE, + ROUTE_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE, Any, ) @@ -43,8 +81,46 @@ class TacticalBrain: execution_actions.append({"operation_code": "query.answer", "mode": "read_only"}) elif intent.name == INTENT_PROJECT_CREATE: execution_actions.append({"operation_code": "project.create", "mode": "write"}) + elif intent.name == INTENT_PROJECT_UPDATE: + execution_actions.append({"operation_code": ROUTE_PROJECT_UPDATE, "mode": "write"}) + elif intent.name == INTENT_PROJECT_ARCHIVE: + execution_actions.append({"operation_code": ROUTE_PROJECT_ARCHIVE, "mode": "write"}) + elif intent.name == INTENT_PROJECT_REOPEN: + execution_actions.append({"operation_code": ROUTE_PROJECT_REOPEN, "mode": "write"}) + elif intent.name == INTENT_PROJECT_DELETE: + execution_actions.append({"operation_code": ROUTE_PROJECT_DELETE, "mode": "write"}) elif intent.name == INTENT_TASK_CREATE: execution_actions.append({"operation_code": "task.create", "mode": "write"}) + elif intent.name == INTENT_TASK_UPDATE: + execution_actions.append({"operation_code": ROUTE_TASK_UPDATE, "mode": "write"}) + elif intent.name == INTENT_TASK_ASSIGN: + execution_actions.append({"operation_code": ROUTE_TASK_ASSIGN, "mode": "write"}) + elif intent.name == INTENT_TASK_PRIORITIZE: + execution_actions.append({"operation_code": ROUTE_TASK_PRIORITIZE, "mode": "write"}) + elif intent.name == INTENT_TASK_STATUS_TRANSITION: + execution_actions.append({"operation_code": ROUTE_TASK_STATUS_TRANSITION, "mode": "write"}) + elif intent.name == INTENT_TASK_DELETE: + execution_actions.append({"operation_code": ROUTE_TASK_DELETE, "mode": "write"}) + elif intent.name == INTENT_PERSONNEL_ROLE_UPDATE: + execution_actions.append({"operation_code": ROUTE_PERSONNEL_ROLE_UPDATE, "mode": "write"}) + elif intent.name == INTENT_PERSONNEL_WORKLOAD_UPDATE: + execution_actions.append({"operation_code": ROUTE_PERSONNEL_WORKLOAD_UPDATE, "mode": "write"}) + elif intent.name == INTENT_PERSONNEL_AVAILABILITY_UPDATE: + execution_actions.append({"operation_code": ROUTE_PERSONNEL_AVAILABILITY_UPDATE, "mode": "write"}) + elif intent.name == INTENT_KNOWLEDGE_DOC_UPSERT: + execution_actions.append({"operation_code": ROUTE_KNOWLEDGE_DOC_UPSERT, "mode": "write"}) + elif intent.name == INTENT_KNOWLEDGE_CHUNK_UPSERT: + execution_actions.append({"operation_code": ROUTE_KNOWLEDGE_CHUNK_UPSERT, "mode": "write"}) + elif intent.name == INTENT_KNOWLEDGE_LINK_UPSERT: + execution_actions.append({"operation_code": ROUTE_KNOWLEDGE_LINK_UPSERT, "mode": "write"}) + elif intent.name == INTENT_KNOWLEDGE_REINDEX: + execution_actions.append({"operation_code": ROUTE_KNOWLEDGE_REINDEX, "mode": "write"}) + elif intent.name == INTENT_NOTIFICATION_CREATE: + execution_actions.append({"operation_code": ROUTE_NOTIFICATION_CREATE, "mode": "write"}) + elif intent.name == INTENT_NOTIFICATION_ACK: + execution_actions.append({"operation_code": ROUTE_NOTIFICATION_ACK, "mode": "write"}) + elif intent.name == INTENT_NOTIFICATION_CLOSE: + execution_actions.append({"operation_code": ROUTE_NOTIFICATION_CLOSE, "mode": "write"}) elif intent.name == INTENT_SIGNAL_SOURCE_ONBOARD: execution_actions.append({"operation_code": "signal.enter", "mode": "write"}) execution_actions.append({"operation_code": "signal_source.onboard", "mode": "write"}) diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py index 1e77d231..609d19f0 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/route_resolver.py @@ -34,6 +34,8 @@ class RouteResolver: mapped = ROUTE_CANONICAL_INTENT_MAP.get(normalized) if mapped: return mapped + if normalized in INTENT_CANDIDATES: + return normalized family, _ = cls._split_route_key(normalized) if family == ROUTE_FAMILY_PROJECT_SIGNAL: return INTENT_SIGNAL_CREATE diff --git a/backend/app/services/ai_pmo/ai_pmo_intent/shared.py b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py index e48a7350..758636cc 100644 --- a/backend/app/services/ai_pmo/ai_pmo_intent/shared.py +++ b/backend/app/services/ai_pmo/ai_pmo_intent/shared.py @@ -11,7 +11,26 @@ from ...signal.signal_source_orchestration import SignalSourceOrchestrationServi INTENT_QUERY = "query.read_only" INTENT_PROJECT_CREATE = "project.create" +INTENT_PROJECT_UPDATE = "project.update" +INTENT_PROJECT_ARCHIVE = "project.archive" +INTENT_PROJECT_REOPEN = "project.reopen" +INTENT_PROJECT_DELETE = "project.delete" INTENT_TASK_CREATE = "task.create" +INTENT_TASK_UPDATE = "task.update" +INTENT_TASK_ASSIGN = "task.assign" +INTENT_TASK_PRIORITIZE = "task.prioritize" +INTENT_TASK_STATUS_TRANSITION = "task.status_transition" +INTENT_TASK_DELETE = "task.delete" +INTENT_PERSONNEL_ROLE_UPDATE = "personnel.role_update" +INTENT_PERSONNEL_WORKLOAD_UPDATE = "personnel.workload_update" +INTENT_PERSONNEL_AVAILABILITY_UPDATE = "personnel.availability_update" +INTENT_KNOWLEDGE_DOC_UPSERT = "knowledge.doc_upsert" +INTENT_KNOWLEDGE_CHUNK_UPSERT = "knowledge.chunk_upsert" +INTENT_KNOWLEDGE_LINK_UPSERT = "knowledge.link_upsert" +INTENT_KNOWLEDGE_REINDEX = "knowledge.reindex" +INTENT_NOTIFICATION_CREATE = "notification.create" +INTENT_NOTIFICATION_ACK = "notification.ack" +INTENT_NOTIFICATION_CLOSE = "notification.close" INTENT_SIGNAL_CREATE = "signal.create" INTENT_SIGNAL_SOURCE_ONBOARD = "signal_source.onboard" INTENT_FOLLOWUP_CONFIRM = "followup.confirm" @@ -26,13 +45,33 @@ FOLLOWUP_TARGET_INTENTS = { ROUTE_FAMILY_QUERY = "query" ROUTE_FAMILY_PROJECT_CREATE = "project_create" +ROUTE_FAMILY_PROJECT = "project" ROUTE_FAMILY_TASK = "task" +ROUTE_FAMILY_PERSONNEL = "personnel" +ROUTE_FAMILY_KNOWLEDGE = "knowledge" +ROUTE_FAMILY_NOTIFICATION = "notification" ROUTE_FAMILY_PROJECT_SIGNAL = "project_signal" ROUTE_FAMILY_SIGNAL_SOURCE = "signal_source" ROUTE_FAMILY_FOLLOWUP = "followup" ROUTE_ACTION_ANSWER = "answer" ROUTE_ACTION_CREATE = "create" +ROUTE_ACTION_UPDATE = "update" +ROUTE_ACTION_ARCHIVE = "archive" +ROUTE_ACTION_REOPEN = "reopen" +ROUTE_ACTION_DELETE = "delete" +ROUTE_ACTION_ASSIGN = "assign" +ROUTE_ACTION_PRIORITIZE = "prioritize" +ROUTE_ACTION_STATUS_TRANSITION = "status_transition" +ROUTE_ACTION_ROLE_UPDATE = "role_update" +ROUTE_ACTION_WORKLOAD_UPDATE = "workload_update" +ROUTE_ACTION_AVAILABILITY_UPDATE = "availability_update" +ROUTE_ACTION_DOC_UPSERT = "doc_upsert" +ROUTE_ACTION_CHUNK_UPSERT = "chunk_upsert" +ROUTE_ACTION_LINK_UPSERT = "link_upsert" +ROUTE_ACTION_REINDEX = "reindex" +ROUTE_ACTION_ACK = "ack" +ROUTE_ACTION_CLOSE = "close" ROUTE_ACTION_DISCOVER = "discover" ROUTE_ACTION_COMMIT = "commit" ROUTE_ACTION_CANCEL = "cancel" @@ -43,7 +82,26 @@ ROUTE_ACTION_CONFIRM = "confirm" ROUTE_QUERY_ANSWER = f"{ROUTE_FAMILY_QUERY}.{ROUTE_ACTION_ANSWER}" ROUTE_PROJECT_CREATE = f"{ROUTE_FAMILY_PROJECT_CREATE}.{ROUTE_ACTION_CREATE}" +ROUTE_PROJECT_UPDATE = f"{ROUTE_FAMILY_PROJECT}.{ROUTE_ACTION_UPDATE}" +ROUTE_PROJECT_ARCHIVE = f"{ROUTE_FAMILY_PROJECT}.{ROUTE_ACTION_ARCHIVE}" +ROUTE_PROJECT_REOPEN = f"{ROUTE_FAMILY_PROJECT}.{ROUTE_ACTION_REOPEN}" +ROUTE_PROJECT_DELETE = f"{ROUTE_FAMILY_PROJECT}.{ROUTE_ACTION_DELETE}" ROUTE_TASK_CREATE = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_CREATE}" +ROUTE_TASK_UPDATE = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_UPDATE}" +ROUTE_TASK_ASSIGN = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_ASSIGN}" +ROUTE_TASK_PRIORITIZE = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_PRIORITIZE}" +ROUTE_TASK_STATUS_TRANSITION = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_STATUS_TRANSITION}" +ROUTE_TASK_DELETE = f"{ROUTE_FAMILY_TASK}.{ROUTE_ACTION_DELETE}" +ROUTE_PERSONNEL_ROLE_UPDATE = f"{ROUTE_FAMILY_PERSONNEL}.{ROUTE_ACTION_ROLE_UPDATE}" +ROUTE_PERSONNEL_WORKLOAD_UPDATE = f"{ROUTE_FAMILY_PERSONNEL}.{ROUTE_ACTION_WORKLOAD_UPDATE}" +ROUTE_PERSONNEL_AVAILABILITY_UPDATE = f"{ROUTE_FAMILY_PERSONNEL}.{ROUTE_ACTION_AVAILABILITY_UPDATE}" +ROUTE_KNOWLEDGE_DOC_UPSERT = f"{ROUTE_FAMILY_KNOWLEDGE}.{ROUTE_ACTION_DOC_UPSERT}" +ROUTE_KNOWLEDGE_CHUNK_UPSERT = f"{ROUTE_FAMILY_KNOWLEDGE}.{ROUTE_ACTION_CHUNK_UPSERT}" +ROUTE_KNOWLEDGE_LINK_UPSERT = f"{ROUTE_FAMILY_KNOWLEDGE}.{ROUTE_ACTION_LINK_UPSERT}" +ROUTE_KNOWLEDGE_REINDEX = f"{ROUTE_FAMILY_KNOWLEDGE}.{ROUTE_ACTION_REINDEX}" +ROUTE_NOTIFICATION_CREATE = f"{ROUTE_FAMILY_NOTIFICATION}.{ROUTE_ACTION_CREATE}" +ROUTE_NOTIFICATION_ACK = f"{ROUTE_FAMILY_NOTIFICATION}.{ROUTE_ACTION_ACK}" +ROUTE_NOTIFICATION_CLOSE = f"{ROUTE_FAMILY_NOTIFICATION}.{ROUTE_ACTION_CLOSE}" ROUTE_PROJECT_SIGNAL_CREATE = f"{ROUTE_FAMILY_PROJECT_SIGNAL}.{ROUTE_ACTION_CREATE}" ROUTE_PROJECT_SIGNAL_DISCOVER = f"{ROUTE_FAMILY_PROJECT_SIGNAL}.{ROUTE_ACTION_DISCOVER}" ROUTE_PROJECT_SIGNAL_COMMIT = f"{ROUTE_FAMILY_PROJECT_SIGNAL}.{ROUTE_ACTION_COMMIT}" @@ -56,7 +114,26 @@ ROUTE_FOLLOWUP_CONTINUE = f"{ROUTE_FAMILY_FOLLOWUP}.{ROUTE_ACTION_CONTINUE}" WORKFLOW_ROUTE_KEYS = { ROUTE_QUERY_ANSWER, ROUTE_PROJECT_CREATE, + ROUTE_PROJECT_UPDATE, + ROUTE_PROJECT_ARCHIVE, + ROUTE_PROJECT_REOPEN, + ROUTE_PROJECT_DELETE, ROUTE_TASK_CREATE, + ROUTE_TASK_UPDATE, + ROUTE_TASK_ASSIGN, + ROUTE_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION, + ROUTE_TASK_DELETE, + ROUTE_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX, + ROUTE_NOTIFICATION_CREATE, + ROUTE_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE, ROUTE_PROJECT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER, ROUTE_PROJECT_SIGNAL_COMMIT, @@ -70,7 +147,26 @@ WORKFLOW_ROUTE_KEYS = { ROUTE_CANONICAL_INTENT_MAP: dict[str, str] = { ROUTE_QUERY_ANSWER: INTENT_QUERY, ROUTE_PROJECT_CREATE: INTENT_PROJECT_CREATE, + ROUTE_PROJECT_UPDATE: INTENT_PROJECT_UPDATE, + ROUTE_PROJECT_ARCHIVE: INTENT_PROJECT_ARCHIVE, + ROUTE_PROJECT_REOPEN: INTENT_PROJECT_REOPEN, + ROUTE_PROJECT_DELETE: INTENT_PROJECT_DELETE, ROUTE_TASK_CREATE: INTENT_TASK_CREATE, + ROUTE_TASK_UPDATE: INTENT_TASK_UPDATE, + ROUTE_TASK_ASSIGN: INTENT_TASK_ASSIGN, + ROUTE_TASK_PRIORITIZE: INTENT_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION: INTENT_TASK_STATUS_TRANSITION, + ROUTE_TASK_DELETE: INTENT_TASK_DELETE, + ROUTE_PERSONNEL_ROLE_UPDATE: INTENT_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE: INTENT_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE: INTENT_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_KNOWLEDGE_DOC_UPSERT: INTENT_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_CHUNK_UPSERT: INTENT_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT: INTENT_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX: INTENT_KNOWLEDGE_REINDEX, + ROUTE_NOTIFICATION_CREATE: INTENT_NOTIFICATION_CREATE, + ROUTE_NOTIFICATION_ACK: INTENT_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE: INTENT_NOTIFICATION_CLOSE, ROUTE_PROJECT_SIGNAL_CREATE: INTENT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER: INTENT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_COMMIT: INTENT_SIGNAL_CREATE, @@ -84,7 +180,26 @@ ROUTE_CANONICAL_INTENT_MAP: dict[str, str] = { LEGACY_INTENT_ROUTE_MAP: dict[str, str] = { INTENT_QUERY: ROUTE_QUERY_ANSWER, INTENT_PROJECT_CREATE: ROUTE_PROJECT_CREATE, + INTENT_PROJECT_UPDATE: ROUTE_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE: ROUTE_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN: ROUTE_PROJECT_REOPEN, + INTENT_PROJECT_DELETE: ROUTE_PROJECT_DELETE, INTENT_TASK_CREATE: ROUTE_TASK_CREATE, + INTENT_TASK_UPDATE: ROUTE_TASK_UPDATE, + INTENT_TASK_ASSIGN: ROUTE_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE: ROUTE_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION: ROUTE_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE: ROUTE_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE: ROUTE_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE: ROUTE_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE: ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT: ROUTE_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT: ROUTE_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT: ROUTE_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX: ROUTE_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE: ROUTE_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK: ROUTE_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE: ROUTE_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE: ROUTE_PROJECT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD: ROUTE_SIGNAL_SOURCE_ONBOARD, INTENT_FOLLOWUP_CONFIRM: ROUTE_FOLLOWUP_CONFIRM, @@ -214,16 +329,54 @@ INTENT_LLM_FUNCTION_NAME = "classify_intent_ai_pmo_kernel" INTENT_CANDIDATES = ( INTENT_SIGNAL_SOURCE_ONBOARD, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, INTENT_SIGNAL_CREATE, - INTENT_QUERY, INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, + INTENT_QUERY, INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE, INTENT_IGNORE, ) WRITE_INTENTS = { INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, INTENT_FOLLOWUP_CONFIRM, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py index 3e1f5d99..22a9eeaa 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_agent_kernel.py @@ -7,7 +7,6 @@ import re from dataclasses import dataclass from typing import Iterable, Sequence -BOOL_TRUE_VALUES = {"1", "true", "yes", "y", "on", "是"} INTENT_FUNCTION_SIMPLE = "classify_intent_ai_pmo_kernel" INTENT_FUNCTION_COMPLEX = "classify_intent_complex" @@ -39,13 +38,6 @@ def _env_float(name: str, default: float) -> float: return float(default) if value < 0 else value -def _env_bool(name: str, default: bool = False) -> bool: - raw = os.getenv(name) - if raw is None: - return default - return str(raw).strip().lower() in BOOL_TRUE_VALUES - - def _normalize_text(text: str) -> str: return re.sub(r"\s+", "", str(text or "")).lower() @@ -167,12 +159,6 @@ def gate_execution( force_manual_code: str = "manual_review_required", force_manual_reason: str = "", ) -> GateDecision: - auto_threshold = _env_float("AGENT_GATE_AUTO_THRESHOLD", 0.82) - review_threshold = _env_float("AGENT_GATE_REVIEW_THRESHOLD", 0.60) - strict_high_risk = _env_bool("AGENT_GATE_STRICT_HIGH_RISK", True) - require_review_below_auto = _env_bool("AGENT_GATE_REVIEW_BELOW_AUTO", False) - normalized_risk = str(risk_level or "medium").strip().lower() - safe_confidence = max(0.0, min(1.0, float(confidence or 0.0))) if force_manual: return GateDecision( @@ -190,34 +176,10 @@ def gate_execution( auto_execute=False, ) - if safe_confidence < review_threshold: - return GateDecision( - action="reject", - gate_code="confidence_too_low", - reason="置信度低于安全阈值,已阻断自动执行。", - auto_execute=False, - ) - - if normalized_risk in {"high", "critical"} and strict_high_risk and safe_confidence < max(auto_threshold, 0.9): - return GateDecision( - action="pending_manual", - gate_code="high_risk_manual_review", - reason="高风险写操作需要更高置信度,当前进入待人工确认。", - auto_execute=False, - ) - - if safe_confidence < auto_threshold and require_review_below_auto: - return GateDecision( - action="pending_manual", - gate_code="confidence_review_required", - reason="置信度未达自动执行阈值,进入待人工确认。", - auto_execute=False, - ) - return GateDecision( action="auto_execute", gate_code="auto_execute_allowed", - reason="置信度达标,允许自动执行。", + reason="基础门禁通过,允许自动执行。", auto_execute=True, ) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py index cb5afa1b..9cee2679 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_reply_renderer.py @@ -65,6 +65,338 @@ class ReplyRenderer: self.receipt_phase_pending = receipt_phase_pending self.receipt_phase_error = receipt_phase_error + @staticmethod + def _operation_code(*, intent: CanonicalIntent, execution_result: dict[str, Any]) -> str: + action_contract = execution_result.get("action_contract") if isinstance(execution_result, dict) else {} + if isinstance(action_contract, dict): + operation_code = str(action_contract.get("operation_code") or "").strip().lower() + if operation_code: + return operation_code + operation_code = str(execution_result.get("operation_code") or "").strip().lower() + if operation_code: + return operation_code + return str(intent.name or "").strip().lower() + + @staticmethod + def _intent_parameters(*, intent: CanonicalIntent, execution_result: dict[str, Any]) -> dict[str, Any]: + action_contract = execution_result.get("action_contract") if isinstance(execution_result, dict) else {} + if isinstance(action_contract, dict): + params = action_contract.get("params") + if isinstance(params, dict) and params: + return params + metadata = intent.metadata if isinstance(intent.metadata, dict) else {} + for key in ("llm_parameters", "parameters"): + params = metadata.get(key) + if isinstance(params, dict) and params: + return params + payload = metadata.get("llm_payload") + if isinstance(payload, dict): + params = payload.get("parameters") + if isinstance(params, dict) and params: + return params + return {} + + def _mutation_target_label(self, *, operation_code: str, intent: CanonicalIntent, execution_result: dict[str, Any]) -> str: + params = self._intent_parameters(intent=intent, execution_result=execution_result) + op = str(operation_code or "").strip().lower() + if op.startswith("project."): + project = execution_result.get("project") if isinstance(execution_result.get("project"), dict) else {} + project_id = _safe_int(execution_result.get("project_id")) or _safe_int(project.get("id")) or _safe_int(params.get("project_id")) + title = str( + project.get("title") + or project.get("name") + or params.get("project_title") + or params.get("project_name") + or params.get("title") + or "" + ).strip() + if project_id is not None and title: + return f"项目#{project_id}《{title}》" + if project_id is not None: + return f"项目#{project_id}" + if title: + return f"项目《{title}》" + return "项目" + if op.startswith("task."): + task = execution_result.get("task") if isinstance(execution_result.get("task"), dict) else {} + task_id = _safe_int(execution_result.get("task_id")) or _safe_int(task.get("id")) or _safe_int(params.get("task_id")) + title = str( + task.get("title") + or params.get("task_title") + or params.get("title") + or "" + ).strip() + if task_id is not None and title: + return f"任务#{task_id}《{title}》" + if task_id is not None: + return f"任务#{task_id}" + if title: + return f"任务《{title}》" + return "任务" + if op.startswith("personnel."): + user = execution_result.get("user") if isinstance(execution_result.get("user"), dict) else {} + user_id = _safe_int(execution_result.get("user_id")) or _safe_int(user.get("id")) or _safe_int(params.get("user_id")) + name = str( + user.get("name") + or user.get("username") + or params.get("user_name") + or params.get("name") + or params.get("username") + or "" + ).strip() + if user_id is not None and name: + return f"用户#{user_id}({name})" + if user_id is not None: + return f"用户#{user_id}" + if name: + return f"用户({name})" + return "用户" + if op.startswith("knowledge."): + doc = execution_result.get("doc") if isinstance(execution_result.get("doc"), dict) else {} + doc_id = _safe_int(execution_result.get("doc_id")) or _safe_int(doc.get("id")) or _safe_int(params.get("doc_id")) + title = str(doc.get("title") or params.get("doc_title") or params.get("title") or "").strip() + if doc_id is not None and title: + return f"知识文档#{doc_id}《{title}》" + if doc_id is not None: + return f"知识文档#{doc_id}" + if title: + return f"知识文档《{title}》" + relation_id = _safe_int(execution_result.get("relation_id")) + if relation_id is not None: + return f"知识关联#{relation_id}" + chunk_id = _safe_int(execution_result.get("chunk_id")) + if chunk_id is not None: + return f"知识分片#{chunk_id}" + return "知识对象" + if op.startswith("notification."): + notification = execution_result.get("notification") if isinstance(execution_result.get("notification"), dict) else {} + notification_id = _safe_int(execution_result.get("notification_id")) or _safe_int(notification.get("id")) or _safe_int(params.get("notification_id")) + title = str(notification.get("title") or params.get("title") or "").strip() + if notification_id is not None and title: + return f"通知#{notification_id}《{title}》" + if notification_id is not None: + return f"通知#{notification_id}" + if title: + return f"通知《{title}》" + return "通知" + return "" + + def _render_mutation_pending( + self, + *, + operation_code: str, + intent: CanonicalIntent, + decision_record: dict[str, Any], + execution_result: dict[str, Any], + ) -> Optional[dict[str, Any]]: + op = str(operation_code or "").strip().lower() + params = self._intent_parameters(intent=intent, execution_result=execution_result) + target = self._mutation_target_label(operation_code=op, intent=intent, execution_result=execution_result) + gate_message = str(((decision_record.get("approval_requirement") or {}).get("message")) or "").strip() + candidate_project_ids = execution_result.get("candidate_project_ids") if isinstance(execution_result.get("candidate_project_ids"), list) else [] + candidate_task_ids = execution_result.get("candidate_task_ids") if isinstance(execution_result.get("candidate_task_ids"), list) else [] + candidate_user_ids = execution_result.get("candidate_user_ids") if isinstance(execution_result.get("candidate_user_ids"), list) else [] + + if op.startswith("project."): + if candidate_project_ids: + ids = "、".join(f"#{_safe_int(item)}" for item in candidate_project_ids[:5] if _safe_int(item) is not None) + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "candidate_project_ids": candidate_project_ids[:5]}, + "text": f"项目名称对应多个结果:{ids}。请补充 project_id 后再继续。", + } + if target: + prefix = "删除" if op == "project.delete" else "归档" if op == "project.archive" else "重新打开" if op == "project.reopen" else "更新" + text = f"已收到{prefix}{target}的请求,当前需要人工确认后执行。" + if gate_message: + text = f"{text} {gate_message}" + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "target": target}, + "text": text, + } + if op.startswith("task."): + if candidate_task_ids: + ids = "、".join(f"#{_safe_int(item)}" for item in candidate_task_ids[:5] if _safe_int(item) is not None) + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "candidate_task_ids": candidate_task_ids[:5]}, + "text": f"任务标题对应多个结果:{ids}。请补充 task_id 后再继续。", + } + if target: + prefix = "删除" if op == "task.delete" else "调整" + text = f"已收到{prefix}{target}的请求,当前需要人工确认后执行。" + if gate_message: + text = f"{text} {gate_message}" + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "target": target}, + "text": text, + } + if op.startswith("personnel.") and candidate_user_ids: + ids = "、".join(f"#{_safe_int(item)}" for item in candidate_user_ids[:5] if _safe_int(item) is not None) + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "candidate_user_ids": candidate_user_ids[:5]}, + "text": f"用户名称对应多个结果:{ids}。请补充 user_id 后再继续。", + } + if op.startswith("knowledge.") and target: + text = f"已收到对{target}的处理请求,当前需要人工确认后执行。" + if gate_message: + text = f"{text} {gate_message}" + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "target": target}, + "text": text, + } + if op.startswith("notification.") and target: + text = f"已收到{target}的处理请求,当前需要人工确认后执行。" + if gate_message: + text = f"{text} {gate_message}" + return { + "receipt_phase": self.receipt_phase_pending, + "template_key": "pending_manual", + "payload": {"intent": intent.name, "operation_code": op, "target": target}, + "text": text, + } + return None + + def _render_mutation_success( + self, + *, + operation_code: str, + intent: CanonicalIntent, + execution_result: dict[str, Any], + ) -> Optional[dict[str, Any]]: + params = self._intent_parameters(intent=intent, execution_result=execution_result) + target = self._mutation_target_label(operation_code=operation_code, intent=intent, execution_result=execution_result) + op = str(operation_code or "").strip().lower() + if op == "project.update": + text = f"已更新{target}。" if target else "已更新项目。" + elif op == "project.archive": + text = f"已归档{target}。" if target else "已归档项目。" + elif op == "project.reopen": + text = f"已重新打开{target}。" if target else "已重新打开项目。" + elif op == "project.delete": + text = f"已删除{target}。" if target else "已删除项目。" + elif op == "task.update": + text = f"已更新{target}。" if target else "已更新任务。" + elif op == "task.assign": + assignee = execution_result.get("assignee") if isinstance(execution_result.get("assignee"), dict) else {} + assignee_name = str(assignee.get("name") or assignee.get("username") or params.get("assignee_name") or params.get("user_name") or params.get("name") or "").strip() + if target and assignee_name: + text = f"已将{target}分配给{assignee_name}。" + elif target: + text = f"已完成{target}分配。" + else: + text = "已完成任务分配。" + elif op == "task.prioritize": + priority = str( + params.get("priority") + or (execution_result.get("task") if isinstance(execution_result.get("task"), dict) else {}).get("priority") + or "" + ).strip() + if target and priority: + text = f"已将{target}的优先级调整为{priority}。" + elif target: + text = f"已调整{target}的优先级。" + else: + text = "已调整任务优先级。" + elif op == "task.status_transition": + status = str( + params.get("status") + or (execution_result.get("task") if isinstance(execution_result.get("task"), dict) else {}).get("status") + or "" + ).strip() + if target and status: + text = f"已将{target}状态更新为{status}。" + elif target: + text = f"已更新{target}状态。" + else: + text = "已更新任务状态。" + elif op == "task.delete": + text = f"已删除{target}。" if target else "已删除任务。" + elif op == "personnel.role_update": + role = str(params.get("role") or (execution_result.get("user") if isinstance(execution_result.get("user"), dict) else {}).get("role") or "").strip() + if target and role: + text = f"已将{target}的角色更新为{role}。" + elif target: + text = f"已更新{target}的角色。" + else: + text = "已更新人员角色。" + elif op == "personnel.workload_update": + workload = execution_result.get("workload") if isinstance(execution_result.get("workload"), dict) else {} + max_hours = workload.get("max_weekly_hours") or params.get("max_weekly_hours") + if target and max_hours is not None: + text = f"已将{target}的每周可用工时更新为{max_hours}。" + elif target: + text = f"已更新{target}的工时设置。" + else: + text = "已更新人员工时。" + elif op == "personnel.availability_update": + status = str(params.get("status") or (execution_result.get("user") if isinstance(execution_result.get("user"), dict) else {}).get("status") or "").strip() + if target and status: + text = f"已将{target}的可用性更新为{status}。" + elif target: + text = f"已更新{target}的可用性。" + else: + text = "已更新人员可用性。" + elif op == "knowledge.doc_upsert": + text = f"已更新{target}。" if target else "已更新知识文档。" + elif op == "knowledge.chunk_upsert": + chunk_id = _safe_int(execution_result.get("chunk_id")) + doc_id = _safe_int(execution_result.get("doc_id")) + if chunk_id is not None and doc_id is not None: + text = f"已更新知识分片#{chunk_id},归属文档#{doc_id}。" + elif chunk_id is not None: + text = f"已更新知识分片#{chunk_id}。" + else: + text = "已更新知识分片。" + elif op == "knowledge.link_upsert": + relation_id = _safe_int(execution_result.get("relation_id")) + if relation_id is not None: + text = f"已建立知识关联#{relation_id}。" + else: + text = "已建立知识关联。" + elif op == "knowledge.reindex": + scope = str(execution_result.get("scope") or params.get("scope") or "tenant").strip().lower() + if scope == "document": + text = "已完成知识文档索引重建。" + elif scope == "project": + text = "已完成项目知识索引重建。" + else: + text = "已完成知识索引重建。" + elif op == "notification.create": + notification_id = _safe_int(execution_result.get("notification_id")) + if target and notification_id is not None: + text = f"已创建{target}并发送。" + elif notification_id is not None: + text = f"已创建通知#{notification_id}并发送。" + else: + text = "已创建通知并发送。" + elif op == "notification.ack": + text = f"已确认{target}。" if target else "已确认通知。" + elif op == "notification.close": + text = f"已关闭{target}。" if target else "已关闭通知。" + else: + return None + return { + "receipt_phase": self.receipt_phase_action, + "template_key": "action_succeeded", + "payload": { + "intent": intent.name, + "operation_code": op, + "target": target or None, + }, + "text": text, + } + def render( self, *, @@ -73,6 +405,7 @@ class ReplyRenderer: decision_record: dict[str, Any], execution_result: dict[str, Any], ) -> dict[str, Any]: + operation_code = self._operation_code(intent=intent, execution_result=execution_result) if work_state == self.state_waiting_manual: if intent.name in {INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: followup_text = str(execution_result.get("followup_reply") or "").strip() @@ -96,6 +429,14 @@ class ReplyRenderer: }, "text": pending_text, } + mutation_pending = self._render_mutation_pending( + operation_code=operation_code, + intent=intent, + decision_record=decision_record, + execution_result=execution_result, + ) + if mutation_pending is not None: + return mutation_pending gate_message = str(((decision_record.get("approval_requirement") or {}).get("message")) or "").strip() if not gate_message: gate_message = "已进入待人工确认队列。" @@ -174,6 +515,13 @@ class ReplyRenderer: }, "text": text, } + mutation_success = self._render_mutation_success( + operation_code=operation_code, + intent=intent, + execution_result=execution_result, + ) + if mutation_success is not None: + return mutation_success if intent.name == INTENT_SIGNAL_SOURCE_ONBOARD: signal_id = _safe_int(execution_result.get("signal_id")) workflow = execution_result.get("signal_source_workflow") if isinstance(execution_result.get("signal_source_workflow"), dict) else {} diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py index 71c93d16..e6594f4f 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_typed_executor_common.py @@ -27,17 +27,71 @@ from ....utils.timezone import now_cn_naive from ...adaptive_rag_service import AdaptiveRAGService from .ai_pmo_domain_workflow import SignalSourceDomainWorkflow from ..ai_pmo_intent import ( + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_PROJECT_CREATE, INTENT_QUERY, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, LEGACY_INTENT_ROUTE_MAP, ROUTE_ACTION_CREATE, + ROUTE_ACTION_UPDATE, + ROUTE_ACTION_ARCHIVE, + ROUTE_ACTION_REOPEN, + ROUTE_ACTION_DELETE, + ROUTE_ACTION_ASSIGN, + ROUTE_ACTION_PRIORITIZE, + ROUTE_ACTION_STATUS_TRANSITION, + ROUTE_ACTION_ROLE_UPDATE, + ROUTE_ACTION_WORKLOAD_UPDATE, + ROUTE_ACTION_AVAILABILITY_UPDATE, + ROUTE_ACTION_DOC_UPSERT, + ROUTE_ACTION_CHUNK_UPSERT, + ROUTE_ACTION_LINK_UPSERT, + ROUTE_ACTION_REINDEX, + ROUTE_ACTION_ACK, + ROUTE_ACTION_CLOSE, ROUTE_ACTION_DISCOVER, ROUTE_PROJECT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER, + ROUTE_PROJECT_UPDATE, + ROUTE_PROJECT_ARCHIVE, + ROUTE_PROJECT_REOPEN, + ROUTE_PROJECT_DELETE, ROUTE_TASK_CREATE, + ROUTE_TASK_UPDATE, + ROUTE_TASK_ASSIGN, + ROUTE_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION, + ROUTE_TASK_DELETE, + ROUTE_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX, + ROUTE_NOTIFICATION_CREATE, + ROUTE_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE, CanonicalIntent, IntentResolver, RouteResolver, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py index ce35ad55..557815c0 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/ai_pmo_workflow_runtime.py @@ -10,6 +10,10 @@ from ..ai_pmo_intent import ( INTENT_FOLLOWUP_CONTINUE, ROUTE_ACTION_DISCOVER, ROUTE_FAMILY_FOLLOWUP, + ROUTE_FAMILY_KNOWLEDGE, + ROUTE_FAMILY_NOTIFICATION, + ROUTE_FAMILY_PERSONNEL, + ROUTE_FAMILY_PROJECT, ROUTE_FAMILY_PROJECT_CREATE, ROUTE_FAMILY_PROJECT_SIGNAL, ROUTE_FAMILY_QUERY, @@ -188,6 +192,31 @@ class TaskCreateWorkflowHandler(WorkflowHandler): ) +class MutationWorkflowHandler(WorkflowHandler): + async def start( + self, + *, + route: WorkflowRoute, + intent: CanonicalIntent, + message_text: str, + sender: str, + operator_user_id: Optional[int], + source_system: str, + source_message_id: str, + channel_ref: Optional[str], + ) -> dict[str, object]: + _ = route + return await self.kernel.executor.execute( + intent=intent, + message_text=message_text, + sender=sender, + operator_user_id=operator_user_id, + source_system=source_system, + source_message_id=source_message_id, + channel_ref=channel_ref, + ) + + class SignalSourceWorkflowHandler(WorkflowHandler): async def start( self, @@ -256,6 +285,10 @@ class WorkflowRegistry: ROUTE_FAMILY_QUERY: QueryWorkflowHandler(kernel), ROUTE_FAMILY_PROJECT_CREATE: ProjectCreateWorkflowHandler(kernel), ROUTE_FAMILY_TASK: TaskCreateWorkflowHandler(kernel), + ROUTE_FAMILY_PROJECT: MutationWorkflowHandler(kernel), + ROUTE_FAMILY_PERSONNEL: MutationWorkflowHandler(kernel), + ROUTE_FAMILY_KNOWLEDGE: MutationWorkflowHandler(kernel), + ROUTE_FAMILY_NOTIFICATION: MutationWorkflowHandler(kernel), ROUTE_FAMILY_PROJECT_SIGNAL: ProjectSignalWorkflowHandler(kernel), ROUTE_FAMILY_SIGNAL_SOURCE: SignalSourceWorkflowHandler(kernel), ROUTE_FAMILY_FOLLOWUP: WorkflowHandler(kernel), diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py index 413697a4..3d448c96 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/closure/ai_pmo_kernel_mixins_closure_payload.py @@ -3,6 +3,7 @@ from __future__ import annotations from .... import ai_pmo_kernel_common_core as _common +from app.utils.thread_context import build_feishu_thread_key for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -95,16 +96,19 @@ class KernelClosurePayloadMixin: owner_user_id: Optional[int] = None, project_id: Optional[int] = None, captured_at: Optional[Any] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: normalized_message = self._normalize_dialogue_text(message_text, max_len=600) turns: list[dict[str, str]] = [] normalized_source_system = str(source_system or "").strip().lower() normalized_channel = str(channel_ref or "").strip() if normalized_source_system == "feishu" and normalized_channel: - conversation_key = self._build_feishu_rag_conversation_key( + conversation_key = build_feishu_thread_key( source_chat_id=normalized_channel, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, ) session_id = AdaptiveRAGService._external_session_id_from_key(conversation_key) if session_id is not None: diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py index 869f1faa..d056e799 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/followup/ai_pmo_kernel_mixins_followup_verification.py @@ -3,6 +3,7 @@ from __future__ import annotations from .... import ai_pmo_kernel_common_core as _common +from app.utils.thread_context import build_feishu_thread_key for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -36,8 +37,64 @@ class KernelFollowupVerificationMixin: return {"verified": bool(str(execution_result.get("answer") or "").strip()), "issues": []} if intent.name == INTENT_PROJECT_CREATE: return {"verified": _safe_int(execution_result.get("project_id")) is not None, "issues": []} + if intent.name in { + "project.update", + "project.archive", + "project.reopen", + "project.delete", + }: + return { + "verified": _safe_int(execution_result.get("project_id")) is not None + or bool(execution_result.get("deleted")), + "issues": [], + } if intent.name == INTENT_TASK_CREATE: return {"verified": _safe_int(execution_result.get("task_id")) is not None, "issues": []} + if intent.name in { + "task.update", + "task.assign", + "task.prioritize", + "task.status_transition", + "task.delete", + }: + return { + "verified": _safe_int(execution_result.get("task_id")) is not None + or bool(execution_result.get("deleted")), + "issues": [], + } + if intent.name in { + "personnel.role_update", + "personnel.workload_update", + "personnel.availability_update", + }: + return { + "verified": _safe_int(execution_result.get("user_id")) is not None, + "issues": [], + } + if intent.name in { + "knowledge.doc_upsert", + "knowledge.chunk_upsert", + "knowledge.link_upsert", + "knowledge.reindex", + }: + return { + "verified": _safe_int(execution_result.get("doc_id")) + is not None + or _safe_int(execution_result.get("chunk_id")) is not None + or _safe_int(execution_result.get("relation_id")) is not None + or bool(execution_result.get("reindexed")), + "issues": [], + } + if intent.name in { + "notification.create", + "notification.ack", + "notification.close", + }: + return { + "verified": _safe_int(execution_result.get("notification_id")) is not None + or bool(execution_result.get("notification_ids")), + "issues": [], + } if intent.name in {INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD}: signal_ids = _normalize_signal_id_list(execution_result.get("signal_ids")) signal_id = _safe_int(execution_result.get("signal_id")) @@ -113,13 +170,13 @@ class KernelFollowupVerificationMixin: source_chat_id: Optional[str], source_sender_open_id: Optional[str], sender: Optional[str], + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> str: - chat = str(source_chat_id or "").strip() - if not chat: - return "" - actor = str(source_sender_open_id or "").strip() - if not actor: - actor = str(sender or "").strip().lower() - actor = re.sub(r"\s+", "", actor) - actor = actor[:96] if actor else "unknown" - return f"feishu:{chat}:{actor}" + return build_feishu_thread_key( + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, + ) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py index aba7f514..bcd25372 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_render.py @@ -106,6 +106,8 @@ class KernelIngressRenderMixin: @staticmethod def _primary_operation_code(intent_name: str) -> Optional[str]: normalized_route = RouteResolver.normalize_route_key(intent_name) + if normalized_route in INTENT_CANDIDATES: + return normalized_route if normalized_route == ROUTE_QUERY_ANSWER: return "query.answer" if normalized_route == ROUTE_PROJECT_CREATE: @@ -130,8 +132,36 @@ class KernelIngressRenderMixin: return "query.answer" if intent_name == INTENT_PROJECT_CREATE: return "project.create" + if intent_name in { + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, + }: + return intent_name if intent_name == INTENT_TASK_CREATE: return "task.create" + if intent_name in { + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + }: + return intent_name + if intent_name in { + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, + }: + return intent_name if intent_name == INTENT_SIGNAL_CREATE: return "signal.enter" if intent_name == INTENT_SIGNAL_SOURCE_ONBOARD: @@ -144,7 +174,34 @@ class KernelIngressRenderMixin: def _risk_level_for_intent(intent_name: str) -> str: if intent_name in {INTENT_PROJECT_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD}: return "high" - if intent_name in {INTENT_TASK_CREATE, INTENT_SIGNAL_CREATE, INTENT_FOLLOWUP_CONFIRM, INTENT_FOLLOWUP_CONTINUE}: + if intent_name in { + INTENT_PROJECT_DELETE, + INTENT_PROJECT_ARCHIVE, + INTENT_TASK_DELETE, + }: + return "high" + if intent_name in { + INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_REOPEN, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, + INTENT_SIGNAL_CREATE, + INTENT_FOLLOWUP_CONFIRM, + INTENT_FOLLOWUP_CONTINUE, + }: return "medium" return "low" diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py index c8580c4d..a064201e 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/ingress/ai_pmo_kernel_mixins_ingress_storage.py @@ -60,9 +60,52 @@ class KernelIngressStorageMixin: if op == "project.create": entity_id = _safe_int(execution_result.get("project_id")) return ("project", str(entity_id)) if entity_id is not None else ("project", None) + if op in { + "project.update", + "project.archive", + "project.reopen", + "project.delete", + }: + entity_id = _safe_int(execution_result.get("project_id")) + return ("project", str(entity_id)) if entity_id is not None else ("project", None) if op == "task.create": entity_id = _safe_int(execution_result.get("task_id")) return ("task", str(entity_id)) if entity_id is not None else ("task", None) + if op in { + "task.update", + "task.assign", + "task.prioritize", + "task.status_transition", + "task.delete", + }: + entity_id = _safe_int(execution_result.get("task_id")) + return ("task", str(entity_id)) if entity_id is not None else ("task", None) + if op in { + "personnel.role_update", + "personnel.workload_update", + "personnel.availability_update", + }: + entity_id = _safe_int(execution_result.get("user_id")) + return ("user", str(entity_id)) if entity_id is not None else ("user", None) + if op in { + "knowledge.doc_upsert", + "knowledge.chunk_upsert", + "knowledge.link_upsert", + "knowledge.reindex", + }: + entity_id = _safe_int(execution_result.get("doc_id")) + if entity_id is None: + entity_id = _safe_int(execution_result.get("chunk_id")) + if entity_id is None: + entity_id = _safe_int(execution_result.get("relation_id")) + return ("knowledge", str(entity_id)) if entity_id is not None else ("knowledge", None) + if op in { + "notification.create", + "notification.ack", + "notification.close", + }: + entity_id = _safe_int(execution_result.get("notification_id")) + return ("notification", str(entity_id)) if entity_id is not None else ("notification", None) if op in {"signal.enter", ROUTE_PROJECT_SIGNAL_COMMIT, ROUTE_PROJECT_SIGNAL_CREATE}: entity_id = _safe_int(execution_result.get("signal_id")) return ("signal", str(entity_id)) if entity_id is not None else ("signal", None) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py index dbdd1e29..e022d1b5 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_llm.py @@ -19,24 +19,27 @@ class KernelIntentLLMMixin: session: Optional[ConversationSession], world_model: dict[str, Any], rule_intent: Optional[CanonicalIntent] = None, + session_last_intent_override: Optional[str] = None, + session_project_id_override: Optional[int] = None, ) -> Optional[CanonicalIntent]: + session_last_intent = ( + str(session_last_intent_override or "").strip() + if session_last_intent_override is not None + else str(getattr(session, "last_intent", "") or "").strip() if session is not None else "" + ) + if session_project_id_override is not None: + session_project_id = _safe_int(session_project_id_override) + elif session is not None: + session_project_id = _safe_int(getattr(session, "current_project_id", None)) + else: + session_project_id = None prompt = PROMPT_AI_PMO_INTENT_CLASSIFICATION.format( message_text=str(message_text or "")[:1200], - session_last_intent=str(getattr(session, "last_intent", "") or "").strip() if session is not None else "", - session_project_id=_safe_int(getattr(session, "current_project_id", None)) if session is not None else None, + session_last_intent=session_last_intent, + session_project_id=session_project_id, world_model_project_id=_safe_int(world_model.get("resolved_project_id")), candidate_intents_json=json.dumps(list(INTENT_CANDIDATES), ensure_ascii=False), - candidate_routes_json=json.dumps( - [ - ROUTE_QUERY_ANSWER, - ROUTE_PROJECT_CREATE, - ROUTE_TASK_CREATE, - ROUTE_PROJECT_SIGNAL_CREATE, - ROUTE_PROJECT_SIGNAL_DISCOVER, - ROUTE_SIGNAL_SOURCE_ONBOARD, - ], - ensure_ascii=False, - ), + candidate_routes_json=json.dumps(sorted(WORKFLOW_ROUTE_KEYS), ensure_ascii=False), ).strip() total_budget_seconds = _intent_llm_total_budget_seconds() stage_started_at = time.perf_counter() @@ -164,12 +167,14 @@ class KernelIntentLLMMixin: project_id=project_id, confidence=confidence, metadata={ - "context_source": "llm", - "stage": stage_label, - "reasoning": reasoning[:300], - "llm_function": function_name, - "route_tier": route_tier, - "route_complex": route_tier == "high", + "context_source": "llm", + "stage": stage_label, + "reasoning": reasoning[:300], + "llm_payload": payload, + "llm_parameters": payload.get("parameters") if isinstance(payload.get("parameters"), dict) else {}, + "llm_function": function_name, + "route_tier": route_tier, + "route_complex": route_tier == "high", "route_reasons": route_reasons, "timeout_seconds": llm_timeout_seconds, "total_budget_seconds": total_budget_seconds, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py index 404c2903..022affd5 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_resolve.py @@ -18,12 +18,18 @@ class KernelIntentResolveMixin: world_model: dict[str, Any], message_sent_at: Optional[Any] = None, session_snapshot: Optional[dict[str, Any]] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> tuple[CanonicalIntent, str]: + reply_anchor_present = bool( + isinstance(thread_context, dict) + and str(thread_context.get("reply_to_message_id") or "").strip() + ) if self._is_short_confirmation_message( message_text=message_text, session=session, message_sent_at=message_sent_at, session_snapshot=session_snapshot, + reply_anchor_present=reply_anchor_present, ) and session is not None: inherited = self._build_short_confirm_intent( message_text=message_text, @@ -50,6 +56,12 @@ class KernelIntentResolveMixin: session=session, world_model=world_model, rule_intent=None, + session_last_intent_override=( + str(getattr(session, "last_intent", "") or "").strip() if reply_anchor_present else "" + ), + session_project_id_override=( + _safe_int(getattr(session, "current_project_id", None)) if reply_anchor_present else None + ), ) if llm_intent is None: raise RuntimeError("intent_model_only_no_valid_llm_intent") diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py index e202035a..28673432 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/intent/ai_pmo_kernel_mixins_intent_rules.py @@ -276,7 +276,10 @@ class KernelIntentRulesMixin: session: Optional[ConversationSession], message_sent_at: Optional[Any] = None, session_snapshot: Optional[dict[str, Any]] = None, + reply_anchor_present: bool = False, ) -> bool: + if not reply_anchor_present: + return False snapshot = session_snapshot if isinstance(session_snapshot, dict) else {} if session is None and not snapshot: return False diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py index cbe121b4..16ce8e50 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py @@ -3,6 +3,7 @@ from __future__ import annotations from .... import ai_pmo_kernel_common_core as _common +from app.utils.thread_context import build_feishu_thread_key for _name, _value in vars(_common).items(): if not _name.startswith("__"): @@ -31,8 +32,29 @@ class KernelWorldModelSessionMixin: return None return session_project_id - async def _get_or_create_session(self, *, tenant_id: int, sender: str) -> ConversationSession: - sender_key = str(sender or "").strip()[:128] or "unknown" + async def _get_or_create_session( + self, + *, + tenant_id: int, + sender: str, + session_key: Optional[str] = None, + source_chat_id: Optional[str] = None, + source_sender_open_id: Optional[str] = None, + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, + ) -> ConversationSession: + sender_key = ( + str(session_key or "").strip() + or build_feishu_thread_key( + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, + ) + or str(sender or "").strip() + or "unknown" + )[:128] existing = ( await self.db.execute( select(ConversationSession).where( @@ -81,11 +103,25 @@ class KernelWorldModelSessionMixin: *, tenant_id: int, sender: str, + session: Optional[ConversationSession] = None, + session_key: Optional[str] = None, + source_chat_id: Optional[str] = None, + source_sender_open_id: Optional[str] = None, + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, current_project_id: Optional[int], last_intent: Optional[str], last_message_text: Optional[str], ) -> None: - row = await self._get_or_create_session(tenant_id=tenant_id, sender=sender) + row = session or await self._get_or_create_session( + tenant_id=tenant_id, + sender=sender, + session_key=session_key, + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + source_message_id=source_message_id, + thread_context=thread_context, + ) if current_project_id is not None: row.current_project_id = int(current_project_id) row.last_intent = (str(last_intent or "").strip()[:64] or None) diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py index 4550cb8a..ec3d7345 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_common_core/imports.py @@ -82,21 +82,60 @@ from ..ai_pmo_intent import ( INTENT_IGNORE, INTENT_LLM_FUNCTION_NAME, INTENT_PROJECT_CREATE, + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, INTENT_QUERY, INTENT_TASK_CREATE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, INTENT_SIGNAL_CREATE, INTENT_SIGNAL_SOURCE_ONBOARD, LEGACY_INTENT_ROUTE_MAP, ROUTE_ACTION_ANSWER, + ROUTE_ACTION_ACK, + ROUTE_ACTION_ARCHIVE, ROUTE_ACTION_CANCEL, + ROUTE_ACTION_CLOSE, ROUTE_ACTION_COMMIT, ROUTE_ACTION_CONFIRM, ROUTE_ACTION_CONTINUE, ROUTE_ACTION_CREATE, ROUTE_ACTION_DISCOVER, + ROUTE_ACTION_DELETE, + ROUTE_ACTION_ASSIGN, + ROUTE_ACTION_PRIORITIZE, + ROUTE_ACTION_REINDEX, + ROUTE_ACTION_REOPEN, + ROUTE_ACTION_ROLE_UPDATE, + ROUTE_ACTION_STATUS_TRANSITION, + ROUTE_ACTION_UPDATE, ROUTE_ACTION_ONBOARD, ROUTE_ACTION_REFRESH, + ROUTE_ACTION_WORKLOAD_UPDATE, + ROUTE_ACTION_AVAILABILITY_UPDATE, + ROUTE_ACTION_DOC_UPSERT, + ROUTE_ACTION_CHUNK_UPSERT, + ROUTE_ACTION_LINK_UPSERT, ROUTE_FAMILY_FOLLOWUP, + ROUTE_FAMILY_KNOWLEDGE, + ROUTE_FAMILY_NOTIFICATION, + ROUTE_FAMILY_PERSONNEL, + ROUTE_FAMILY_PROJECT, ROUTE_FAMILY_PROJECT_CREATE, ROUTE_FAMILY_PROJECT_SIGNAL, ROUTE_FAMILY_QUERY, @@ -105,12 +144,31 @@ from ..ai_pmo_intent import ( ROUTE_FOLLOWUP_CONFIRM, ROUTE_FOLLOWUP_CONTINUE, ROUTE_PROJECT_CREATE, + ROUTE_PROJECT_ARCHIVE, + ROUTE_PROJECT_DELETE, + ROUTE_PROJECT_REOPEN, + ROUTE_PROJECT_UPDATE, + ROUTE_NOTIFICATION_ACK, + ROUTE_NOTIFICATION_CLOSE, + ROUTE_NOTIFICATION_CREATE, + ROUTE_PERSONNEL_AVAILABILITY_UPDATE, + ROUTE_PERSONNEL_ROLE_UPDATE, + ROUTE_PERSONNEL_WORKLOAD_UPDATE, + ROUTE_KNOWLEDGE_CHUNK_UPSERT, + ROUTE_KNOWLEDGE_DOC_UPSERT, + ROUTE_KNOWLEDGE_LINK_UPSERT, + ROUTE_KNOWLEDGE_REINDEX, ROUTE_PROJECT_SIGNAL_CANCEL, ROUTE_PROJECT_SIGNAL_COMMIT, ROUTE_PROJECT_SIGNAL_CREATE, ROUTE_PROJECT_SIGNAL_DISCOVER, ROUTE_PROJECT_SIGNAL_REFRESH, ROUTE_QUERY_ANSWER, + ROUTE_TASK_ASSIGN, + ROUTE_TASK_DELETE, + ROUTE_TASK_PRIORITIZE, + ROUTE_TASK_STATUS_TRANSITION, + ROUTE_TASK_UPDATE, ROUTE_SIGNAL_SOURCE_ONBOARD, ROUTE_TASK_CREATE, SESSION_SHORT_CONFIRM_WINDOW_SECONDS, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py index 32261c4e..54a38e72 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_auto.py @@ -25,6 +25,7 @@ class KernelIngressFlowAutoMixin: started: float, fallback_intent: CanonicalIntent, resolved_project_for_session: Optional[int], + session: ConversationSession, log_perf_fn: Any, ) -> dict[str, Any]: work_item.state = STATE_PLANNED @@ -149,6 +150,7 @@ class KernelIngressFlowAutoMixin: await self._update_session( tenant_id=self.tenant_id, sender=sender, + session=session, current_project_id=final_project_id, last_intent=STATE_DRAFT_PENDING_CONFIRM, last_message_text=message_text[:200], @@ -331,6 +333,7 @@ class KernelIngressFlowAutoMixin: await self._update_session( tenant_id=self.tenant_id, sender=sender, + session=session, current_project_id=final_project_id, last_intent=final_intent_name, last_message_text=message_text[:200], diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py index 5151df2e..653fe0cb 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py @@ -62,7 +62,7 @@ class KernelIngressFlowEntryMixin: question=question_part, asked_by=None, asked_via=source_system, - project_id=project_hint_id, + project_id=None, source_chat_id=channel_ref, conversation_key=source_message_id, message_intent="mixed", @@ -98,6 +98,7 @@ class KernelIngressFlowEntryMixin: chat_type: Optional[str] = None, message_sent_at: Optional[Any] = None, session_snapshot: Optional[dict[str, Any]] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: return await self.process_ingress( source_system="feishu", @@ -110,6 +111,11 @@ class KernelIngressFlowEntryMixin: chat_type=chat_type, message_sent_at=message_sent_at, session_snapshot=session_snapshot, + ingress_options=( + {"thread_context": dict(thread_context)} + if isinstance(thread_context, dict) and thread_context + else None + ), ) async def process_ingress( @@ -131,6 +137,7 @@ class KernelIngressFlowEntryMixin: started = time.perf_counter() perf_work_item_id: Optional[int] = None perf_intent_name = "" + thread_context = self._extract_thread_context_from_ingress_options(ingress_options) def _log_perf(stage_name: str, stage_started_at: float) -> None: elapsed_seconds = time.perf_counter() - stage_started_at perf_line = ( @@ -150,6 +157,7 @@ class KernelIngressFlowEntryMixin: captured_at=captured_at, chat_type=chat_type, project_hint_id=project_hint_id, + thread_context=thread_context, ) if ingress_prepare.get("early_result") is not None: return ingress_prepare["early_result"] @@ -160,7 +168,15 @@ class KernelIngressFlowEntryMixin: operator = ingress_prepare["operator"] work_item = ingress_prepare["work_item"] session = ingress_prepare["session"] - if str(getattr(session, "last_intent", "") or "").strip() == STATE_DRAFT_PENDING_CONFIRM: + self._merge_ingress_thread_context( + ingest_event=ingest_event, + ingress_options=ingress_options, + ) + if self._should_route_to_draft_confirmation( + session=session, + message_text=clean_text, + ingress_options=ingress_options, + ): draft_capture = await self._maybe_handle_draft_confirmation_ingress( work_item=work_item, ingest_event=ingest_event, @@ -172,6 +188,7 @@ class KernelIngressFlowEntryMixin: channel_ref=channel_ref, message_text=clean_text, started=started, + ingress_options=ingress_options, ) if draft_capture is not None: return draft_capture @@ -208,6 +225,7 @@ class KernelIngressFlowEntryMixin: world_model=world_model, message_sent_at=message_sent_at, session_snapshot=session_snapshot, + thread_context=thread_context, ) intent = self._enrich_signal_source_intent_from_closure( intent=intent, @@ -229,6 +247,12 @@ class KernelIngressFlowEntryMixin: **(intent.metadata or {}), "rag_request_context": dict(rag_request_context), } + if thread_context: + intent.metadata = { + **(intent.metadata or {}), + "thread_context": dict(thread_context), + "session_key": str(ingress_prepare.get("session_key") or "").strip() or None, + } workflow_route = self.route_resolver.from_canonical( canonical_intent=intent, message_text=clean_text, @@ -258,6 +282,7 @@ class KernelIngressFlowEntryMixin: return await self._finalize_ignored_ingress( work_item=work_item, ingest_event=ingest_event, + session=session, synthetic_message_id=synthetic_message_id, clean_text=clean_text, intent=intent, @@ -348,6 +373,7 @@ class KernelIngressFlowEntryMixin: stage_started_at = time.perf_counter() capture_result = await self._finalize_waiting_manual_ingress( work_item=work_item, + session=session, intent=intent, world_model=world_model, decision_payload=decision_payload, @@ -377,13 +403,14 @@ class KernelIngressFlowEntryMixin: message_text=clean_text, sender=sender, channel_ref=channel_ref, - source_sender_open_id=source_sender_open_id, - ingest_event=ingest_event, - started=started, - fallback_intent=fallback_intent, - resolved_project_for_session=resolved_project_for_session, - log_perf_fn=_log_perf, - ) + source_sender_open_id=source_sender_open_id, + ingest_event=ingest_event, + started=started, + fallback_intent=fallback_intent, + resolved_project_for_session=resolved_project_for_session, + session=session, + log_perf_fn=_log_perf, + ) except Exception as exc: await self._persist_kernel_failure_with_fresh_session( work_item_id=_safe_int(getattr(work_item, "id", None)), @@ -414,6 +441,7 @@ class KernelIngressFlowEntryMixin: channel_ref: Optional[str], message_text: str, started: float, + ingress_options: Optional[dict[str, Any]], ) -> Optional[dict[str, Any]]: from ..draft_confirm_handler import DraftConfirmHandler @@ -426,6 +454,7 @@ class KernelIngressFlowEntryMixin: channel_ref=channel_ref, source_sender_open_id=source_sender_open_id, expected_version=None, + thread_context=self._extract_thread_context_from_ingress_options(ingress_options), ) if not bool(decision.get("handled")): return None @@ -513,6 +542,7 @@ class KernelIngressFlowEntryMixin: await self._update_session( tenant_id=self.tenant_id, sender=sender, + session=session, current_project_id=_safe_int(decision.get("project_id")), last_intent=next_session_intent, last_message_text=message_text[:200], @@ -548,6 +578,78 @@ class KernelIngressFlowEntryMixin: classification="command", ) + @staticmethod + def _extract_thread_context_from_ingress_options( + ingress_options: Optional[dict[str, Any]], + ) -> dict[str, Optional[str]]: + if not isinstance(ingress_options, dict): + return {} + raw_context = ingress_options.get("thread_context") + if not isinstance(raw_context, dict): + return {} + normalized: dict[str, Optional[str]] = {} + for key in ("thread_id", "root_message_id", "reply_to_message_id"): + value = str(raw_context.get(key) or "").strip() + if value: + normalized[key] = value[:128] + return normalized + + @classmethod + def _has_explicit_draft_reply_anchor( + cls, + *, + message_text: str, + ingress_options: Optional[dict[str, Any]], + ) -> bool: + from ..draft_confirm_handler import DraftConfirmHandler + + if DraftConfirmHandler._extract_work_item_id(message_text) is not None: + return True + thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) + return bool(str(thread_context.get("reply_to_message_id") or "").strip()) + + @classmethod + def _should_route_to_draft_confirmation( + cls, + *, + session: ConversationSession, + message_text: str, + ingress_options: Optional[dict[str, Any]], + ) -> bool: + _ = session + from ..draft_confirm_handler import DraftConfirmHandler + + if DraftConfirmHandler._extract_work_item_id(message_text) is not None: + return True + + thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) + if not str(thread_context.get("reply_to_message_id") or "").strip(): + return False + + action, _ = DraftConfirmHandler.parse_action_from_text(message_text=message_text) + return action in { + DraftConfirmHandler._ACTION_CONFIRM, + DraftConfirmHandler._ACTION_REVISE, + DraftConfirmHandler._ACTION_CANCEL, + } + + @classmethod + def _merge_ingress_thread_context( + cls, + *, + ingest_event: IngestEvent, + ingress_options: Optional[dict[str, Any]], + ) -> None: + thread_context = cls._extract_thread_context_from_ingress_options(ingress_options) + if not thread_context: + return + raw_payload = getattr(ingest_event, "payload", None) + payload = raw_payload if isinstance(raw_payload, dict) else {} + ingest_event.payload = { + **payload, + **thread_context, + } + async def _persist_kernel_failure_with_fresh_session( self, *, diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py index c13065ed..a3a613f8 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_prepare_manual.py @@ -1,6 +1,7 @@ from __future__ import annotations from .shared import * +from app.utils.thread_context import build_feishu_thread_key class KernelIngressFlowPrepareManualMixin: async def _prepare_ingress_envelope( @@ -15,6 +16,7 @@ class KernelIngressFlowPrepareManualMixin: captured_at: Optional[datetime], chat_type: Optional[str], project_hint_id: Optional[int], + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: clean_text = self._normalize_text(message_text) if not clean_text: @@ -50,6 +52,26 @@ class KernelIngressFlowPrepareManualMixin: } } + session_key = ( + build_feishu_thread_key( + source_chat_id=channel_ref, + source_sender_open_id=source_sender_open_id, + sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, + ) + if str(source_system or "").strip().lower() == "feishu" + else "" + ) + logger.info( + "ai_pmo_ingress_session_key_snapshot", + source_system=source_system, + source_message_id=source_message_id, + channel_ref=channel_ref, + session_key=session_key or None, + thread_context={k: str(v)[:128] for k, v in (thread_context or {}).items()} if isinstance(thread_context, dict) else None, + ) + synthetic_message_id = self._build_source_message_id( source_system=source_system, source_message_id=source_message_id, @@ -66,6 +88,7 @@ class KernelIngressFlowPrepareManualMixin: source_message_id=synthetic_message_id, captured_at=captured_at, project_id=project_hint_id, + thread_context=thread_context, ) ingest_event, created = await self._claim_ingress_event( @@ -103,7 +126,15 @@ class KernelIngressFlowPrepareManualMixin: source_message_id=synthetic_message_id, owner_user_id=_safe_int(getattr(operator, "id", None)), ) - session = await self._get_or_create_session(tenant_id=self.tenant_id, sender=sender) + session = await self._get_or_create_session( + tenant_id=self.tenant_id, + sender=sender, + session_key=session_key, + source_chat_id=channel_ref, + source_sender_open_id=source_sender_open_id, + source_message_id=synthetic_message_id, + thread_context=thread_context, + ) return { "early_result": None, "clean_text": clean_text, @@ -113,6 +144,7 @@ class KernelIngressFlowPrepareManualMixin: "operator": operator, "work_item": work_item, "session": session, + "session_key": session_key or None, } async def _finalize_ignored_ingress( @@ -120,6 +152,7 @@ class KernelIngressFlowPrepareManualMixin: *, work_item: Any, ingest_event: Any, + session: ConversationSession, synthetic_message_id: str, clean_text: str, intent: CanonicalIntent, @@ -144,6 +177,7 @@ class KernelIngressFlowPrepareManualMixin: await self._update_session( tenant_id=self.tenant_id, sender=sender, + session=session, current_project_id=resolved_project_for_session, last_intent=intent.name, last_message_text=clean_text[:200], @@ -162,6 +196,7 @@ class KernelIngressFlowPrepareManualMixin: self, *, work_item: Any, + session: ConversationSession, intent: CanonicalIntent, world_model: dict[str, Any], decision_payload: dict[str, Any], @@ -209,7 +244,7 @@ class KernelIngressFlowPrepareManualMixin: work_state=STATE_WAITING_MANUAL, intent=intent, decision_record={"approval_requirement": policy.get("approval_requirement")}, - execution_result={}, + execution_result=work_item.execution_result if isinstance(work_item.execution_result, dict) else {}, ) work_item.reply_template_key = str(render["template_key"]) await self._project_waiting_manual( @@ -271,6 +306,7 @@ class KernelIngressFlowPrepareManualMixin: await self._update_session( tenant_id=self.tenant_id, sender=sender, + session=session, current_project_id=_safe_int(work_item.project_id), last_intent=intent.name, last_message_text=message_text[:200], diff --git a/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py index 6e17aa4f..a04e3fd5 100644 --- a/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py +++ b/backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py @@ -82,6 +82,31 @@ class KernelWorldModelCoreBuilderMixin: risk_summary = await self._build_risk_summary(project_id=resolved_project_id, query_tables=queried_tables) dependencies = self._build_dependency_summary(charter=charter, task_summary=task_summary) recent_signals = await self._build_recent_signal_summary(project_id=resolved_project_id, query_tables=queried_tables) + self._record_world_model_query_table(queried_tables, "projects") + active_projects_rows = ( + await self.db.execute( + select(Project.id, Project.name, Project.title, Project.description, Project.status, Project.owner_id) + .where( + and_( + Project.tenant_id == self.tenant_id, + Project.status != "inactive", + ) + ) + .order_by(Project.id.desc()) + .limit(20) + ) + ).all() + active_projects = [ + { + "id": int(row.id), + "name": str(row.name or ""), + "title": str(row.title or row.name or ""), + "description": str(row.description or ""), + "status": str(row.status or ""), + "owner_id": _safe_int(row.owner_id), + } + for row in active_projects_rows + ] world_model: WorldModel = { "sender": sender, @@ -98,6 +123,7 @@ class KernelWorldModelCoreBuilderMixin: "risk_summary": risk_summary, "dependencies": dependencies, "recent_signals": recent_signals, + "active_projects": active_projects, "decision_context_snapshot": { "captured_at": _utc_now_naive().isoformat(), "project_id": resolved_project_id, diff --git a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_mutations.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_mutations.py new file mode 100644 index 00000000..a4741f6c --- /dev/null +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_mutations.py @@ -0,0 +1,1857 @@ +"""Mutation intent execution for typed executor.""" + +from __future__ import annotations + +from typing import Iterable, Optional + +from sqlalchemy import and_, delete, func, or_, select, update + +from ....models import ( + AIOrchestrationRun, + AIWorkerExecution, + Budget, + ConversationSession, + CostTracking, + Experiment, + KnowledgeBaseChunk, + KnowledgeBaseDoc, + KGEntity, + KGRelation, + Notification, + OperationExecutionLedger, + OperationLog, + Project, + ProjectSignalLink, + SignalSourceRegistry, + Task, + TruthNode, + User, + UserProject, + UserSkillBinding, + UserWorkload, + WorkItem, +) +from ....models.enums import UserStatus +from ....services.adaptive_rag_service import AdaptiveRAGService +from ....services.context_cache_service import context_cache_service +from ....services.notification_service import NotificationService +from ....services.operation_log_service import log_operation +from ....services.project_knowledge_graph_service import ProjectKnowledgeGraphService +from ....services.task_persistence_core import normalize_due_date, normalize_estimated_hours, normalize_int_list, normalize_task_priority, normalize_task_status +from ....services.workload import ensure_user_workload, recalculate_user_workload_ratio +from ....services.workload.shared import LEVEL_HOURS_MULTIPLIER +from ....utils.title import compact_title +from ....utils.timezone import now_cn_naive +from .shared import * # noqa: F401, F403 + + +WORLD_MODEL_STATIC_CACHE_KEY_PREFIX = "wm:static" + + +class ExecuteMutationsMixin: + """Handle non-create write intents with typed DB mutations.""" + + @staticmethod + def _intent_parameters(intent) -> dict[str, Any]: + metadata = intent.metadata if isinstance(intent.metadata, dict) else {} + for key in ("llm_parameters", "parameters"): + params = metadata.get(key) + if isinstance(params, dict) and params: + return params + payload = metadata.get("llm_payload") + if isinstance(payload, dict): + params = payload.get("parameters") + if isinstance(params, dict) and params: + return params + return {} + + @staticmethod + def _clean_text(value: Any, *, max_len: int = 5000) -> Optional[str]: + text = str(value or "").strip() + if not text: + return None + return text[:max_len] + + @staticmethod + def _clean_title(value: Any, *, max_len: int = 80) -> Optional[str]: + title = compact_title(str(value or "").strip(), max_len=max_len) + return title or None + + @staticmethod + def _normalize_project_status(value: Any, *, default: str = "active") -> str: + raw = str(value or "").strip().lower() + if raw in {"active", "archived", "done", "inactive"}: + return raw + if raw in {"已归档", "归档", "archive"}: + return "archived" + if raw in {"完成", "completed", "closed"}: + return "done" + if raw in {"停用", "inactive"}: + return "inactive" + return default + + @staticmethod + def _normalize_user_status(value: Any, *, default: str = "active") -> str: + raw = str(value or "").strip().lower() + if raw in {"active", "away", "offline"}: + return raw + if raw in {"离开", "away"}: + return "away" + if raw in {"离线", "停用", "offline"}: + return "offline" + return default + + @staticmethod + def _serialize_project(project: Project) -> dict[str, Any]: + return { + "id": int(project.id), + "title": str(project.title or project.name or "").strip(), + "name": str(project.name or project.title or "").strip(), + "description": project.description, + "status": project.status, + "domain_id": int(project.domain_id) if project.domain_id is not None else None, + "assigned_role": project.assigned_role, + "owner_id": int(project.owner_id) if project.owner_id is not None else None, + "signal_count": int(project.signal_count or 0), + "created_at": project.created_at.isoformat() if project.created_at else None, + "updated_at": project.updated_at.isoformat() if project.updated_at else None, + } + + @staticmethod + def _serialize_task(task: Task) -> dict[str, Any]: + return { + "id": int(task.id), + "truth_node_id": int(task.truth_node_id), + "project_id": int(task.project_id), + "assignee_id": int(task.assignee_id) if task.assignee_id is not None else None, + "title": str(task.title or "").strip(), + "description": task.description, + "required_skill_tag_ids": list(task.required_skill_tag_ids or []), + "status": str(task.status or "").strip(), + "priority": str(task.priority or "").strip(), + "estimated_hours": float(task.estimated_hours) if task.estimated_hours is not None else None, + "due_date": task.due_date.isoformat() if task.due_date else None, + "completed_at": task.completed_at.isoformat() if task.completed_at else None, + "updated_at": task.updated_at.isoformat() if task.updated_at else None, + } + + @staticmethod + def _serialize_user_row(user: User) -> dict[str, Any]: + return { + "id": int(user.id), + "username": str(user.username or "").strip(), + "name": str(user.name or "").strip(), + "email": str(user.email or "").strip(), + "role": str(user.role or "").strip(), + "assigned_domains": user.assigned_domains or [], + "workload": int(user.workload or 0), + "status": str(user.status or "").strip(), + "is_active": bool(user.is_active), + "created_at": user.created_at.isoformat() if user.created_at else None, + "updated_at": user.updated_at.isoformat() if user.updated_at else None, + } + + @staticmethod + def _serialize_notification(row: Notification) -> dict[str, Any]: + return { + "id": int(row.id), + "user_id": int(row.user_id), + "title": str(row.title or "").strip(), + "content": row.content, + "category": row.category, + "source": row.source, + "entity_type": row.entity_type, + "entity_id": int(row.entity_id) if row.entity_id is not None else None, + "target_url": row.target_url, + "kind": row.kind, + "priority": row.priority, + "status": row.status, + "blocking_reason": row.blocking_reason, + "recommended_action": row.recommended_action, + "dedupe_key": row.dedupe_key, + "occurrence_count": int(row.occurrence_count or 1), + "payload": row.payload if isinstance(row.payload, dict) else None, + "is_read": bool(row.is_read), + "read_at": row.read_at.isoformat() if row.read_at else None, + "created_at": row.created_at.isoformat() if row.created_at else None, + "updated_at": row.updated_at.isoformat() if row.updated_at else None, + } + + @staticmethod + def _serialize_doc(doc: KnowledgeBaseDoc) -> dict[str, Any]: + return { + "id": int(doc.id), + "title": str(doc.title or "").strip(), + "file_type": str(doc.file_type or "").strip(), + "source_type": str(doc.source_type or "").strip(), + "file_url": doc.file_url, + "uploaded_by": int(doc.uploaded_by) if doc.uploaded_by is not None else None, + "status": str(doc.status or "").strip(), + "last_fetched_at": doc.last_fetched_at.isoformat() if doc.last_fetched_at else None, + "error_message": doc.error_message, + "created_at": doc.created_at.isoformat() if doc.created_at else None, + "updated_at": doc.updated_at.isoformat() if doc.updated_at else None, + } + + @staticmethod + def _mutation_failed(operation_code: str, message: str, **payload: Any) -> dict[str, Any]: + return { + "status": "failed_safe", + "operation_code": operation_code, + "error_message": message, + **payload, + } + + @staticmethod + def _mutation_pending(operation_code: str, message: str, **payload: Any) -> dict[str, Any]: + return { + "status": "pending_manual", + "operation_code": operation_code, + "error_message": message, + **payload, + } + + async def _lookup_project_candidates( + self, + *, + project_id: Optional[int], + project_title: Optional[str], + project_name: Optional[str], + ) -> list[Project]: + if project_id is not None: + row = ( + await self.db.execute( + select(Project).where( + and_( + Project.tenant_id == int(self.tenant_id), + Project.id == int(project_id), + ) + ) + ) + ).scalar_one_or_none() + return [row] if row is not None else [] + + raw = self._clean_title(project_title or project_name, max_len=80) + if not raw: + return [] + lowered = raw.lower() + rows = ( + await self.db.execute( + select(Project) + .where(Project.tenant_id == int(self.tenant_id)) + .where( + or_( + func.lower(Project.title) == lowered, + func.lower(Project.name) == lowered, + ) + ) + .order_by(Project.updated_at.desc(), Project.id.desc()) + ) + ).scalars().all() + if rows: + return list(rows) + return [] + + async def _resolve_project( + self, + *, + project_id: Optional[int], + project_title: Optional[str], + project_name: Optional[str], + ) -> tuple[Optional[Project], Optional[list[Project]]]: + candidates = await self._lookup_project_candidates( + project_id=project_id, + project_title=project_title, + project_name=project_name, + ) + if not candidates: + return None, None + if len(candidates) > 1 and project_id is None: + return None, candidates + return candidates[0], None + + async def _lookup_task_candidates( + self, + *, + task_id: Optional[int], + task_title: Optional[str], + project_id: Optional[int], + ) -> list[Task]: + if task_id is not None: + row = ( + await self.db.execute( + select(Task).where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.id == int(task_id), + ) + ) + ) + ).scalar_one_or_none() + return [row] if row is not None else [] + + raw = self._clean_title(task_title, max_len=120) + if not raw: + return [] + lowered = raw.lower() + stmt = select(Task).where(Task.tenant_id == int(self.tenant_id)) + if project_id is not None: + stmt = stmt.where(Task.project_id == int(project_id)) + rows = ( + await self.db.execute( + stmt.where(func.lower(Task.title) == lowered).order_by(Task.updated_at.desc(), Task.id.desc()) + ) + ).scalars().all() + return list(rows) + + async def _resolve_task( + self, + *, + task_id: Optional[int], + task_title: Optional[str], + project_id: Optional[int], + ) -> tuple[Optional[Task], Optional[list[Task]]]: + candidates = await self._lookup_task_candidates( + task_id=task_id, + task_title=task_title, + project_id=project_id, + ) + if not candidates: + return None, None + if len(candidates) > 1 and task_id is None: + return None, candidates + return candidates[0], None + + async def _resolve_user( + self, + *, + user_id: Optional[int], + user_name: Optional[str], + username: Optional[str] = None, + ) -> tuple[Optional[User], Optional[list[User]]]: + if user_id is not None: + row = ( + await self.db.execute( + select(User).where( + and_( + User.tenant_id == int(self.tenant_id), + User.id == int(user_id), + ) + ) + ) + ).scalar_one_or_none() + return row, None + + raw = self._clean_title(user_name or username, max_len=100) + if not raw: + return None, None + lowered = raw.lower() + rows = ( + await self.db.execute( + select(User) + .where(User.tenant_id == int(self.tenant_id)) + .where( + or_( + func.lower(User.name) == lowered, + func.lower(User.username) == lowered, + ) + ) + .order_by(User.updated_at.desc(), User.id.desc()) + ) + ).scalars().all() + if rows: + if len(rows) > 1: + return None, list(rows) + return rows[0], None + return None, None + + async def _resolve_notification( + self, + *, + notification_id: Optional[int], + user_id: Optional[int], + ) -> Notification | None: + if notification_id is None: + return None + stmt = select(Notification).where( + and_( + Notification.tenant_id == int(self.tenant_id), + Notification.id == int(notification_id), + ) + ) + if user_id is not None: + stmt = stmt.where(Notification.user_id == int(user_id)) + return (await self.db.execute(stmt)).scalar_one_or_none() + + @staticmethod + def _world_model_cache_key(tenant_id: int, project_id: int) -> str: + return f"{WORLD_MODEL_STATIC_CACHE_KEY_PREFIX}:{int(tenant_id)}:{int(project_id)}" + + async def _invalidate_project_cache( + self, + *, + project_id: Optional[int], + user_ids: Optional[Iterable[int]] = None, + ) -> None: + await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) + if project_id is not None: + await context_cache_service.delete_keys( + self._world_model_cache_key(int(self.tenant_id), int(project_id)) + ) + if user_ids: + for user_id in user_ids: + if _safe_int(user_id, 0) and _safe_int(user_id, 0) > 0: + await context_cache_service.invalidate_user_context( + tenant_id=self.tenant_id, + user_id=int(user_id), + ) + + async def _invalidate_user_project_caches(self, *, user_id: Optional[int]) -> None: + if user_id is None: + return + project_ids = ( + await self.db.execute( + select(UserProject.project_id).where( + and_( + UserProject.tenant_id == int(self.tenant_id), + UserProject.user_id == int(user_id), + ) + ) + ) + ).scalars().all() + for project_id in {int(pid) for pid in project_ids if pid is not None}: + await context_cache_service.delete_keys( + self._world_model_cache_key(int(self.tenant_id), int(project_id)) + ) + + async def _refresh_user_workload_without_commit(self, *, user_id: int) -> Optional[UserWorkload]: + workload = await ensure_user_workload(self.db, int(self.tenant_id), int(user_id)) + task_rows = ( + await self.db.execute( + select(Task).where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.assignee_id == int(user_id), + func.lower(Task.status).notin_(["done", "completed", "closed"]), + ) + ) + ) + ).scalars().all() + skill_rows = ( + await self.db.execute( + select( + UserSkillBinding.skill_tag_id, + UserSkillBinding.level, + UserSkillBinding.proficiency_level, + UserSkillBinding.proficiency, + ).where( + and_( + UserSkillBinding.tenant_id == int(self.tenant_id), + UserSkillBinding.user_id == int(user_id), + ) + ) + ) + ).all() + + def normalize_level( + level: Optional[str], + proficiency_level: Optional[str], + proficiency: Optional[int], + ) -> str: + raw = (level or proficiency_level or "").strip().lower() + if raw in LEVEL_HOURS_MULTIPLIER: + return raw + score = int(proficiency or 0) + if score >= 85: + return "expert" + if score >= 70: + return "advanced" + if score >= 50: + return "intermediate" + return "beginner" + + level_map: dict[int, str] = { + int(skill_tag_id): normalize_level(level, proficiency_level, proficiency) + for skill_tag_id, level, proficiency_level, proficiency in skill_rows + } + + def effective_estimated_hours(task: Task) -> float: + base = float(task.estimated_hours or 0.0) + if base <= 0: + return 0.0 + required_ids = normalize_int_list(task.required_skill_tag_ids) + if not required_ids: + return base + multipliers = [ + LEVEL_HOURS_MULTIPLIER.get(level_map[sid], 1.0) + for sid in required_ids + if sid in level_map + ] + if not multipliers: + return base + factor = sum(multipliers) / len(multipliers) + return round(base * factor, 2) + + workload.current_assigned_hours = round( + sum(effective_estimated_hours(task) for task in task_rows), + 2, + ) + await recalculate_user_workload_ratio(self.db, int(self.tenant_id), int(user_id), commit=False) + return workload + + async def _log_operation( + self, + *, + actor_user_id: Optional[int], + project_id: Optional[int], + entity_type: str, + entity_id: str, + action: str, + before_data: Optional[dict[str, Any]] = None, + after_data: Optional[dict[str, Any]] = None, + ) -> None: + if actor_user_id is None: + return + await log_operation( + self.db, + tenant_id=int(self.tenant_id), + user_id=int(actor_user_id), + project_id=int(project_id) if project_id is not None else None, + entity_type=entity_type, + entity_id=str(entity_id), + action=action, + before_data=before_data, + after_data=after_data, + caller="ai", + remark="ai_pmo_typed_intent", + ) + + async def _cleanup_project_dependencies(self, *, project_id: int) -> None: + task_ids_subquery = ( + select(Task.id) + .where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.project_id == int(project_id), + ) + ) + .scalar_subquery() + ) + + await self.db.execute( + update(CostTracking) + .where( + and_( + CostTracking.tenant_id == int(self.tenant_id), + CostTracking.task_id.in_(task_ids_subquery), + ) + ) + .values(task_id=None) + ) + await self.db.execute( + delete(AIWorkerExecution).where( + and_( + AIWorkerExecution.tenant_id == int(self.tenant_id), + AIWorkerExecution.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + delete(AIOrchestrationRun).where( + and_( + AIOrchestrationRun.tenant_id == int(self.tenant_id), + AIOrchestrationRun.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + delete(SignalSourceRegistry).where( + and_( + SignalSourceRegistry.tenant_id == int(self.tenant_id), + SignalSourceRegistry.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + delete(ProjectSignalLink).where( + and_( + ProjectSignalLink.tenant_id == int(self.tenant_id), + ProjectSignalLink.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + delete(Task).where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + delete(UserProject).where( + and_( + UserProject.tenant_id == int(self.tenant_id), + UserProject.project_id == int(project_id), + ) + ) + ) + await self.db.execute( + update(WorkItem) + .where( + and_( + WorkItem.tenant_id == int(self.tenant_id), + WorkItem.project_id == int(project_id), + ) + ) + .values(project_id=None) + ) + await self.db.execute( + update(OperationLog) + .where( + and_( + OperationLog.tenant_id == int(self.tenant_id), + OperationLog.project_id == int(project_id), + ) + ) + .values(project_id=None) + ) + await self.db.execute( + update(OperationExecutionLedger) + .where( + and_( + OperationExecutionLedger.tenant_id == int(self.tenant_id), + OperationExecutionLedger.project_id == int(project_id), + ) + ) + .values(project_id=None) + ) + await self.db.execute( + update(Experiment) + .where( + and_( + Experiment.tenant_id == int(self.tenant_id), + Experiment.project_id == int(project_id), + ) + ) + .values(project_id=None) + ) + await self.db.execute( + update(Budget) + .where( + and_( + Budget.tenant_id == int(self.tenant_id), + Budget.project_id == int(project_id), + ) + ) + .values(project_id=None) + ) + await self.db.execute( + update(ConversationSession) + .where( + and_( + ConversationSession.tenant_id == int(self.tenant_id), + ConversationSession.current_project_id == int(project_id), + ) + ) + .values(current_project_id=None) + ) + + async def _execute_mutation_intent( + self, + *, + intent, + message_text: str, + operator_user_id: Optional[int], + ) -> dict[str, Any]: + operation_code = str(intent.name or "").strip().lower() + params = self._intent_parameters(intent) + if operation_code == INTENT_PROJECT_UPDATE: + return await self._execute_project_update_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PROJECT_ARCHIVE: + return await self._execute_project_archive_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PROJECT_REOPEN: + return await self._execute_project_reopen_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PROJECT_DELETE: + return await self._execute_project_delete_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_TASK_UPDATE: + return await self._execute_task_update_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_TASK_ASSIGN: + return await self._execute_task_assign_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_TASK_PRIORITIZE: + return await self._execute_task_prioritize_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_TASK_STATUS_TRANSITION: + return await self._execute_task_status_transition_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_TASK_DELETE: + return await self._execute_task_delete_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PERSONNEL_ROLE_UPDATE: + return await self._execute_personnel_role_update_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PERSONNEL_WORKLOAD_UPDATE: + return await self._execute_personnel_workload_update_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_PERSONNEL_AVAILABILITY_UPDATE: + return await self._execute_personnel_availability_update_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_KNOWLEDGE_DOC_UPSERT: + return await self._execute_knowledge_doc_upsert_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_KNOWLEDGE_CHUNK_UPSERT: + return await self._execute_knowledge_chunk_upsert_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_KNOWLEDGE_LINK_UPSERT: + return await self._execute_knowledge_link_upsert_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_KNOWLEDGE_REINDEX: + return await self._execute_knowledge_reindex_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_NOTIFICATION_CREATE: + return await self._execute_notification_create_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_NOTIFICATION_ACK: + return await self._execute_notification_ack_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + if operation_code == INTENT_NOTIFICATION_CLOSE: + return await self._execute_notification_close_intent( + intent=intent, + params=params, + operator_user_id=operator_user_id, + ) + return self._mutation_failed( + operation_code, + "当前写意图尚未迁移到 AI PMO typed executor。", + intent=operation_code, + ) + + async def _execute_project_update_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project, candidates = await self._resolve_project( + project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), + project_title=params.get("project_title") or params.get("title"), + project_name=params.get("project_name"), + ) + if project is None: + if candidates: + candidate_ids = [int(row.id) for row in candidates[:5]] + return self._mutation_pending( + intent.name, + "当前项目名称对应多个项目,请补充 project_id 后再执行。", + candidate_project_ids=candidate_ids, + ) + return self._mutation_failed(intent.name, "未找到可更新的项目。", project_id=None) + + before = self._serialize_project(project) + if (title := self._clean_title(params.get("title"), max_len=80)) is not None: + project.title = title + project.name = title + if (description := self._clean_text(params.get("description"), max_len=4000)) is not None: + project.description = description + if params.get("status") is not None: + project.status = self._normalize_project_status(params.get("status"), default=project.status) + if params.get("domain_id") is not None: + try: + project.domain_id = int(params.get("domain_id")) + except (TypeError, ValueError): + return self._mutation_failed(intent.name, "domain_id 非法。", project_id=int(project.id)) + if params.get("assigned_role") is not None: + project.assigned_role = self._clean_text(params.get("assigned_role"), max_len=50) + + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(project.id), + entity_type="project", + entity_id=str(project.id), + action=intent.name, + before_data=before, + after_data=self._serialize_project(project), + ) + await self.db.commit() + await self.db.refresh(project) + await self._invalidate_project_cache(project_id=int(project.id)) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(project.id), + "project": self._serialize_project(project), + } + + async def _execute_project_archive_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project, candidates = await self._resolve_project( + project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), + project_title=params.get("project_title") or params.get("title"), + project_name=params.get("project_name"), + ) + if project is None: + if candidates: + return self._mutation_pending(intent.name, "项目名称重复,请补充 project_id。") + return self._mutation_failed(intent.name, "未找到可归档的项目。", project_id=None) + before = self._serialize_project(project) + project.status = "archived" + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(project.id), + entity_type="project", + entity_id=str(project.id), + action=intent.name, + before_data=before, + after_data=self._serialize_project(project), + ) + await self.db.commit() + await self.db.refresh(project) + await self._invalidate_project_cache(project_id=int(project.id)) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(project.id), + "project": self._serialize_project(project), + } + + async def _execute_project_reopen_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project, candidates = await self._resolve_project( + project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), + project_title=params.get("project_title") or params.get("title"), + project_name=params.get("project_name"), + ) + if project is None: + if candidates: + return self._mutation_pending(intent.name, "项目名称重复,请补充 project_id。") + return self._mutation_failed(intent.name, "未找到可重新打开的项目。", project_id=None) + before = self._serialize_project(project) + project.status = "active" + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(project.id), + entity_type="project", + entity_id=str(project.id), + action=intent.name, + before_data=before, + after_data=self._serialize_project(project), + ) + await self.db.commit() + await self.db.refresh(project) + await self._invalidate_project_cache(project_id=int(project.id)) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(project.id), + "project": self._serialize_project(project), + } + + async def _execute_project_delete_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project, candidates = await self._resolve_project( + project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), + project_title=params.get("project_title") or params.get("title"), + project_name=params.get("project_name"), + ) + if project is None: + if candidates: + return self._mutation_pending(intent.name, "项目名称重复,请补充 project_id。") + return self._mutation_failed(intent.name, "未找到可删除的项目。", project_id=None) + before = self._serialize_project(project) + task_assignee_ids = [ + int(uid) + for uid in ( + await self.db.execute( + select(Task.assignee_id).where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.project_id == int(project.id), + ) + ) + ) + ).scalars().all() + if uid is not None + ] + member_user_ids = [ + int(uid) + for uid in ( + await self.db.execute( + select(UserProject.user_id).where( + and_( + UserProject.tenant_id == int(self.tenant_id), + UserProject.project_id == int(project.id), + ) + ) + ) + ).scalars().all() + if uid is not None + ] + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(project.id), + entity_type="project", + entity_id=str(project.id), + action=intent.name, + before_data=before, + after_data={"deleted": True, "project_id": int(project.id), "project_title": before["title"]}, + ) + await self._cleanup_project_dependencies(project_id=int(project.id)) + await self.db.execute( + delete(Project).where( + and_( + Project.tenant_id == int(self.tenant_id), + Project.id == int(project.id), + ) + ) + ) + for assignee_id in sorted(set(task_assignee_ids)): + await self._refresh_user_workload_without_commit(user_id=int(assignee_id)) + await self.db.commit() + await self._invalidate_project_cache(project_id=int(project.id), user_ids=member_user_ids) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(project.id), + "deleted": True, + } + + async def _execute_task_update_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + task, candidates = await self._resolve_task( + task_id=_safe_int(params.get("task_id")), + task_title=params.get("task_title") or params.get("title"), + project_id=project_id, + ) + if task is None: + if candidates: + return self._mutation_pending(intent.name, "任务标题对应多个结果,请补充 task_id。") + return self._mutation_failed(intent.name, "未找到可更新的任务。", task_id=None) + before = self._serialize_task(task) + old_assignee_id = int(task.assignee_id) if task.assignee_id is not None else None + if (title := self._clean_title(params.get("title"), max_len=120)) is not None: + task.title = title + if (description := self._clean_text(params.get("description"), max_len=5000)) is not None: + task.description = description + if params.get("assignee_id") is not None: + try: + task.assignee_id = int(params.get("assignee_id")) + except (TypeError, ValueError): + return self._mutation_failed(intent.name, "assignee_id 非法。", task_id=int(task.id)) + if params.get("required_skill_tag_ids") is not None: + task.required_skill_tag_ids = normalize_int_list(params.get("required_skill_tag_ids")) + if params.get("priority") is not None: + task.priority = normalize_task_priority(params.get("priority")) + if params.get("estimated_hours") is not None: + try: + task.estimated_hours = normalize_estimated_hours(params.get("estimated_hours")) + except ValueError as exc: + return self._mutation_failed(intent.name, str(exc), task_id=int(task.id)) + if params.get("due_date") is not None: + try: + task.due_date = normalize_due_date(params.get("due_date")) + except ValueError as exc: + return self._mutation_failed(intent.name, str(exc), task_id=int(task.id)) + + touched_user_ids = {uid for uid in (old_assignee_id, int(task.assignee_id) if task.assignee_id else None) if uid is not None} + for uid in touched_user_ids: + await self._refresh_user_workload_without_commit(user_id=int(uid)) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(task.project_id), + entity_type="task", + entity_id=str(task.id), + action=intent.name, + before_data=before, + after_data=self._serialize_task(task), + ) + await self.db.commit() + await self.db.refresh(task) + await self._invalidate_project_cache(project_id=int(task.project_id), user_ids=touched_user_ids) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(task.project_id), + "task_id": int(task.id), + "task": self._serialize_task(task), + } + + async def _execute_task_assign_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + task, candidates = await self._resolve_task( + task_id=_safe_int(params.get("task_id")), + task_title=params.get("task_title") or params.get("title"), + project_id=project_id, + ) + if task is None: + if candidates: + return self._mutation_pending(intent.name, "任务标题对应多个结果,请补充 task_id。") + return self._mutation_failed(intent.name, "未找到可分配的任务。", task_id=None) + assignee, assignee_candidates = await self._resolve_user( + user_id=_safe_int(params.get("assignee_id")), + user_name=params.get("assignee_name") or params.get("user_name"), + username=params.get("username"), + ) + if assignee is None: + if assignee_candidates: + return self._mutation_pending(intent.name, "负责人名称对应多个用户,请补充 assignee_id。") + return self._mutation_failed(intent.name, "未找到可分配的负责人。", task_id=int(task.id)) + before = self._serialize_task(task) + old_assignee_id = int(task.assignee_id) if task.assignee_id is not None else None + task.assignee_id = int(assignee.id) + for uid in {uid for uid in (old_assignee_id, int(assignee.id)) if uid is not None}: + await self._refresh_user_workload_without_commit(user_id=int(uid)) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(task.project_id), + entity_type="task", + entity_id=str(task.id), + action=intent.name, + before_data=before, + after_data=self._serialize_task(task), + ) + await self.db.commit() + await self.db.refresh(task) + await self._invalidate_project_cache(project_id=int(task.project_id), user_ids={old_assignee_id, int(assignee.id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(task.project_id), + "task_id": int(task.id), + "assignee_id": int(assignee.id), + "task": self._serialize_task(task), + "assignee": self._serialize_user_row(assignee), + } + + async def _execute_task_prioritize_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + task, candidates = await self._resolve_task( + task_id=_safe_int(params.get("task_id")), + task_title=params.get("task_title") or params.get("title"), + project_id=project_id, + ) + if task is None: + if candidates: + return self._mutation_pending(intent.name, "任务标题对应多个结果,请补充 task_id。") + return self._mutation_failed(intent.name, "未找到可调整优先级的任务。", task_id=None) + before = self._serialize_task(task) + if params.get("priority") is None: + return self._mutation_failed(intent.name, "缺少 priority。", task_id=int(task.id)) + task.priority = normalize_task_priority(params.get("priority")) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(task.project_id), + entity_type="task", + entity_id=str(task.id), + action=intent.name, + before_data=before, + after_data=self._serialize_task(task), + ) + await self.db.commit() + await self.db.refresh(task) + await self._invalidate_project_cache(project_id=int(task.project_id), user_ids={int(task.assignee_id)} if task.assignee_id else None) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(task.project_id), + "task_id": int(task.id), + "task": self._serialize_task(task), + } + + async def _execute_task_status_transition_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + task, candidates = await self._resolve_task( + task_id=_safe_int(params.get("task_id")), + task_title=params.get("task_title") or params.get("title"), + project_id=project_id, + ) + if task is None: + if candidates: + return self._mutation_pending(intent.name, "任务标题对应多个结果,请补充 task_id。") + return self._mutation_failed(intent.name, "未找到可变更状态的任务。", task_id=None) + if params.get("status") is None: + return self._mutation_failed(intent.name, "缺少 status。", task_id=int(task.id)) + before = self._serialize_task(task) + new_status = normalize_task_status(params.get("status")) + previous_status = str(task.status or "").strip().lower() + task.status = new_status + task.completed_at = now_cn_naive() if new_status in {"done", "completed", "closed"} else None + if task.assignee_id is not None: + await self._refresh_user_workload_without_commit(user_id=int(task.assignee_id)) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(task.project_id), + entity_type="task", + entity_id=str(task.id), + action=intent.name, + before_data=before, + after_data=self._serialize_task(task), + ) + await self.db.commit() + await self.db.refresh(task) + await self._invalidate_project_cache(project_id=int(task.project_id), user_ids={int(task.assignee_id)} if task.assignee_id else None) + result = { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(task.project_id), + "task_id": int(task.id), + "task": self._serialize_task(task), + } + if previous_status not in {"done", "completed", "closed"} and new_status in {"done", "completed", "closed"}: + result["completed"] = True + return result + + async def _execute_task_delete_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + task, candidates = await self._resolve_task( + task_id=_safe_int(params.get("task_id")), + task_title=params.get("task_title") or params.get("title"), + project_id=project_id, + ) + if task is None: + if candidates: + return self._mutation_pending(intent.name, "任务标题对应多个结果,请补充 task_id。") + return self._mutation_failed(intent.name, "未找到可删除的任务。", task_id=None) + before = self._serialize_task(task) + assignee_id = int(task.assignee_id) if task.assignee_id is not None else None + await self._log_operation( + actor_user_id=operator_user_id, + project_id=int(task.project_id), + entity_type="task", + entity_id=str(task.id), + action=intent.name, + before_data=before, + after_data={"deleted": True, "task_id": int(task.id), "task_title": before["title"]}, + ) + await self.db.execute( + delete(Task).where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.id == int(task.id), + Task.project_id == int(task.project_id), + ) + ) + ) + if assignee_id is not None: + await self._refresh_user_workload_without_commit(user_id=assignee_id) + await self.db.commit() + await self._invalidate_project_cache(project_id=int(task.project_id), user_ids={assignee_id} if assignee_id else None) + return { + "status": "succeeded", + "operation_code": intent.name, + "project_id": int(task.project_id), + "task_id": int(task.id), + "deleted": True, + } + + async def _execute_personnel_role_update_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + user, candidates = await self._resolve_user( + user_id=_safe_int(params.get("user_id")), + user_name=params.get("user_name") or params.get("name"), + username=params.get("username"), + ) + if user is None: + if candidates: + return self._mutation_pending(intent.name, "用户名称对应多个结果,请补充 user_id。") + return self._mutation_failed(intent.name, "未找到可更新角色的用户。", user_id=None) + if params.get("role") is None: + return self._mutation_failed(intent.name, "缺少 role。", user_id=int(user.id)) + before = self._serialize_user_row(user) + user.role = self._clean_text(params.get("role"), max_len=50) or user.role + await self._log_operation( + actor_user_id=operator_user_id, + project_id=None, + entity_type="user", + entity_id=str(user.id), + action=intent.name, + before_data=before, + after_data=self._serialize_user_row(user), + ) + await self.db.commit() + await self.db.refresh(user) + await self._invalidate_project_cache(project_id=None, user_ids={int(user.id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "user_id": int(user.id), + "user": self._serialize_user_row(user), + } + + async def _execute_personnel_workload_update_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + user, candidates = await self._resolve_user( + user_id=_safe_int(params.get("user_id")), + user_name=params.get("user_name") or params.get("name"), + username=params.get("username"), + ) + if user is None: + if candidates: + return self._mutation_pending(intent.name, "用户名称对应多个结果,请补充 user_id。") + return self._mutation_failed(intent.name, "未找到可更新工时的用户。", user_id=None) + if params.get("max_weekly_hours") is None: + return self._mutation_failed(intent.name, "缺少 max_weekly_hours。", user_id=int(user.id)) + try: + max_weekly_hours = max(1.0, float(params.get("max_weekly_hours"))) + except (TypeError, ValueError): + return self._mutation_failed(intent.name, "max_weekly_hours 非法。", user_id=int(user.id)) + workload = await ensure_user_workload(self.db, int(self.tenant_id), int(user.id)) + before = { + "user_id": int(user.id), + "max_weekly_hours": float(workload.max_available_hours_per_week or 40.0), + "workload_ratio": float(workload.workload_ratio or 0.0), + } + workload.max_available_hours_per_week = max_weekly_hours + await recalculate_user_workload_ratio(self.db, int(self.tenant_id), int(user.id), commit=False) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=None, + entity_type="user", + entity_id=str(user.id), + action=intent.name, + before_data=before, + after_data={ + "user_id": int(user.id), + "max_weekly_hours": float(max_weekly_hours), + "workload_ratio": float(workload.workload_ratio or 0.0), + }, + ) + await self.db.commit() + await self._invalidate_project_cache(project_id=None, user_ids={int(user.id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "user_id": int(user.id), + "workload": { + "user_id": int(user.id), + "max_weekly_hours": float(workload.max_available_hours_per_week or 0.0), + "workload_ratio": float(workload.workload_ratio or 0.0), + }, + } + + async def _execute_personnel_availability_update_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + user, candidates = await self._resolve_user( + user_id=_safe_int(params.get("user_id")), + user_name=params.get("user_name") or params.get("name"), + username=params.get("username"), + ) + if user is None: + if candidates: + return self._mutation_pending(intent.name, "用户名称对应多个结果,请补充 user_id。") + return self._mutation_failed(intent.name, "未找到可更新可用性的用户。", user_id=None) + if params.get("status") is None: + return self._mutation_failed(intent.name, "缺少 status。", user_id=int(user.id)) + before = self._serialize_user_row(user) + status = self._normalize_user_status(params.get("status")) + user.status = status + user.is_active = status != "offline" + if status == "offline": + user.workload = 0 + await self.db.execute( + update(Task) + .where( + and_( + Task.tenant_id == int(self.tenant_id), + Task.assignee_id == int(user.id), + ) + ) + .values(assignee_id=None, updated_at=now_cn_naive()) + ) + workload = await ensure_user_workload(self.db, int(self.tenant_id), int(user.id)) + workload.current_assigned_hours = 0.0 + workload.workload_ratio = 0.0 + workload.max_available_hours_per_week = max(1.0, float(workload.max_available_hours_per_week or 40.0)) + else: + await self._refresh_user_workload_without_commit(user_id=int(user.id)) + await self._log_operation( + actor_user_id=operator_user_id, + project_id=None, + entity_type="user", + entity_id=str(user.id), + action=intent.name, + before_data=before, + after_data=self._serialize_user_row(user), + ) + await self.db.commit() + await self.db.refresh(user) + await self._invalidate_user_project_caches(user_id=int(user.id)) + await self._invalidate_project_cache(project_id=None, user_ids={int(user.id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "user_id": int(user.id), + "user": self._serialize_user_row(user), + } + + async def _execute_knowledge_doc_upsert_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + doc_id = _safe_int(params.get("doc_id")) + title = self._clean_title(params.get("title"), max_len=255) + content = self._clean_text( + params.get("content") + or params.get("raw_content") + or params.get("body") + or params.get("text"), + max_len=200000, + ) + if not title: + return self._mutation_failed(intent.name, "缺少 title。", doc_id=doc_id) + if not content: + return self._mutation_failed(intent.name, "缺少 content。", doc_id=doc_id) + source_type = self._clean_text(params.get("source_type") or "file", max_len=32) or "file" + file_type = self._clean_text(params.get("file_type") or "txt", max_len=32) or "txt" + file_url = self._clean_text(params.get("file_url"), max_len=1024) + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + rag_service = AdaptiveRAGService(self.db, tenant_id=int(self.tenant_id)) + if doc_id is None: + doc = KnowledgeBaseDoc( + tenant_id=int(self.tenant_id), + title=title, + file_type=file_type, + source_type=source_type, + file_url=file_url, + uploaded_by=_safe_int(operator_user_id), + status="processing", + last_fetched_at=now_cn_naive() if source_type == "url" else None, + ) + self.db.add(doc) + await self.db.flush() + else: + doc = ( + await self.db.execute( + select(KnowledgeBaseDoc).where( + and_( + KnowledgeBaseDoc.tenant_id == int(self.tenant_id), + KnowledgeBaseDoc.id == int(doc_id), + ) + ) + ) + ).scalar_one_or_none() + if doc is None: + return self._mutation_failed(intent.name, "知识文档不存在。", doc_id=doc_id) + doc.title = title + doc.file_type = file_type + doc.source_type = source_type + doc.file_url = file_url + if operator_user_id is not None: + doc.uploaded_by = int(operator_user_id) + doc.status = "processing" + doc.last_fetched_at = now_cn_naive() if source_type == "url" else doc.last_fetched_at + await rag_service._index_doc_content( + doc=doc, + raw_content=content, + project_id=project_id, + source_type=source_type, + ) + await self.db.refresh(doc) + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "doc_id": int(doc.id), + "doc": rag_service._serialize_doc(doc), + } + + async def _execute_knowledge_chunk_upsert_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + doc_id = _safe_int(params.get("doc_id")) + if doc_id is None: + return self._mutation_failed(intent.name, "缺少 doc_id。") + doc = ( + await self.db.execute( + select(KnowledgeBaseDoc).where( + and_( + KnowledgeBaseDoc.tenant_id == int(self.tenant_id), + KnowledgeBaseDoc.id == int(doc_id), + ) + ) + ) + ).scalar_one_or_none() + if doc is None: + return self._mutation_failed(intent.name, "知识文档不存在。", doc_id=doc_id) + chunk_text = self._clean_text( + params.get("chunk_text") or params.get("content") or params.get("text"), + max_len=10000, + ) + if not chunk_text: + return self._mutation_failed(intent.name, "缺少 chunk_text。", doc_id=doc_id) + chunk_id = _safe_int(params.get("chunk_id")) + source_type = self._clean_text(params.get("source_type") or doc.source_type or "doc", max_len=32) or "doc" + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + metadata = params.get("metadata") if isinstance(params.get("metadata"), dict) else {} + embedding = await llm_service.get_embedding_async(chunk_text, db=self.db, tenant_id=int(self.tenant_id)) + if chunk_id is None: + max_index = ( + await self.db.execute( + select(func.max(KnowledgeBaseChunk.chunk_index)).where( + and_( + KnowledgeBaseChunk.tenant_id == int(self.tenant_id), + KnowledgeBaseChunk.doc_id == int(doc.id), + ) + ) + ) + ).scalar_one() + chunk = KnowledgeBaseChunk( + tenant_id=int(self.tenant_id), + doc_id=int(doc.id), + source_type=source_type, + project_id=project_id, + content=chunk_text, + embedding=embedding if embedding else None, + chunk_index=int(max_index or 0) + 1, + chunk_metadata={ + "title": doc.title, + "file_type": doc.file_type, + "source_type": source_type, + "project_id": project_id, + **metadata, + }, + ) + self.db.add(chunk) + await self.db.flush() + else: + chunk = ( + await self.db.execute( + select(KnowledgeBaseChunk).where( + and_( + KnowledgeBaseChunk.tenant_id == int(self.tenant_id), + KnowledgeBaseChunk.id == int(chunk_id), + ) + ) + ) + ).scalar_one_or_none() + if chunk is None: + return self._mutation_failed(intent.name, "知识分片不存在。", doc_id=doc_id, chunk_id=chunk_id) + chunk.doc_id = int(doc.id) + chunk.source_type = source_type + chunk.project_id = project_id + chunk.content = chunk_text + chunk.embedding = embedding if embedding else None + chunk.chunk_metadata = { + "title": doc.title, + "file_type": doc.file_type, + "source_type": source_type, + "project_id": project_id, + **metadata, + } + await self.db.commit() + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "doc_id": int(doc.id), + "chunk_id": int(chunk.id), + "chunk": { + "id": int(chunk.id), + "doc_id": int(chunk.doc_id) if chunk.doc_id is not None else None, + "project_id": int(chunk.project_id) if chunk.project_id is not None else None, + "source_type": str(chunk.source_type or "").strip(), + "chunk_index": int(chunk.chunk_index or 0), + }, + } + + async def _execute_knowledge_link_upsert_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + source_type = self._clean_text(params.get("source_type"), max_len=40) + target_type = self._clean_text(params.get("target_type"), max_len=40) + source_id = _safe_int(params.get("source_id")) + target_id = _safe_int(params.get("target_id")) + if not source_type or not target_type or source_id is None or target_id is None: + return self._mutation_failed(intent.name, "link_upsert 缺少 source/target 信息。") + relation_type = self._clean_text( + params.get("relation_type") or f"{source_type}_to_{target_type}", + max_len=40, + ) or "knowledge_link" + weight = float(params.get("weight") or 1.0) + payload = params.get("payload") if isinstance(params.get("payload"), dict) else {} + kg_service = ProjectKnowledgeGraphService(self.db, tenant_id=int(self.tenant_id)) + source_entity = await kg_service.upsert_entity( + entity_type=source_type, + entity_key=f"{source_type}:{int(source_id)}", + display_name=str(params.get("source_name") or f"{source_type}#{int(source_id)}").strip()[:255], + payload={ + "source_type": source_type, + "source_id": int(source_id), + **(params.get("source_payload") if isinstance(params.get("source_payload"), dict) else {}), + }, + source_type="ai_pmo", + source_ref=f"{source_type}:{int(source_id)}", + ) + target_entity = await kg_service.upsert_entity( + entity_type=target_type, + entity_key=f"{target_type}:{int(target_id)}", + display_name=str(params.get("target_name") or f"{target_type}#{int(target_id)}").strip()[:255], + payload={ + "target_type": target_type, + "target_id": int(target_id), + **(params.get("target_payload") if isinstance(params.get("target_payload"), dict) else {}), + }, + source_type="ai_pmo", + source_ref=f"{target_type}:{int(target_id)}", + ) + relation = await kg_service.upsert_relation( + relation_type=relation_type, + subject_entity_id=int(source_entity.id), + object_entity_id=int(target_entity.id), + weight=weight, + payload={ + "source_type": source_type, + "source_id": int(source_id), + "target_type": target_type, + "target_id": int(target_id), + **payload, + }, + source_type="ai_pmo", + source_ref=f"{relation_type}:{source_type}:{int(source_id)}->{target_type}:{int(target_id)}", + ) + await kg_service.add_evidence( + evidence_type="knowledge_link_upsert", + content_excerpt=f"{source_type}#{int(source_id)} {relation_type} {target_type}#{int(target_id)}", + relation_id=int(relation.id), + source_type="ai_pmo", + source_ref=f"{relation_type}:{source_type}:{int(source_id)}->{target_type}:{int(target_id)}", + metadata={ + "operation_code": intent.name, + "payload": payload, + }, + ) + await self.db.commit() + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "relation_id": int(relation.id), + "link_id": int(relation.id), + "source_entity_id": int(source_entity.id), + "target_entity_id": int(target_entity.id), + } + + async def _execute_knowledge_reindex_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + scope = self._clean_text(params.get("scope") or "tenant", max_len=20) or "tenant" + project_id = _safe_int(params.get("project_id")) or _safe_int(intent.project_id) + doc_id = _safe_int(params.get("doc_id")) + kg_service = ProjectKnowledgeGraphService(self.db, tenant_id=int(self.tenant_id)) + if scope == "project" and project_id is not None: + counters = await kg_service.sync_structured_entities(limit=1000) + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "reindexed": True, + "scope": scope, + "project_id": int(project_id), + "doc_id": doc_id, + "counters": counters, + } + if scope == "document" and doc_id is not None: + doc = ( + await self.db.execute( + select(KnowledgeBaseDoc).where( + and_( + KnowledgeBaseDoc.tenant_id == int(self.tenant_id), + KnowledgeBaseDoc.id == int(doc_id), + ) + ) + ) + ).scalar_one_or_none() + if doc is None: + return self._mutation_failed(intent.name, "知识文档不存在。", doc_id=doc_id) + chunks = ( + await self.db.execute( + select(KnowledgeBaseChunk).where( + and_( + KnowledgeBaseChunk.tenant_id == int(self.tenant_id), + KnowledgeBaseChunk.doc_id == int(doc.id), + ) + ) + ) + ).scalars().all() + if doc.source_type == "url" and str(doc.file_url or "").strip(): + rag_service = AdaptiveRAGService(self.db, tenant_id=int(self.tenant_id)) + await rag_service.refresh_url_doc(doc_id=int(doc.id), project_id=project_id) + elif chunks: + await self.db.execute( + update(KnowledgeBaseChunk) + .where( + and_( + KnowledgeBaseChunk.tenant_id == int(self.tenant_id), + KnowledgeBaseChunk.doc_id == int(doc.id), + ) + ) + .values( + chunk_metadata={ + "reindexed_at": now_cn_naive().isoformat(), + "project_id": project_id, + } + ) + ) + await self.db.commit() + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "reindexed": True, + "scope": scope, + "project_id": project_id, + "doc_id": int(doc.id), + "chunk_count": len(chunks), + } + counters = await kg_service.sync_structured_entities(limit=1000) + await self._invalidate_project_cache(project_id=project_id) + return { + "status": "succeeded", + "operation_code": intent.name, + "reindexed": True, + "scope": scope, + "project_id": project_id, + "doc_id": doc_id, + "counters": counters, + } + + async def _execute_notification_create_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + user, candidates = await self._resolve_user( + user_id=_safe_int(params.get("user_id")), + user_name=params.get("user_name") or params.get("name"), + username=params.get("username"), + ) + if user is None: + if candidates: + return self._mutation_pending(intent.name, "用户名称对应多个结果,请补充 user_id。") + return self._mutation_failed(intent.name, "未找到可创建通知的用户。", user_id=None) + title = self._clean_title(params.get("title"), max_len=150) + content = self._clean_text(params.get("content") or params.get("message"), max_len=2000) + if not title or not content: + return self._mutation_failed(intent.name, "缺少通知 title 或 content。", user_id=int(user.id)) + notification_service = NotificationService(self.db) + row = await notification_service.create_notification( + tenant_id=int(self.tenant_id), + user_id=int(user.id), + title=title, + content=content, + category=self._clean_text(params.get("category") or "system", max_len=50) or "system", + source=self._clean_text(params.get("source") or "ai_pmo", max_len=50) or "ai_pmo", + entity_type=self._clean_text(params.get("entity_type"), max_len=50), + entity_id=_safe_int(params.get("entity_id")), + target_url=self._clean_text(params.get("target_url"), max_len=500), + kind=self._clean_text(params.get("kind"), max_len=30), + priority=self._clean_text(params.get("priority"), max_len=20), + status=self._clean_text(params.get("status") or "notified", max_len=30) or "notified", + blocking_reason=self._clean_text(params.get("blocking_reason"), max_len=2000), + recommended_action=self._clean_text(params.get("recommended_action"), max_len=80), + dedupe_key=self._clean_text(params.get("dedupe_key"), max_len=255), + occurrence_count=int(params.get("occurrence_count") or 1), + payload=params.get("payload") if isinstance(params.get("payload"), dict) else None, + commit=False, + ) + await self.db.commit() + await self._invalidate_project_cache(project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), user_ids={int(user.id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "notification_id": int(row.id), + "notification": self._serialize_notification(row), + } + + async def _execute_notification_ack_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + if operator_user_id is None: + return self._mutation_failed(intent.name, "缺少当前操作者,无法确认通知。") + notification = await self._resolve_notification( + notification_id=_safe_int(params.get("notification_id")), + user_id=_safe_int(operator_user_id), + ) + if notification is None: + return self._mutation_failed(intent.name, "未找到可确认的通知。", notification_id=_safe_int(params.get("notification_id"))) + notification.is_read = True + notification.read_at = now_cn_naive() + if self._clean_text(params.get("status"), max_len=30) is not None: + notification.status = self._clean_text(params.get("status"), max_len=30) + await self.db.commit() + await self._invalidate_project_cache(project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), user_ids={int(notification.user_id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "notification_id": int(notification.id), + "notification": self._serialize_notification(notification), + } + + async def _execute_notification_close_intent( + self, + *, + intent, + params: dict[str, Any], + operator_user_id: Optional[int], + ) -> dict[str, Any]: + if operator_user_id is None: + return self._mutation_failed(intent.name, "缺少当前操作者,无法关闭通知。") + notification = await self._resolve_notification( + notification_id=_safe_int(params.get("notification_id")), + user_id=_safe_int(operator_user_id), + ) + if notification is None: + return self._mutation_failed(intent.name, "未找到可关闭的通知。", notification_id=_safe_int(params.get("notification_id"))) + notification.is_read = True + notification.read_at = now_cn_naive() + notification.status = "closed" + await self.db.commit() + await self._invalidate_project_cache(project_id=_safe_int(params.get("project_id")) or _safe_int(intent.project_id), user_ids={int(notification.user_id)}) + return { + "status": "succeeded", + "operation_code": intent.name, + "notification_id": int(notification.id), + "notification": self._serialize_notification(notification), + } diff --git a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py index 2cd19dbb..749fd880 100644 --- a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_query_project.py @@ -8,6 +8,7 @@ from ...adaptive_rag_service import AdaptiveRAGService from ...context_cache_service import context_cache_service from ...sstc_service_core import ALIGNED_STATUSES from ...task_persistence_core import build_task_create_draft, persist_task +from app.utils.thread_context import build_feishu_thread_key class ExecuteQueryProjectMixin: @@ -43,11 +44,27 @@ class ExecuteQueryProjectMixin: message_intent = str(rag_request_context.get("message_intent") or "").strip() or None mixed_signal_part = str(rag_request_context.get("mixed_signal_part") or "").strip() or None else: - asked_by = _safe_int(operator_user_id) - asked_via = source_system + thread_context = metadata.get("thread_context") if isinstance(metadata.get("thread_context"), dict) else None + is_feishu_source = str(source_system or "").strip().lower() == "feishu" + if is_feishu_source: + asked_by = None + asked_via = "feishu" + else: + asked_by = _safe_int(operator_user_id) + asked_via = source_system requested_project_id = None - source_chat_id = source_message_id - conversation_key = source_message_id + source_chat_id = None + conversation_key = ( + build_feishu_thread_key( + source_chat_id=None, + source_sender_open_id=None, + sender=None, + source_message_id=source_message_id, + thread_context=thread_context, + ) + if is_feishu_source + else source_message_id + ) session_id = None force_new_session = False thinking_mode = None diff --git a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py index 3675f87c..2c3c3ff7 100644 --- a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/mixin_execute_signal_source.py @@ -2,6 +2,9 @@ from __future__ import annotations +from sqlalchemy.exc import IntegrityError + +from ....models import OperationExecutionLedger from .shared import * # noqa: F401, F403 @@ -18,89 +21,274 @@ class ExecuteSignalSourceMixin: source_message_id: str, channel_ref, ) -> dict[str, Any]: - semantic_payload = self._build_signal_source_semantic_payload( - intent=intent, + normalized_idempotency_key = self._build_signal_source_onboard_idempotency_key( + source_system=source_system, + source_message_id=source_message_id, + ) + existing_ledger = await self._load_signal_source_onboard_ledger( + idempotency_key=normalized_idempotency_key, + ) + if existing_ledger is not None: + existing_status = str(existing_ledger.status or "").strip().lower() + stored_result = ( + existing_ledger.result_payload if isinstance(existing_ledger.result_payload, dict) else {} + ) + if existing_status in {"applied", "skipped"} and stored_result: + return dict(stored_result) + if stored_result: + return dict(stored_result) + return { + "status": "pending_manual" if existing_status == "processing" else "failed_safe", + "project_id": intent.project_id, + "signal_id": None, + "signal_source_workflow": { + "status": existing_status or "processing", + "message": "signal_source.onboard already in progress", + "idempotency_key": normalized_idempotency_key, + }, + "signal_source_orchestration": {}, + "auto_tasks": [], + } + + ledger, created = await self._claim_signal_source_onboard_ledger( + idempotency_key=normalized_idempotency_key, + source_system=source_system, + source_message_id=source_message_id, + project_id=_safe_int(intent.project_id), message_text=message_text, ) - signal_summary = str(semantic_payload.get("summary") or intent.signal_summary or message_text) - signal_content = str(semantic_payload.get("content") or message_text) - initial_title_seed = self._normalize_title_candidate( - str(semantic_payload.get("title") or intent.signal_title or "") - ) - initial_signal_title = ( - initial_title_seed - if self._is_acceptable_title(initial_title_seed) - else "信号源接入请求" - ) - strategic_payload = ( - semantic_payload.get("strategic_analysis") - if isinstance(semantic_payload.get("strategic_analysis"), dict) - else {} - ) - source_context = ( - strategic_payload.get("source_context") - if isinstance(strategic_payload.get("source_context"), dict) - else {} - ) - source_context["source_message_id"] = source_message_id - source_context["channel_ref"] = channel_ref - strategic_payload["source_context"] = source_context - signal = await self._create_signal( - title=initial_signal_title, - summary=signal_summary, - content=signal_content, - project_id=intent.project_id, - source="PMFlow", - signal_type="新需求", - recommended_action="enter_only", - reasoning_bullets=( - semantic_payload.get("reasoning_bullets") - if isinstance(semantic_payload.get("reasoning_bullets"), list) - else None - ), - strategic_analysis=( - strategic_payload if isinstance(strategic_payload, dict) else None - ), - ) - domain_workflow = await self.signal_source_workflow.run( - tenant_id=self.tenant_id, - project_id=int(intent.project_id or 0), - signal_id=int(signal.id), - initiating_text=message_text, - requested_via=source_system, - operator_user_id=operator_user_id, - ) - orchestration = ( - domain_workflow.get("raw_result") - if isinstance(domain_workflow.get("raw_result"), dict) - else {} - ) - signal_source_payload = ( - orchestration.get("signal_source") - if isinstance(orchestration.get("signal_source"), dict) - else {} - ) - ai_source_title = self._normalize_title_candidate(signal_source_payload.get("source_name")) - if self._is_acceptable_title(ai_source_title): - signal.title = ai_source_title - await self.db.flush() - return { - "status": ( - "succeeded" - if str(domain_workflow.get("status") or "").lower() == "completed" - else ( - "pending_manual" - if str(domain_workflow.get("status") or "").lower() == "pending_manual" - else "failed_safe" + if ledger is not None and not created: + ledger_status = str(ledger.status or "").strip().lower() + stored_result = ledger.result_payload if isinstance(ledger.result_payload, dict) else {} + if ledger_status in {"applied", "skipped"} and stored_result: + return dict(stored_result) + if stored_result: + return dict(stored_result) + return { + "status": "pending_manual" if ledger_status == "processing" else "failed_safe", + "project_id": intent.project_id, + "signal_id": None, + "signal_source_workflow": { + "status": ledger_status or "processing", + "message": "signal_source.onboard already in progress", + "idempotency_key": normalized_idempotency_key, + }, + "signal_source_orchestration": {}, + "auto_tasks": [], + } + + await self.db.commit() + + try: + semantic_payload = self._build_signal_source_semantic_payload( + intent=intent, + message_text=message_text, + ) + signal_summary = str(semantic_payload.get("summary") or intent.signal_summary or message_text) + signal_content = str(semantic_payload.get("content") or message_text) + initial_title_seed = self._normalize_title_candidate( + str(semantic_payload.get("title") or intent.signal_title or "") + ) + initial_signal_title = ( + initial_title_seed + if self._is_acceptable_title(initial_title_seed) + else "信号源接入请求" + ) + strategic_payload = ( + semantic_payload.get("strategic_analysis") + if isinstance(semantic_payload.get("strategic_analysis"), dict) + else {} + ) + source_context = ( + strategic_payload.get("source_context") + if isinstance(strategic_payload.get("source_context"), dict) + else {} + ) + source_context["source_message_id"] = source_message_id + source_context["channel_ref"] = channel_ref + strategic_payload["source_context"] = source_context + signal = await self._create_signal( + title=initial_signal_title, + summary=signal_summary, + content=signal_content, + project_id=int(intent.project_id), + source="PMFlow", + signal_type="新需求", + recommended_action="enter_only", + reasoning_bullets=( + semantic_payload.get("reasoning_bullets") + if isinstance(semantic_payload.get("reasoning_bullets"), list) + else None + ), + strategic_analysis=( + strategic_payload if isinstance(strategic_payload, dict) else None + ), + ) + domain_workflow = await self.signal_source_workflow.run( + tenant_id=self.tenant_id, + project_id=int(intent.project_id or 0), + signal_id=int(signal.id), + initiating_text=message_text, + requested_via=source_system, + operator_user_id=operator_user_id, + ) + orchestration = ( + domain_workflow.get("raw_result") + if isinstance(domain_workflow.get("raw_result"), dict) + else {} + ) + signal_source_payload = ( + orchestration.get("signal_source") + if isinstance(orchestration.get("signal_source"), dict) + else {} + ) + ai_source_title = self._normalize_title_candidate(signal_source_payload.get("source_name")) + if self._is_acceptable_title(ai_source_title): + signal.title = ai_source_title + await self.db.flush() + result = { + "status": ( + "succeeded" + if str(domain_workflow.get("status") or "").lower() == "completed" + else ( + "pending_manual" + if str(domain_workflow.get("status") or "").lower() == "pending_manual" + else "failed_safe" + ) + ), + "signal_id": int(signal.id), + "project_id": intent.project_id, + "signal_source_workflow": domain_workflow, + "signal_source_orchestration": orchestration, + "auto_tasks": self._extract_auto_tasks( + orchestration.get("execution") + if isinstance(orchestration.get("execution"), dict) + else orchestration.get("openclaw") + ), + } + await self._finalize_signal_source_onboard_ledger( + ledger=ledger, + status=result["status"], + result=result, + signal_source_payload=signal_source_payload, + signal_id=int(signal.id), + ) + await self.db.commit() + await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) + return result + except Exception as exc: + await self._finalize_signal_source_onboard_ledger( + ledger=ledger, + status="failed", + result={ + "status": "failed_safe", + "project_id": intent.project_id, + "signal_id": None, + "signal_source_workflow": {}, + "signal_source_orchestration": {}, + "auto_tasks": [], + "error_message": str(exc)[:1000], + }, + signal_source_payload=None, + signal_id=None, + error_message=str(exc), + ) + await self.db.commit() + raise + + @staticmethod + def _build_signal_source_onboard_idempotency_key( + *, + source_system: str, + source_message_id: str, + ) -> str: + message_id = str(source_message_id or "").strip() + if message_id: + source = str(source_system or "").strip().lower() + key = f"{source}:{message_id}" if source else message_id + return key[:128] + return "" + + async def _load_signal_source_onboard_ledger( + self, + *, + idempotency_key: str, + ) -> Optional[OperationExecutionLedger]: + if not idempotency_key: + return None + return ( + await self.db.execute( + select(OperationExecutionLedger).where( + and_( + OperationExecutionLedger.tenant_id == self.tenant_id, + OperationExecutionLedger.operation_code == "signal_source.onboard", + OperationExecutionLedger.idempotency_key == idempotency_key, + ) + ).limit(1) + ) + ).scalar_one_or_none() + + async def _claim_signal_source_onboard_ledger( + self, + *, + idempotency_key: str, + source_system: str, + source_message_id: str, + project_id: Optional[int], + message_text: str, + ) -> tuple[Optional[OperationExecutionLedger], bool]: + if not idempotency_key: + return None, False + ledger = await self._load_signal_source_onboard_ledger(idempotency_key=idempotency_key) + if ledger is not None: + return ledger, False + try: + async with self.db.begin_nested(): + ledger = OperationExecutionLedger( + tenant_id=self.tenant_id, + operation_code="signal_source.onboard", + project_id=_safe_int(project_id), + idempotency_key=idempotency_key, + trace_id=str(source_message_id or "").strip()[:64] or None, + status="processing", + payload={ + "source_system": str(source_system or "").strip() or None, + "source_message_id": str(source_message_id or "").strip() or None, + "message_text": str(message_text or "")[:4000], + "project_id": _safe_int(project_id), + }, + result_payload=None, + entity_type="signal_source", + entity_id=None, + error_message=None, ) - ), - "signal_id": int(signal.id), - "project_id": intent.project_id, - "signal_source_workflow": domain_workflow, - "signal_source_orchestration": orchestration, - "auto_tasks": self._extract_auto_tasks( - orchestration.get("execution") - if isinstance(orchestration.get("execution"), dict) - else orchestration.get("openclaw") - ), - } + self.db.add(ledger) + await self.db.flush() + return ledger, True + except IntegrityError: + return await self._load_signal_source_onboard_ledger(idempotency_key=idempotency_key), False + + async def _finalize_signal_source_onboard_ledger( + self, + *, + ledger: Optional[OperationExecutionLedger], + status: str, + result: dict[str, Any], + signal_source_payload: Optional[dict[str, Any]], + signal_id: Optional[int], + error_message: Optional[str] = None, + ) -> None: + if ledger is None: + return + normalized_status = str(status or "").strip().lower() or "processing" + ledger.status = normalized_status + ledger.entity_type = "signal_source" + ledger.entity_id = ( + str(signal_source_payload.get("registry_id")) + if isinstance(signal_source_payload, dict) and signal_source_payload.get("registry_id") is not None + else str(signal_id) if signal_id is not None else None + ) + ledger.result_payload = dict(result) + ledger.error_message = (str(error_message or "").strip() or None) if normalized_status == "failed" else None + ledger.project_id = _safe_int(result.get("project_id")) + await self.db.flush() diff --git a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py index 5d3258fb..2a41e316 100644 --- a/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py +++ b/backend/app/services/ai_pmo/ai_pmo_typed_executor_execute/service.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .mixin_execute_mutations import ExecuteMutationsMixin from .mixin_execute_query_project import ExecuteQueryProjectMixin from .mixin_execute_signal_create import ExecuteSignalCreateMixin from .mixin_execute_signal_source import ExecuteSignalSourceMixin @@ -11,6 +12,7 @@ from .shared import * # noqa: F401, F403 class TypedExecutorExecuteMixin( ExecuteHelpersMixin, + ExecuteMutationsMixin, ExecuteQueryProjectMixin, ExecuteSignalSourceMixin, ExecuteSignalCreateMixin, @@ -49,6 +51,33 @@ class TypedExecutorExecuteMixin( operator_user_id=operator_user_id, ) + if intent.name in { + INTENT_PROJECT_UPDATE, + INTENT_PROJECT_ARCHIVE, + INTENT_PROJECT_REOPEN, + INTENT_PROJECT_DELETE, + INTENT_TASK_UPDATE, + INTENT_TASK_ASSIGN, + INTENT_TASK_PRIORITIZE, + INTENT_TASK_STATUS_TRANSITION, + INTENT_TASK_DELETE, + INTENT_PERSONNEL_ROLE_UPDATE, + INTENT_PERSONNEL_WORKLOAD_UPDATE, + INTENT_PERSONNEL_AVAILABILITY_UPDATE, + INTENT_KNOWLEDGE_DOC_UPSERT, + INTENT_KNOWLEDGE_CHUNK_UPSERT, + INTENT_KNOWLEDGE_LINK_UPSERT, + INTENT_KNOWLEDGE_REINDEX, + INTENT_NOTIFICATION_CREATE, + INTENT_NOTIFICATION_ACK, + INTENT_NOTIFICATION_CLOSE, + }: + return await self._execute_mutation_intent( + intent=intent, + message_text=message_text, + operator_user_id=operator_user_id, + ) + if intent.name == INTENT_SIGNAL_SOURCE_ONBOARD: return await self._execute_signal_source_onboard_intent( intent=intent, diff --git a/backend/app/services/ai_pmo/draft_confirm_handler.py b/backend/app/services/ai_pmo/draft_confirm_handler.py index b2b377fd..6850ae30 100644 --- a/backend/app/services/ai_pmo/draft_confirm_handler.py +++ b/backend/app/services/ai_pmo/draft_confirm_handler.py @@ -12,6 +12,7 @@ for _name, _value in vars(_common).items(): globals()[_name] = _value from ..signal.draft_generator_service import DraftGeneratorService +from ...models.feishu_reply_outbox import FeishuReplyOutbox class DraftConfirmHandler: @@ -56,6 +57,7 @@ class DraftConfirmHandler: channel_ref: Optional[str], source_sender_open_id: Optional[str], expected_version: Optional[int] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: action, revision_note = self.parse_action_from_text(message_text=message_text) explicit_work_item_id = self._extract_work_item_id(message_text) @@ -66,12 +68,25 @@ class DraftConfirmHandler: channel_ref=channel_ref, source_sender_open_id=source_sender_open_id, explicit_work_item_id=explicit_work_item_id, + thread_context=thread_context, ) if target is None: + if explicit_work_item_id is not None: + return { + "handled": False, + "status": "ignored", + "followup_reply": "", + "target_work_item_id": None, + "target_state": None, + "target_intent": None, + "project_id": None, + "signal_id": None, + "workflow_route": None, + } return { - "handled": True, + "handled": False, "status": "ignored", - "followup_reply": "当前没有待确认草稿,请先提交写操作需求。", + "followup_reply": "", "target_work_item_id": None, "target_state": None, "target_intent": None, @@ -580,6 +595,7 @@ class DraftConfirmHandler: channel_ref: Optional[str], source_sender_open_id: Optional[str], explicit_work_item_id: Optional[int], + thread_context: Optional[dict[str, Any]], ) -> Optional[tuple[WorkItem, IngestEvent]]: stmt = ( select(WorkItem, IngestEvent) @@ -603,6 +619,41 @@ class DraftConfirmHandler: normalized_sender = str(sender or "").strip() normalized_channel = str(channel_ref or "").strip() normalized_open_id = str(source_sender_open_id or "").strip() + anchor_context = self._normalize_thread_context(thread_context) + if not anchor_context: + return None + + source_message_ids = sorted( + { + str(getattr(work_item, "source_message_id", "") or "").strip() + for work_item, _ in rows + if str(getattr(work_item, "source_message_id", "") or "").strip() + } + ) + sent_message_id_map: dict[str, str] = {} + if source_message_ids: + outbox_rows = ( + await self.db.execute( + select( + FeishuReplyOutbox.source_message_id, + FeishuReplyOutbox.sent_message_id, + ) + .where( + and_( + FeishuReplyOutbox.tenant_id == int(self.tenant_id), + FeishuReplyOutbox.source_message_id.in_(source_message_ids), + FeishuReplyOutbox.status == "sent", + FeishuReplyOutbox.sent_message_id.is_not(None), + ) + ) + .order_by(FeishuReplyOutbox.id.desc()) + ) + ).all() + for source_message_id, sent_message_id in outbox_rows: + source_key = str(source_message_id or "").strip() + sent_key = str(sent_message_id or "").strip() + if source_key and sent_key and source_key not in sent_message_id_map: + sent_message_id_map[source_key] = sent_key candidates: list[tuple[WorkItem, IngestEvent]] = [] for work_item, ingest in rows: @@ -616,12 +667,57 @@ class DraftConfirmHandler: else: if not (same_sender or same_channel): continue + if not self._matches_thread_anchor( + work_item=work_item, + ingest=ingest, + anchor_context=anchor_context, + draft_reply_message_id=sent_message_id_map.get(str(getattr(work_item, "source_message_id", "") or "").strip()), + ): + continue candidates.append((work_item, ingest)) if not candidates: return None return candidates[0] + @staticmethod + def _normalize_thread_context( + thread_context: Optional[dict[str, Any]], + ) -> dict[str, str]: + if not isinstance(thread_context, dict): + return {} + normalized: dict[str, str] = {} + for key in ("reply_to_message_id", "root_message_id", "thread_id"): + value = str(thread_context.get(key) or "").strip() + if value: + normalized[key] = value[:128] + return normalized + + @classmethod + def _matches_thread_anchor( + cls, + *, + work_item: WorkItem, + ingest: IngestEvent, + anchor_context: dict[str, str], + draft_reply_message_id: Optional[str] = None, + ) -> bool: + if not anchor_context: + return False + payload = ingest.payload if isinstance(ingest.payload, dict) else {} + candidate_values = { + str(payload.get("reply_to_message_id") or "").strip(), + str(payload.get("root_message_id") or "").strip(), + str(getattr(ingest, "source_message_id", "") or "").strip(), + str(getattr(work_item, "source_message_id", "") or "").strip(), + str(draft_reply_message_id or "").strip(), + } + candidate_values.discard("") + return any( + str(value or "").strip() in candidate_values + for value in anchor_context.values() + ) + async def _sync_decision_outcome( self, *, diff --git a/backend/app/services/ai_service_core/mixin_business_tasks.py b/backend/app/services/ai_service_core/mixin_business_tasks.py index 767b1c28..4f7c1d8b 100644 --- a/backend/app/services/ai_service_core/mixin_business_tasks.py +++ b/backend/app/services/ai_service_core/mixin_business_tasks.py @@ -71,14 +71,29 @@ class AIServiceBusinessTasksMixin: signal_content: str, tenant_id: Union[str, int] = "default", db=None, + project_id: Optional[Union[str, int]] = None, + project_context: Optional[str] = None, + active_projects_json: Optional[str] = None, ) -> dict: """从市场/模型信号生成需求宪章草案""" from ...config.ai_prompts import PROMPT_GENERATE_CHARTER tid = _t2s(tenant_id) - prompt = PROMPT_GENERATE_CHARTER.format(signal_content=signal_content) + try: + resolved_project_id = int(project_id) if project_id is not None and str(project_id).strip() else None + except (TypeError, ValueError): + resolved_project_id = None + prompt = PROMPT_GENERATE_CHARTER.format( + signal_content=signal_content, + project_context=str(project_context or "").strip() or "无", + active_projects_json=str(active_projects_json or "[]").strip() or "[]", + ) text = await self._invoke_with_retry( - prompt, db=db, tenant_id=tid, function_name="generate_charter_from_signal", + prompt, + db=db, + tenant_id=tid, + function_name="generate_charter_from_signal", + project_id=resolved_project_id, ) return self._parse_json(text) diff --git a/backend/app/services/ai_service_core/mixin_runtime_core.py b/backend/app/services/ai_service_core/mixin_runtime_core.py index 6f57f784..38651e53 100644 --- a/backend/app/services/ai_service_core/mixin_runtime_core.py +++ b/backend/app/services/ai_service_core/mixin_runtime_core.py @@ -42,47 +42,68 @@ class AIServiceRuntimeCoreMixin: routing_enabled = self._is_env_true("OPENCLAW_ROUTING_ENABLED", True) direct_emergency_enabled = self._is_env_true("LLM_DIRECT_EMERGENCY_ENABLED", False) if routing_enabled and not direct_emergency_enabled: + high_defaults = dict(TIER_DEFAULTS.get("high", {})) + high_provider = str(high_defaults.get("provider") or "openai").strip().lower() + provider_defaults = dict(PROVIDER_DEFAULTS.get(high_provider, PROVIDER_DEFAULTS["openai"])) return { - "api_key": str(api_key or os.getenv("OPENAI_API_KEY", "") or "").strip(), - "model": str( - model - or os.getenv("LLM_ROUTING_HIGH_MODEL") - or os.getenv("OPENAI_MODEL") - or DEFAULT_OPENAI_MODEL + "api_key": str( + api_key + or os.getenv(str(provider_defaults.get("api_env") or "OPENAI_API_KEY"), "") + or os.getenv("OPENAI_API_KEY", "") or "" ).strip(), - "base_url": str(base_url or os.getenv("OPENAI_BASE_URL", DEFAULT_OPENAI_BASE_URL) or "").strip(), + "model": str(model or high_defaults.get("model") or DEFAULT_OPENAI_MODEL or "").strip(), + "base_url": str(base_url or high_defaults.get("base_url") or DEFAULT_OPENAI_BASE_URL or "").strip(), } provider = str(os.getenv("LLM_PROVIDER", "openai") or "openai").strip().lower() if provider == "glm": + defaults = dict(PROVIDER_DEFAULTS.get("glm", {})) return { - "api_key": str(api_key or os.getenv("GLM_API_KEY", "") or "").strip(), - "model": str(model or os.getenv("GLM_MODEL", DEFAULT_GLM_MODEL) or "").strip(), - "base_url": str(base_url or os.getenv("GLM_BASE_URL", DEFAULT_OPENAI_BASE_URL) or "").strip(), - } - if provider == "deepseek": - return { - "api_key": str(api_key or os.getenv("DEEPSEEK_API_KEY", "") or "").strip(), - "model": str(model or os.getenv("DEEPSEEK_MODEL", "deepseek-chat") or "").strip(), - "base_url": str(base_url or os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1") or "").strip(), - } - if provider == "ollama": - return { - "api_key": str(api_key or "").strip(), - "model": str(model or os.getenv("OLLAMA_MODEL", "llama3:latest") or "").strip(), + "api_key": str( + api_key + or os.getenv("DASHSCOPE_API_KEY") + or os.getenv("GLM_API_KEY", "") + or "" + ).strip(), + "model": str( + model + or defaults.get("model") + or DEFAULT_GLM_MODEL + or "" + ).strip(), "base_url": str( base_url - or os.getenv("OLLAMA_BASE_URL") + or defaults.get("base_url") + or DEFAULT_DASHSCOPE_BASE_URL + or "" + ).strip(), + } + if provider == "deepseek": + defaults = dict(PROVIDER_DEFAULTS.get("deepseek", {})) + return { + "api_key": str(api_key or os.getenv("DEEPSEEK_API_KEY", "") or "").strip(), + "model": str(model or defaults.get("model") or "deepseek-chat" or "").strip(), + "base_url": str(base_url or defaults.get("base_url") or "https://api.deepseek.com/v1" or "").strip(), + } + if provider == "ollama": + defaults = dict(PROVIDER_DEFAULTS.get("ollama", {})) + return { + "api_key": str(api_key or "").strip(), + "model": str(model or defaults.get("model") or "llama3:latest" or "").strip(), + "base_url": str( + base_url + or defaults.get("base_url") or os.getenv("OLLAMA_HOST", "http://127.0.0.1:11434") or "" ).strip(), } + defaults = dict(PROVIDER_DEFAULTS.get("openai", {})) return { "api_key": str(api_key or os.getenv("OPENAI_API_KEY", "") or "").strip(), - "model": str(model or os.getenv("OPENAI_MODEL", DEFAULT_OPENAI_MODEL) or "").strip(), - "base_url": str(base_url or os.getenv("OPENAI_BASE_URL", DEFAULT_OPENAI_BASE_URL) or "").strip(), + "model": str(model or defaults.get("model") or DEFAULT_OPENAI_MODEL or "").strip(), + "base_url": str(base_url or defaults.get("base_url") or DEFAULT_OPENAI_BASE_URL or "").strip(), } def _get_llm(self) -> BaseChatModel: diff --git a/backend/app/services/ai_service_core/shared.py b/backend/app/services/ai_service_core/shared.py index f6aace8b..a1feb557 100644 --- a/backend/app/services/ai_service_core/shared.py +++ b/backend/app/services/ai_service_core/shared.py @@ -20,6 +20,7 @@ from sqlalchemy import and_, select # LangChain & OpenAI from ...utils.timezone import now_cn_naive +from ..model.model_config import get_provider_defaults, get_tier_defaults try: from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage, SystemMessage @@ -41,9 +42,15 @@ INPUT_PRICE_PER_1K = 0.00015 OUTPUT_PRICE_PER_1K = 0.0006 CACHE_TTL_SECONDS = 3600 # 1 小时 PROMPT_PROFILE_CACHE_TTL_SECONDS = 60 -DEFAULT_OPENAI_BASE_URL = "https://api.edgefn.net/v1" -DEFAULT_OPENAI_MODEL = "MiniMax-M2.5" -DEFAULT_GLM_MODEL = "GLM-4.7" +_PROVIDER_DEFAULTS = get_provider_defaults() +_TIER_DEFAULTS = get_tier_defaults() +PROVIDER_DEFAULTS = _PROVIDER_DEFAULTS +TIER_DEFAULTS = _TIER_DEFAULTS +DEFAULT_OPENAI_BASE_URL = str(_PROVIDER_DEFAULTS.get("openai", {}).get("base_url") or "https://api.edgefn.net/v1") +DEFAULT_OPENAI_MODEL = str(_PROVIDER_DEFAULTS.get("openai", {}).get("model") or "MiniMax-M2.5") +DEFAULT_DASHSCOPE_BASE_URL = str(_PROVIDER_DEFAULTS.get("glm", {}).get("base_url") or "https://dashscope.aliyuncs.com/compatible-mode/v1") +DEFAULT_DASHSCOPE_MODEL = str(_PROVIDER_DEFAULTS.get("glm", {}).get("model") or "qwen-plus") +DEFAULT_GLM_MODEL = DEFAULT_DASHSCOPE_MODEL from ...config.ai_prompts import build_default_role_prompt_bundle from ...core.database import AsyncSessionLocal @@ -73,4 +80,3 @@ def _tenant_to_int(tenant_id: Union[str, int]) -> int: __all__ = [name for name in globals() if not name.startswith("__")] - diff --git a/backend/app/services/context_cache_service/invalidation.py b/backend/app/services/context_cache_service/invalidation.py index 956dd1c1..3cd8d81c 100644 --- a/backend/app/services/context_cache_service/invalidation.py +++ b/backend/app/services/context_cache_service/invalidation.py @@ -21,19 +21,19 @@ class ContextCacheInvalidator: @staticmethod def _system_key(tenant_id: int) -> str: - return ContextCacheService._system_key(_safe_int(tenant_id, 1)) + return f"pmflow:context:system:{_safe_int(tenant_id, 1)}" @staticmethod def _project_list_key(tenant_id: int) -> str: - return ContextCacheService._project_list_key(_safe_int(tenant_id, 1)) + return f"pmflow:project:list:{_safe_int(tenant_id, 1)}" @staticmethod def _signal_pending_key(tenant_id: int) -> str: - return ContextCacheService._signal_pending_key(_safe_int(tenant_id, 1)) + return f"pmflow:signal:pending_count:{_safe_int(tenant_id, 1)}" @staticmethod def _user_key(tenant_id: int, user_id: int) -> str: - return ContextCacheService._user_key(_safe_int(tenant_id, 1), _safe_int(user_id, 0)) + return f"pmflow:context:user:{_safe_int(tenant_id, 1)}:{_safe_int(user_id, 0)}" def invalidate( self, @@ -169,4 +169,3 @@ def register_context_cache_invalidation_hooks() -> None: session.info.pop("_pmflow_cache_invalidation", None) setattr(register_context_cache_invalidation_hooks, "_registered", True) - diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_card_action.py b/backend/app/services/integrations/feishu_integration_service/mixin_card_action.py index f9b03091..01dee247 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_card_action.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_card_action.py @@ -10,6 +10,7 @@ class FeishuCardActionMixin: _ACTION_CONTINUE = "continue" _TEMPLATE_DRAFT_PENDING_CONFIRM = "draft_pending_confirm" _TEMPLATE_PENDING_MANUAL = "pending_manual" + _TEMPLATE_QUEUE_PENDING_CONFIRM = "queue_pending_confirm" _ACTION_ALIASES: dict[str, str] = { "confirm": _ACTION_CONFIRM, "draft_confirm": _ACTION_CONFIRM, @@ -60,6 +61,21 @@ class FeishuCardActionMixin: return int(row_work_item_id) return None + @staticmethod + def _coerce_capture_queue_item_id(capture_result: Dict[str, Any]) -> Optional[int]: + queue_item_id = FeishuCardActionMixin._safe_int( + capture_result.get("queue_item_id") + or capture_result.get("item_id") + ) + if queue_item_id is not None and queue_item_id > 0: + return int(queue_item_id) + rows = capture_result.get("results") if isinstance(capture_result.get("results"), list) else [] + if rows and isinstance(rows[0], dict): + row_item_id = FeishuCardActionMixin._safe_int(rows[0].get("item_id")) + if row_item_id is not None and row_item_id > 0: + return int(row_item_id) + return None + @classmethod def _build_action_card_from_capture_result( cls, @@ -72,7 +88,8 @@ class FeishuCardActionMixin: return None template_key = str(capture_result.get("reply_template_key") or "").strip().lower() work_item_id = cls._coerce_capture_work_item_id(capture_result) - if work_item_id is None: + queue_item_id = cls._coerce_capture_queue_item_id(capture_result) + if template_key != cls._TEMPLATE_QUEUE_PENDING_CONFIRM and work_item_id is None: return None reply_text = str(capture_result.get("direct_reply_text") or "").strip() safe_reply_text = reply_text[:3000] if reply_text else "已生成动作卡片,请点击按钮继续。" @@ -160,6 +177,35 @@ class FeishuCardActionMixin: "elements": [*base_elements, {"tag": "action", "actions": actions}], } + if template_key == cls._TEMPLATE_QUEUE_PENDING_CONFIRM and queue_item_id is not None: + actions = [ + { + "tag": "button", + "type": "primary", + "text": {"tag": "plain_text", "content": "继续执行"}, + "value": { + "action": cls._ACTION_CONTINUE, + "queue_item_id": int(queue_item_id), + }, + }, + { + "tag": "button", + "type": "danger", + "text": {"tag": "plain_text", "content": "取消"}, + "value": { + "action": cls._ACTION_CANCEL, + "queue_item_id": int(queue_item_id), + }, + }, + ] + return { + "config": {"wide_screen_mode": True, "enable_forward": True}, + "header": { + "title": {"tag": "plain_text", "content": "待确认动作"}, + }, + "elements": [*base_elements, {"tag": "action", "actions": actions}], + } + return None @classmethod @@ -195,6 +241,11 @@ class FeishuCardActionMixin: or value.get("target_work_item_id") or value.get("workItemId") ) + queue_item_id = cls._safe_int( + value.get("queue_item_id") + or value.get("item_id") + or value.get("queueItemId") + ) draft_version = cls._safe_int(value.get("draft_version") or value.get("version")) revision_note = ( str(value.get("revision_note") or value.get("note") or value.get("comment") or "") @@ -225,6 +276,7 @@ class FeishuCardActionMixin: return { "action": normalized_action, "work_item_id": work_item_id, + "queue_item_id": queue_item_id, "draft_version": draft_version, "revision_note": revision_note, "source_message_id": source_message_id, @@ -240,20 +292,69 @@ class FeishuCardActionMixin: parsed = self._extract_card_action_payload(event) action = str(parsed.get("action") or "").strip().lower() work_item_id = self._safe_int(parsed.get("work_item_id")) + queue_item_id = self._safe_int(parsed.get("queue_item_id")) if action not in {self._ACTION_CONFIRM, self._ACTION_REVISE, self._ACTION_CANCEL, self._ACTION_CONTINUE}: return {"code": 0, "msg": "card_action_ignored", "data": {"reason": "unsupported_action"}} - if work_item_id is None or work_item_id <= 0: + if (work_item_id is None or work_item_id <= 0) and (queue_item_id is None or queue_item_id <= 0): return {"code": 0, "msg": "card_action_ignored", "data": {"reason": "missing_work_item_id"}} from app.services.ai_pmo.ai_pmo_kernel.ai_pmo_kernel_runtime import AIPMOKernelService from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler + from app.services.integrations.feishu_signal_pipeline import FeishuSignalPipelineService operator_sender = str(parsed.get("sender_name") or "飞书用户").strip()[:80] or "飞书用户" source_message_id = str(parsed.get("source_message_id") or "").strip()[:128] or None chat_id = str(parsed.get("chat_id") or "").strip()[:128] or None sender_open_id = str(parsed.get("sender_open_id") or "").strip()[:128] or None try: - if action == self._ACTION_CONTINUE: + if queue_item_id is not None and queue_item_id > 0: + pipeline = FeishuSignalPipelineService(self.db, tenant_id=self.tenant_id) + operator_user = await pipeline._resolve_command_operator_user(sender=operator_sender) + if operator_user is None: + raise ValueError("operator_not_found") + if action == self._ACTION_CANCEL: + queue_result = await pipeline.dismiss_item( + item_id=int(queue_item_id), + reason="feishu_card_cancelled", + ) + normalized = { + "handled": True, + "status": "cancelled", + "followup_reply": f"已取消待处理项#{int(queue_item_id)}。", + "queue_item_id": int(queue_item_id), + "queue_status": str(queue_result.get("status") or "").strip() or None, + } + else: + queue_result = await pipeline.confirm_item( + item_id=int(queue_item_id), + user_id=int(operator_user.id), + ) + followup_reply = f"已继续处理待处理项#{int(queue_item_id)}。" + if queue_result.get("source_registry_id") and queue_result.get("project_id"): + followup_reply = ( + f"已继续处理:信号源注册#{queue_result['source_registry_id']}" + f" 已接入项目#{queue_result['project_id']}。" + ) + elif queue_result.get("task_id") and queue_result.get("project_id"): + followup_reply = ( + f"已继续处理:任务#{queue_result['task_id']}" + f" 已在项目#{queue_result['project_id']} 中创建。" + ) + elif queue_result.get("project_id"): + followup_reply = f"已继续处理,结果已落到项目#{queue_result['project_id']}。" + normalized = { + "handled": True, + "status": "succeeded", + "followup_reply": followup_reply, + "queue_item_id": int(queue_item_id), + "project_id": self._safe_int(queue_result.get("project_id")), + "task_id": self._safe_int(queue_result.get("task_id")), + "signal_id": self._safe_int(queue_result.get("signal_id")), + "source_registry_id": self._safe_int(queue_result.get("source_registry_id")), + } + if hasattr(self.db, "commit") and callable(getattr(self.db, "commit")): + await self.db.commit() + elif action == self._ACTION_CONTINUE: kernel = AIPMOKernelService(self.db, tenant_id=self.tenant_id) result = await kernel.process_ingress( source_system="ai_pmo_ui", @@ -310,15 +411,22 @@ class FeishuCardActionMixin: or normalized.get("work_item_id") or work_item_id ) + target_queue_item_id = self._safe_int( + normalized.get("queue_item_id") + or queue_item_id + ) template_key = "" if status == self._TEMPLATE_DRAFT_PENDING_CONFIRM: template_key = self._TEMPLATE_DRAFT_PENDING_CONFIRM elif status in {"pending_manual", "pending_confirmation"}: template_key = self._TEMPLATE_PENDING_MANUAL + elif status in {"queued_for_decision", "pending_queue", "pending_confirmation_queue"}: + template_key = self._TEMPLATE_QUEUE_PENDING_CONFIRM action_card = self._build_action_card_from_capture_result( capture_result={ "reply_template_key": template_key, "work_item_id": target_work_item_id, + "queue_item_id": target_queue_item_id, "draft_version": self._safe_int(normalized.get("draft_version")), "direct_reply_text": followup_reply, }, @@ -329,7 +437,7 @@ class FeishuCardActionMixin: await self._safe_reply_card( chat_id=chat_id, card_content=action_card, - trace_id=f"feishu-card-action-{int(work_item_id)}", + trace_id=f"feishu-card-action-{int(target_work_item_id or target_queue_item_id or 0)}", source_message_id=source_message_id, perf_t0=None, ) @@ -339,7 +447,7 @@ class FeishuCardActionMixin: mention_open_id=sender_open_id, mention_name=operator_sender, text=followup_reply, - trace_id=f"feishu-card-action-{int(work_item_id)}", + trace_id=f"feishu-card-action-{int(target_work_item_id or target_queue_item_id or 0)}", source_message_id=source_message_id, perf_t0=None, ) diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_config.py b/backend/app/services/integrations/feishu_integration_service/mixin_config.py index e0549242..8c58edb8 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_config.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_config.py @@ -61,8 +61,8 @@ class FeishuConfigMixin: config = integration.config or {} self.app_id = config.get('app_id') self.app_secret = config.get('app_secret') - self.encrypt_key = None - self.verification_token = None + self.encrypt_key = str(config.get('encrypt_key') or "").strip() or None + self.verification_token = str(config.get('verification_token') or "").strip() or None self.event_mode = self._normalize_event_mode(config.get('event_mode')) else: self.app_id = None @@ -86,13 +86,6 @@ class FeishuConfigMixin: ): """保存飞书配置到数据库""" mode = self._normalize_event_mode(event_mode) - config = { - 'app_id': app_id, - 'app_secret': app_secret, - 'encrypt_key': "", - 'verification_token': "", - 'event_mode': mode, - } # 查找或创建集成记录 result = await self.db.execute( @@ -102,6 +95,25 @@ class FeishuConfigMixin: ) ) integration = result.scalar_one_or_none() + existing_config: dict[str, Any] = {} + if integration: + if isinstance(integration.config, str): + try: + existing_config = json.loads(integration.config) if integration.config else {} + except Exception: + existing_config = {} + else: + existing_config = integration.config or {} + + resolved_encrypt_key = str(encrypt_key or "").strip() or str(existing_config.get('encrypt_key') or "").strip() + resolved_verification_token = str(verification_token or "").strip() or str(existing_config.get('verification_token') or "").strip() + config = { + 'app_id': app_id, + 'app_secret': app_secret, + 'encrypt_key': resolved_encrypt_key, + 'verification_token': resolved_verification_token, + 'event_mode': mode, + } if integration: integration.config = config @@ -120,8 +132,8 @@ class FeishuConfigMixin: # 更新内存配置 self.app_id = app_id self.app_secret = app_secret - self.encrypt_key = None - self.verification_token = None + self.encrypt_key = resolved_encrypt_key or None + self.verification_token = resolved_verification_token or None self.event_mode = mode self.tenant_id = tenant_id diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_receive.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive.py index 47455672..5892f122 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_receive.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_receive.py @@ -68,6 +68,11 @@ class FeishuReceiveMixin: message_id=(message.get("message_id") or "").strip() or None, chat_type=str(context.get("chat_type") or "group"), message_attachments=message_attachments, + thread_context=( + context.get("thread_context") + if isinstance(context.get("thread_context"), dict) + else None + ), ) except Exception as exc: return await self._handle_capture_exception_from_ingress( diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_receive_capture_flow.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive_capture_flow.py index 931e4762..9ae8a2d0 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_receive_capture_flow.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_receive_capture_flow.py @@ -16,6 +16,7 @@ class FeishuReceiveCaptureFlowMixin: message_id: str, chat_type: str, message_attachments: List[Dict[str, Any]], + thread_context: Optional[Dict[str, Any]], ) -> Dict[str, Any]: capture_service = ProactiveSignalCaptureService(self.db) result = await capture_service.capture_from_feishu( @@ -28,6 +29,7 @@ class FeishuReceiveCaptureFlowMixin: mentioned_bot_names=mentioned_bot_names, feishu_message_id=message_id, chat_type=chat_type, + thread_context=thread_context, ) await self._persist_signal_attachments_from_ingress( signal_id=result.get("signal_id"), diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_receive_prepare.py b/backend/app/services/integrations/feishu_integration_service/mixin_receive_prepare.py index ae3241d4..393e083f 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_receive_prepare.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_receive_prepare.py @@ -41,6 +41,11 @@ class FeishuReceivePrepareMixin: chat_type = str(chat.get("chat_type") or message.get("chat_type") or "").strip().lower() or "group" captured_at = self._extract_message_datetime(message) occurred_at_iso = captured_at.isoformat() if isinstance(captured_at, datetime) else None + thread_context = self._extract_thread_context( + event=event, + message=message, + message_payload=message_payload, + ) self._emit_state_transition( trace_id=state_trace_id, @@ -71,6 +76,127 @@ class FeishuReceivePrepareMixin: "chat_type": chat_type, "captured_at": captured_at, "occurred_at_iso": occurred_at_iso, + "thread_context": thread_context, + } + + @staticmethod + def _extract_thread_context( + *, + event: Dict[str, Any], + message: Dict[str, Any], + message_payload: Dict[str, Any], + ) -> Dict[str, Optional[str]]: + def _pick_text(*values: Any) -> Optional[str]: + for value in values: + text = str(value or "").strip() + if text: + return text[:128] + return None + + def _find_nested_text( + value: Any, + *, + candidate_keys: tuple[str, ...], + depth: int = 0, + max_depth: int = 4, + ) -> Optional[str]: + if depth > max_depth: + return None + if isinstance(value, dict): + for key in candidate_keys: + if key in value: + text = _pick_text(value.get(key)) + if text: + return text + for nested_value in value.values(): + nested_text = _find_nested_text( + nested_value, + candidate_keys=candidate_keys, + depth=depth + 1, + max_depth=max_depth, + ) + if nested_text: + return nested_text + elif isinstance(value, list): + for nested_value in value: + nested_text = _find_nested_text( + nested_value, + candidate_keys=candidate_keys, + depth=depth + 1, + max_depth=max_depth, + ) + if nested_text: + return nested_text + return None + + top_level = event if isinstance(event, dict) else {} + content = message.get("content") if isinstance(message.get("content"), dict) else {} + thread_id = _pick_text( + top_level.get("thread_id"), + top_level.get("message_thread_id"), + top_level.get("chat_thread_id"), + message.get("thread_id"), + message.get("message_thread_id"), + message.get("chat_thread_id"), + content.get("thread_id") if isinstance(content, dict) else None, + ) or _find_nested_text( + top_level, + candidate_keys=("thread_id", "message_thread_id", "chat_thread_id"), + ) or _find_nested_text( + message, + candidate_keys=("thread_id", "message_thread_id", "chat_thread_id"), + ) + root_message_id = _pick_text( + top_level.get("root_id"), + top_level.get("root_message_id"), + top_level.get("reply_root_id"), + message.get("root_id"), + message.get("root_message_id"), + message.get("reply_root_id"), + content.get("root_id") if isinstance(content, dict) else None, + ) or _find_nested_text( + top_level, + candidate_keys=("root_id", "root_message_id", "reply_root_id", "parent_message_id"), + ) or _find_nested_text( + message, + candidate_keys=("root_id", "root_message_id", "reply_root_id", "parent_message_id"), + ) + reply_to_message_id = _pick_text( + top_level.get("reply_to_message_id"), + top_level.get("reply_to_id"), + top_level.get("parent_id"), + top_level.get("reply_in_thread_message_id"), + message.get("reply_to_message_id"), + message.get("reply_to_id"), + message.get("parent_id"), + message.get("reply_in_thread_message_id"), + content.get("reply_to_message_id") if isinstance(content, dict) else None, + message_payload.get("reply_to_message_id") if isinstance(message_payload, dict) else None, + ) or _find_nested_text( + top_level, + candidate_keys=( + "reply_to_message_id", + "reply_to_id", + "parent_id", + "reply_in_thread_message_id", + "parent_message_id", + "reply_message_id", + ), + ) or _find_nested_text( + message, + candidate_keys=( + "reply_to_message_id", + "reply_to_id", + "parent_id", + "reply_in_thread_message_id", + "parent_message_id", + "reply_message_id", + ), + ) + return { + "thread_id": thread_id, + "root_message_id": root_message_id, + "reply_to_message_id": reply_to_message_id, } async def _reply_for_empty_text_ingress( diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py index 13583294..e7532b19 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_llm_core.py @@ -99,11 +99,19 @@ class FeishuReplyLLMCoreMixin: reply_text = self._strip_leading_thinking_paragraphs(reply_text) if not self._is_capability_question(message_content): reply_text = self._strip_identity_lead(reply_text) - if (not reply_text) or self._looks_like_provider_error(reply_text): - reply_text = self._fallback_reply_text(capture_result) + if not reply_text: + reply_text = self._build_llm_failure_reply_text( + capture_result, + reason="模型返回空内容", + ) + elif self._looks_like_provider_error(reply_text): + reply_text = self._build_llm_failure_reply_text( + capture_result, + reason=reply_text, + ) except Exception as exc: print(f"[Feishu] generate mention reply failed: {exc}") - reply_text = self._fallback_reply_text(capture_result) + reply_text = self._build_llm_failure_reply_text(capture_result, reason=str(exc)) reply_text, _ = self._guard_qa_reply_text_with_reason( reply_text, trace_id=str(trace_id or f"feishu-sm-{uuid4().hex[:12]}"), @@ -257,15 +265,29 @@ class FeishuReplyLLMCoreMixin: return False + @staticmethod + def _build_llm_failure_reply_text( + capture_result: Dict[str, Any], + *, + reason: Optional[str] = None, + ) -> str: + classification = str(capture_result.get("classification") or "").strip().lower() + scope_text = "项目级需求" if classification in {"project", "new_project"} else "信号" + reason_text = re.sub(r"\s+", " ", str(reason or "").strip()) + if reason_text: + return ( + f"当前回复生成失败,原因:{reason_text[:120]}。" + f"系统已保留这条{scope_text}处理结果,请稍后重试," + "或补充目标、范围和时间要求。" + ) + return ( + f"当前回复暂未生成到位,系统已保留这条{scope_text}处理结果。" + "请稍后重试,或补充目标、范围和时间要求。" + ) + @staticmethod def _fallback_reply_text(capture_result: Dict[str, Any]) -> str: - status = str(capture_result.get("status") or "") - classification = str(capture_result.get("classification") or "") - if status == "captured": - if classification in {"project", "new_project"}: - return "我已收到需求并完成初步识别,已按项目级信息入队处理。你可以继续补充目标、范围和时间,我会进一步细化分派。" - return "我已收到需求并完成初步识别,已进入信号处理流程。若你希望我直接分派任务,请补充目标、优先级和期望时间。" - return "我已收到消息并开始处理。若你希望我直接分派任务,请补充目标、优先级和时间要求。" + return FeishuReplyLLMCoreMixin._build_llm_failure_reply_text(capture_result) @staticmethod def _build_time_truth_reply(message_content: Optional[str]) -> Optional[str]: diff --git a/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py index 4208ab85..a3a3517c 100644 --- a/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py +++ b/backend/app/services/integrations/feishu_integration_service/mixin_reply_outbox.py @@ -256,6 +256,7 @@ class FeishuReplyOutboxMixin: return tenant_id = int(claimed.get("tenant_id") or 1) reply_hash = str(claimed.get("reply_hash") or "").strip() + sent_message_id: Optional[str] = None if reply_hash: async with AsyncSessionLocal() as db_check: sent_duplicate = ( @@ -296,15 +297,16 @@ class FeishuReplyOutboxMixin: card_content = cls._decode_outbox_card_message(raw_message) if not isinstance(card_content, dict): raise ValueError("invalid_outbox_card_payload") - await service.send_interactive_card( + send_result = await service.send_interactive_card( chat_id=str(claimed.get("chat_id") or ""), card_content=card_content, ) else: - await service.send_message_to_group( + send_result = await service.send_message_to_group( chat_id=str(claimed.get("chat_id") or ""), message=raw_message, ) + sent_message_id = cls._extract_sent_message_id(send_result) except Exception as exc: send_error = str(exc) @@ -324,6 +326,8 @@ class FeishuReplyOutboxMixin: row.sent_at = now_cn_naive() row.next_retry_at = None row.last_error = None + if sent_message_id: + row.sent_message_id = sent_message_id[:128] await db_update.commit() perf_t2 = time.perf_counter() perf_t0 = _OUTBOX_PERF_T0_BY_ID.pop(outbox_id, None) @@ -366,3 +370,21 @@ class FeishuReplyOutboxMixin: return None raw = f"{source}{body}".encode("utf-8") return hashlib.md5(raw, usedforsecurity=False).hexdigest() + + @staticmethod + def _extract_sent_message_id(response: Any) -> Optional[str]: + if not isinstance(response, dict): + return None + data = response.get("data") + data_dict = data if isinstance(data, dict) else {} + candidates = ( + response.get("message_id"), + response.get("msg_id"), + data_dict.get("message_id"), + data_dict.get("msg_id"), + ) + for value in candidates: + text = str(value or "").strip() + if text: + return text[:128] + return None diff --git a/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py index c10e4dea..bec717d1 100644 --- a/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py +++ b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_connection.py @@ -8,6 +8,7 @@ from .shared import ( Integration, Optional, asyncio, + ensure_feishu_tls_trust_bundle, json, logger, now_cn_aware, @@ -63,6 +64,8 @@ class LongConnectionLifecycleConnectionMixin: self._tenant_id = int((config or {}).get("tenant_id") or 1) app_id = str((config or {}).get("app_id") or "").strip() app_secret = str((config or {}).get("app_secret") or "").strip() + encrypt_key = str((config or {}).get("encrypt_key") or "").strip() + verification_token = str((config or {}).get("verification_token") or "").strip() self._configured = bool(app_id and app_secret) if not self._configured: @@ -76,6 +79,16 @@ class LongConnectionLifecycleConnectionMixin: return False try: + tls_bundle = ensure_feishu_tls_trust_bundle() + logger.info( + "feishu_long_connection_tls_bundle_ready", + tenant_id=self._tenant_id, + bundle_path=tls_bundle["bundle_path"], + source=tls_bundle["source"], + applied_keys=tls_bundle["applied_keys"], + has_encrypt_key=bool(encrypt_key), + has_verification_token=bool(verification_token), + ) await self._preflight_auth(tenant_id=self._tenant_id) self._last_auth_at = now_cn_aware().isoformat() except Exception as exc: @@ -98,7 +111,12 @@ class LongConnectionLifecycleConnectionMixin: self._last_disconnect_at = None self._restart_attempts = 0 self._next_retry_at = 0.0 - self._start_ws_thread(app_id, app_secret) + self._start_ws_thread( + app_id, + app_secret, + encrypt_key=encrypt_key, + verification_token=verification_token, + ) return True async def _preflight_auth(self, *, tenant_id: int) -> None: @@ -169,10 +187,17 @@ class LongConnectionLifecycleConnectionMixin: backoff = self._retry_base_seconds * (2 ** max(0, self._restart_attempts - 1)) self._next_retry_at = time.time() + backoff - def _start_ws_thread(self, app_id: str, app_secret: str) -> None: + def _start_ws_thread( + self, + app_id: str, + app_secret: str, + *, + encrypt_key: str = "", + verification_token: str = "", + ) -> None: self._thread = threading.Thread( target=self._run_ws_client, - args=(app_id, app_secret), + args=(app_id, app_secret, encrypt_key, verification_token), name="feishu-long-connection", daemon=True, ) diff --git a/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py index 11ac8009..70f91899 100644 --- a/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py +++ b/backend/app/services/integrations/feishu_long_connection_core/mixin_lifecycle_control.py @@ -40,6 +40,8 @@ class LongConnectionLifecycleControlMixin: self._last_error: Optional[str] = None self._last_error_code: Optional[str] = None self._last_error_retryable = False + self._last_received_at: Optional[str] = None + self._last_received_message_id: Optional[str] = None self._last_event_at: Optional[str] = None self._last_auth_at: Optional[str] = None self._last_connect_at: Optional[str] = None @@ -134,6 +136,8 @@ class LongConnectionLifecycleControlMixin: "last_error": self._last_error, "last_error_code": self._last_error_code, "last_error_retryable": self._last_error_retryable, + "last_received_at": self._last_received_at, + "last_received_message_id": self._last_received_message_id, "last_event_at": self._last_event_at, "last_auth_at": self._last_auth_at, "last_connect_at": self._last_connect_at, diff --git a/backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py b/backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py index 792b85ef..6f989497 100644 --- a/backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py +++ b/backend/app/services/integrations/feishu_long_connection_core/mixin_runtime.py @@ -2,18 +2,34 @@ from __future__ import annotations -from .shared import Any, FeishuIntegrationService, Optional, asyncio, logger, now_cn_aware, time +from .shared import ( + Any, + FeishuIntegrationService, + Optional, + asyncio, + ensure_feishu_tls_trust_bundle, + logger, + now_cn_aware, + time, +) class LongConnectionRuntimeMixin: """Owns SDK callback/runtime event processing.""" - def _run_ws_client(self, app_id: str, app_secret: str) -> None: + def _run_ws_client( + self, + app_id: str, + app_secret: str, + encrypt_key: str = "", + verification_token: str = "", + ) -> None: """Run blocking SDK websocket client in dedicated thread.""" try: + ensure_feishu_tls_trust_bundle() import lark_oapi as lark # type: ignore - builder = lark.EventDispatcherHandler.builder("", "") + builder = lark.EventDispatcherHandler.builder(encrypt_key or "", verification_token or "") register_receive = getattr(builder, "register_p2_im_message_receive_v1", None) if not callable(register_receive): raise RuntimeError("当前 lark_oapi 版本不支持 register_p2_im_message_receive_v1") @@ -80,6 +96,9 @@ class LongConnectionRuntimeMixin: payload = self._to_dict(data) message_id = "" event_type = "" + event_keys: list[str] = [] + message_keys: list[str] = [] + thread_debug: dict[str, Any] = {} try: event = payload.get("event", {}) if isinstance(payload, dict) else {} header = payload.get("header", {}) if isinstance(payload, dict) else {} @@ -91,6 +110,31 @@ class LongConnectionRuntimeMixin: or payload.get("type") or "" ).strip() + if isinstance(event, dict): + event_keys = [str(k) for k in sorted(event.keys())[:40]] + if isinstance(message, dict): + message_keys = [str(k) for k in sorted(message.keys())[:40]] + if isinstance(event, dict) and isinstance(message, dict): + candidate_keys = ( + "thread_id", + "message_thread_id", + "chat_thread_id", + "root_id", + "root_message_id", + "reply_root_id", + "reply_to_message_id", + "reply_to_id", + "parent_id", + "reply_in_thread_message_id", + ) + thread_debug = { + key: message.get(key) + for key in candidate_keys + if str(message.get(key) or "").strip() + } + for key in candidate_keys: + if key not in thread_debug and str(event.get(key) or "").strip(): + thread_debug[key] = event.get(key) except Exception: pass if isinstance(payload, dict): @@ -100,6 +144,27 @@ class LongConnectionRuntimeMixin: f"message_id={message_id or 'unknown'} " f"event_type={event_type or 'unknown'}" ) + self._last_received_at = now_cn_aware().isoformat() + self._last_received_message_id = message_id or None + if thread_debug: + logger.info( + "feishu_long_connection_thread_context_snapshot", + tenant_id=self._tenant_id, + message_id=message_id or "unknown", + event_type=event_type or "unknown", + event_keys=event_keys, + message_keys=message_keys, + thread_debug={k: str(v)[:128] for k, v in thread_debug.items()}, + ) + else: + logger.info( + "feishu_long_connection_event_keys_snapshot", + tenant_id=self._tenant_id, + message_id=message_id or "unknown", + event_type=event_type or "unknown", + event_keys=event_keys, + message_keys=message_keys, + ) logger.info( "feishu_long_connection_event_received: " f"event_type={event_type or 'unknown'}, message_id={message_id or 'unknown'}" diff --git a/backend/app/services/integrations/feishu_long_connection_core/shared.py b/backend/app/services/integrations/feishu_long_connection_core/shared.py index 47428364..04789247 100644 --- a/backend/app/services/integrations/feishu_long_connection_core/shared.py +++ b/backend/app/services/integrations/feishu_long_connection_core/shared.py @@ -11,6 +11,7 @@ import threading import time from typing import Any, Optional +import certifi from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from sqlalchemy.future import select @@ -24,6 +25,43 @@ logger = get_logger(__name__) DEFAULT_FEISHU_LONG_CONNECTION_RETRY_MAX_ATTEMPTS = 3 DEFAULT_FEISHU_LONG_CONNECTION_RETRY_BASE_SECONDS = 1.0 + +def ensure_feishu_tls_trust_bundle() -> dict[str, Any]: + """Ensure Feishu websocket/HTTP clients have a trustworthy CA bundle. + + The local development machine may not have the full system trust store + available for Feishu's certificate chain. We default to certifi and let + explicit environment overrides win. + """ + + explicit_bundle = str(os.getenv("FEISHU_LONG_CONNECTION_SSL_CERT_FILE") or "").strip() + current_ssl_bundle = str(os.getenv("SSL_CERT_FILE") or "").strip() + bundle_path = explicit_bundle or current_ssl_bundle or certifi.where() + source = ( + "FEISHU_LONG_CONNECTION_SSL_CERT_FILE" + if explicit_bundle + else "SSL_CERT_FILE" + if current_ssl_bundle + else "certifi" + ) + + applied_keys: list[str] = [] + if explicit_bundle: + for key in ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE"): + os.environ[key] = bundle_path + applied_keys.append(key) + else: + for key in ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE"): + if not os.getenv(key): + os.environ[key] = bundle_path + applied_keys.append(key) + + return { + "bundle_path": bundle_path, + "source": source, + "applied_keys": applied_keys, + } + __all__ = [ "Any", "Optional", @@ -41,6 +79,7 @@ __all__ = [ "FeishuIntegrationService", "CN_TZ", "now_cn_aware", + "ensure_feishu_tls_trust_bundle", "logger", "DEFAULT_FEISHU_LONG_CONNECTION_RETRY_MAX_ATTEMPTS", "DEFAULT_FEISHU_LONG_CONNECTION_RETRY_BASE_SECONDS", diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py index 071be92d..e0c2f9c2 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_project.py @@ -11,6 +11,7 @@ class FeishuPipelineCommandCommitProjectMixin: operator_user: User, payload: dict[str, Any], trace_id: str, + idempotency_key: Optional[str] = None, ) -> dict[str, Any]: operator_user_id = _safe_int(getattr(operator_user, "id", None)) if operator_user_id is None: @@ -20,6 +21,29 @@ class FeishuPipelineCommandCommitProjectMixin: if not title: raise ValueError("project title required") description = str(payload.get("description") or "").strip()[:4000] or None + normalized_idempotency_key = self._build_project_create_idempotency_key( + idempotency_key=idempotency_key, + trace_id=trace_id, + ) + + existing_ledger = await self._load_project_create_ledger( + idempotency_key=normalized_idempotency_key, + ) + if existing_ledger is not None and str(existing_ledger.status or "").strip().lower() in {"applied", "skipped"}: + stored_result = existing_ledger.result_payload if isinstance(existing_ledger.result_payload, dict) else {} + if stored_result: + return dict(stored_result) + + ledger = await self._claim_project_create_ledger( + idempotency_key=normalized_idempotency_key, + trace_id=trace_id, + payload={ + "title": title, + "description": description, + }, + ) + if ledger is not None and isinstance(ledger.result_payload, dict) and str(ledger.status or "").strip().lower() in {"applied", "skipped"}: + return dict(ledger.result_payload) existing = ( await self.db.execute( @@ -33,7 +57,7 @@ class FeishuPipelineCommandCommitProjectMixin: ) ).scalar_one_or_none() if existing is not None: - return { + result = { "status": "skipped", "message": "project_already_exists", "entity_type": "project", @@ -44,47 +68,83 @@ class FeishuPipelineCommandCommitProjectMixin: "status": str(existing.status or "").strip(), }, } + await self._finalize_project_create_ledger( + ledger=ledger, + status="skipped", + result=result, + project_id=int(existing.id), + ) + await self.db.commit() + return result await ensure_project_id_sequence(self.db) - project = Project( - tenant_id=self.tenant_id, - name=title, - title=title, - description=description, - owner_id=operator_user_id, - status="active", - domain_id=None, - assigned_role="ai_engineer", - signal_count=0, - ) - self.db.add(project) - await self.db.flush() - await ensure_project_default_members( - self.db, - tenant_id=self.tenant_id, - project_id=int(project.id), - owner_id=int(project.owner_id) if project.owner_id is not None else None, - assigned_role=project.assigned_role, - ) - await log_operation( - self.db, - tenant_id=self.tenant_id, - user_id=operator_user_id, - project_id=int(project.id), - entity_type="project", - entity_id=str(project.id), - action="project.create", - before_data=None, - after_data={ - "title": project.title, - "status": project.status, - "owner_id": int(project.owner_id) if project.owner_id is not None else None, - }, - remark=trace_id[:1000], - ) - await self.db.commit() - await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) - return { + project: Optional[Project] = None + try: + async with self.db.begin_nested(): + project = Project( + tenant_id=self.tenant_id, + name=title, + title=title, + description=description, + owner_id=operator_user_id, + status="active", + domain_id=None, + assigned_role="ai_engineer", + signal_count=0, + ) + self.db.add(project) + await self.db.flush() + await ensure_project_default_members( + self.db, + tenant_id=self.tenant_id, + project_id=int(project.id), + owner_id=int(project.owner_id) if project.owner_id is not None else None, + assigned_role=project.assigned_role, + ) + await log_operation( + self.db, + tenant_id=self.tenant_id, + user_id=operator_user_id, + project_id=int(project.id), + entity_type="project", + entity_id=str(project.id), + action="project.create", + before_data=None, + after_data={ + "title": project.title, + "status": project.status, + "owner_id": int(project.owner_id) if project.owner_id is not None else None, + }, + remark=trace_id[:1000], + ) + except IntegrityError: + duplicate = await self._load_existing_project_by_title(title=title) + if duplicate is None: + raise + result = { + "status": "skipped", + "message": "project_already_exists", + "entity_type": "project", + "entity_id": str(duplicate.id), + "result": { + "project_id": int(duplicate.id), + "title": str(duplicate.title or duplicate.name or "").strip(), + "status": str(duplicate.status or "").strip(), + }, + } + await self._finalize_project_create_ledger( + ledger=ledger, + status="skipped", + result=result, + project_id=int(duplicate.id), + ) + await self.db.commit() + return result + + if project is None: + raise ValueError("project_create_failed") + + result = { "status": "applied", "message": f"project created #{int(project.id)}", "entity_type": "project", @@ -95,4 +155,100 @@ class FeishuPipelineCommandCommitProjectMixin: "status": str(project.status or "").strip(), }, } + await self._finalize_project_create_ledger( + ledger=ledger, + status="applied", + result=result, + project_id=int(project.id), + ) + await self.db.commit() + await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) + return result + @staticmethod + def _build_project_create_idempotency_key( + *, + idempotency_key: Optional[str], + trace_id: str, + ) -> str: + raw = str(idempotency_key or "").strip() or str(trace_id or "").strip() + return raw[:128] + + async def _load_project_create_ledger( + self, + *, + idempotency_key: str, + ) -> Optional[OperationExecutionLedger]: + return ( + await self.db.execute( + select(OperationExecutionLedger).where( + and_( + OperationExecutionLedger.tenant_id == self.tenant_id, + OperationExecutionLedger.operation_code == "project.create", + OperationExecutionLedger.idempotency_key == idempotency_key, + ) + ).limit(1) + ) + ).scalar_one_or_none() + + async def _claim_project_create_ledger( + self, + *, + idempotency_key: str, + trace_id: str, + payload: dict[str, Any], + ) -> Optional[OperationExecutionLedger]: + ledger = await self._load_project_create_ledger(idempotency_key=idempotency_key) + if ledger is not None: + return ledger + try: + async with self.db.begin_nested(): + ledger = OperationExecutionLedger( + tenant_id=self.tenant_id, + operation_code="project.create", + project_id=None, + idempotency_key=idempotency_key, + trace_id=trace_id[:64] or None, + status="processing", + payload=payload, + result_payload=None, + entity_type="project", + entity_id=None, + error_message=None, + ) + self.db.add(ledger) + await self.db.flush() + return ledger + except IntegrityError: + return await self._load_project_create_ledger(idempotency_key=idempotency_key) + + async def _load_existing_project_by_title(self, *, title: str) -> Optional[Project]: + return ( + await self.db.execute( + select(Project).where( + and_( + Project.tenant_id == self.tenant_id, + or_(func.lower(Project.title) == title.lower(), func.lower(Project.name) == title.lower()), + Project.status != "inactive", + ) + ).limit(1) + ) + ).scalar_one_or_none() + + async def _finalize_project_create_ledger( + self, + *, + ledger: Optional[OperationExecutionLedger], + status: str, + result: dict[str, Any], + project_id: Optional[int], + ) -> None: + if ledger is None: + return + ledger.status = str(status or "").strip().lower() or "processing" + ledger.project_id = _safe_int(project_id) + ledger.result_payload = dict(result) + ledger.entity_type = "project" + ledger.entity_id = str(project_id) if project_id is not None else None + ledger.error_message = None + await self.db.flush() diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py index 674d72d1..6b8d8b15 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_commit_task.py @@ -11,10 +11,16 @@ class FeishuPipelineCommandCommitTaskMixin: operator_user: User, payload: dict[str, Any], trace_id: str, + idempotency_key: Optional[str] = None, ) -> dict[str, Any]: operator_user_id = _safe_int(getattr(operator_user, "id", None)) if operator_user_id is None: raise ValueError("operator_not_found") + + normalized_idempotency_key = self._build_task_create_idempotency_key( + idempotency_key=idempotency_key, + trace_id=trace_id, + ) project_id = _safe_int(payload.get("project_id")) if project_id is None: raise ValueError("project_context_missing") @@ -46,6 +52,26 @@ class FeishuPipelineCommandCommitTaskMixin: if charter is None: raise ValueError("aligned charter required before task.create") + existing_ledger = await self._load_task_create_ledger( + idempotency_key=normalized_idempotency_key, + ) + if existing_ledger is not None and str(existing_ledger.status or "").strip().lower() in {"applied", "skipped"}: + stored_result = existing_ledger.result_payload if isinstance(existing_ledger.result_payload, dict) else {} + if stored_result: + return dict(stored_result) + + ledger = await self._claim_task_create_ledger( + idempotency_key=normalized_idempotency_key, + trace_id=trace_id, + payload={ + "title": str(payload.get("title") or "").strip(), + "description": str(payload.get("description") or "").strip() or None, + "project_id": int(project_id), + }, + ) + if ledger is not None and isinstance(ledger.result_payload, dict) and str(ledger.status or "").strip().lower() in {"applied", "skipped"}: + return dict(ledger.result_payload) + task = await persist_task( self.db, tenant_id=self.tenant_id, @@ -59,20 +85,105 @@ class FeishuPipelineCommandCommitTaskMixin: actor_user_id=operator_user_id, action="task.create", remark=trace_id[:1000], - commit=True, + commit=False, refresh_workload=False, ) - await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) - self._sync_task_rag(task=task) - return { + task_id = int(task.id) + task_title = str(task.title or "").strip() + task_project_id = int(task.project_id) + result = { "status": "applied", - "message": f"task created #{int(task.id)}", + "message": f"task created #{task_id}", "entity_type": "task", - "entity_id": str(task.id), + "entity_id": str(task_id), "result": { - "task_id": int(task.id), - "title": str(task.title or "").strip(), - "project_id": int(project_id), + "task_id": task_id, + "title": task_title, + "project_id": task_project_id, }, } + await self._finalize_task_create_ledger( + ledger=ledger, + status="applied", + result=result, + task_id=task_id, + ) + await self.db.commit() + await context_cache_service.invalidate_system_context(tenant_id=self.tenant_id) + self._sync_task_rag(task=task) + return result + @staticmethod + def _build_task_create_idempotency_key( + *, + idempotency_key: Optional[str], + trace_id: str, + ) -> str: + raw = str(idempotency_key or "").strip() or str(trace_id or "").strip() + return raw[:128] + + async def _load_task_create_ledger( + self, + *, + idempotency_key: str, + ) -> Optional[OperationExecutionLedger]: + return ( + await self.db.execute( + select(OperationExecutionLedger).where( + and_( + OperationExecutionLedger.tenant_id == self.tenant_id, + OperationExecutionLedger.operation_code == "task.create", + OperationExecutionLedger.idempotency_key == idempotency_key, + ) + ).limit(1) + ) + ).scalar_one_or_none() + + async def _claim_task_create_ledger( + self, + *, + idempotency_key: str, + trace_id: str, + payload: dict[str, Any], + ) -> Optional[OperationExecutionLedger]: + ledger = await self._load_task_create_ledger(idempotency_key=idempotency_key) + if ledger is not None: + return ledger + try: + async with self.db.begin_nested(): + ledger = OperationExecutionLedger( + tenant_id=self.tenant_id, + operation_code="task.create", + project_id=_safe_int(payload.get("project_id")), + idempotency_key=idempotency_key, + trace_id=trace_id[:64] or None, + status="processing", + payload=payload, + result_payload=None, + entity_type="task", + entity_id=None, + error_message=None, + ) + self.db.add(ledger) + await self.db.flush() + return ledger + except IntegrityError: + return await self._load_task_create_ledger(idempotency_key=idempotency_key) + + async def _finalize_task_create_ledger( + self, + *, + ledger: Optional[OperationExecutionLedger], + status: str, + result: dict[str, Any], + task_id: Optional[int], + ) -> None: + if ledger is None: + return + ledger.status = str(status or "").strip().lower() or "processing" + ledger.project_id = _safe_int(result.get("result", {}).get("project_id") if isinstance(result.get("result"), dict) else None) + ledger.entity_type = "task" + ledger.entity_id = str(task_id) if task_id is not None else None + ledger.result_payload = dict(result) + ledger.error_message = None + await self.db.flush() diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py index a06e08ec..6bc549ac 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_command_exec_core.py @@ -70,6 +70,7 @@ class FeishuPipelineCommandExecCoreMixin: operation_code: str, payload: dict[str, Any], trace_id: str, + idempotency_key: Optional[str] = None, ) -> dict[str, Any]: logger.info( "operation_started", @@ -84,12 +85,14 @@ class FeishuPipelineCommandExecCoreMixin: operator_user=operator_user, payload=payload, trace_id=trace_id, + idempotency_key=idempotency_key, ) elif operation_code == "task.create": result = await self._commit_task_create( operator_user=operator_user, payload=payload, trace_id=trace_id, + idempotency_key=idempotency_key, ) else: raise ValueError("operation_not_supported") @@ -144,4 +147,3 @@ class FeishuPipelineCommandExecCoreMixin: ) except Exception: return - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py index f50331f1..dc8e20e0 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py @@ -163,6 +163,8 @@ class FeishuPipelineContextPrefillAMixin: source_chat_id: str, source_sender_open_id: Optional[str], sender: str, + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, limit: int = 3, ) -> list[dict[str, str]]: if self.db is None or not source_chat_id: @@ -171,6 +173,8 @@ class FeishuPipelineContextPrefillAMixin: source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, ) external_session_id = AdaptiveRAGService._external_session_id_from_key(conversation_key) if external_session_id is None: @@ -276,4 +280,3 @@ class FeishuPipelineContextPrefillAMixin: "missing_fields": missing_fields, "ready": not missing_fields, } - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py index f1acd9cd..02edf566 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_b.py @@ -13,6 +13,8 @@ class FeishuPipelineContextPrefillBMixin: source_sender_open_id: Optional[str], sender: str, fields: dict[str, Any], + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: if fields.get("ready"): return fields @@ -23,6 +25,8 @@ class FeishuPipelineContextPrefillBMixin: source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, limit=3, ) context_blocks: list[str] = [] @@ -39,12 +43,6 @@ class FeishuPipelineContextPrefillBMixin: context_blocks.append(f"最近系统回答:{recent_answer}") answer_hint = recent_answer - if not context_blocks: - reply_fallback = await self._load_recent_chat_reply_fallback_text(source_chat_id=source_chat_id) - if reply_fallback: - context_blocks.append(f"最近系统回答:{reply_fallback}") - answer_hint = reply_fallback - if not context_blocks: return fields @@ -230,4 +228,3 @@ class FeishuPipelineContextPrefillBMixin: if str(blocking_reason or "").strip(): return str(blocking_reason).strip() return _compact_text(fallback_text, max_len=140) - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py index 32e88e2a..0147a030 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_extraction.py @@ -257,9 +257,11 @@ class FeishuPipelineExtractionMixin: return { "suggested_name": suggested_name, "suggested_description": suggested_description, - "suggested_owner": "待指定", - "suggested_timeline": "待评估", - "confidence": 0.62, - "reasoning": ["LLM 返回异常,已按规则生成项目预填草案。"], + "suggested_owner": None, + "suggested_timeline": None, + "confidence": 0.35, + "reasoning": [ + "LLM 返回异常,已按规则生成项目预填草案。", + "负责人和时间线仅在消息明确给出时才填写。", + ], } - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py index 3271201d..65cb07f0 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_a.py @@ -2,6 +2,7 @@ from __future__ import annotations from .shared import * +from app.utils.thread_context import extract_thread_anchor class FeishuPipelineFollowupAMixin: @@ -11,8 +12,10 @@ class FeishuPipelineFollowupAMixin: message: str, sender: str, source_chat_id: str, + source_sender_open_id: Optional[str], feishu_message_id: Optional[str], process_started_at: float, + thread_context: Optional[dict[str, Any]] = None, ) -> Optional[dict[str, Any]]: if not source_chat_id: return None @@ -21,7 +24,26 @@ class FeishuPipelineFollowupAMixin: if not wants_continue and not wants_status: return None - recent_items = await self._load_recent_chat_items(source_chat_id=source_chat_id) + recent_items = await self._load_recent_chat_items( + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + sender=sender, + ) + thread_anchor = extract_thread_anchor(thread_context) + anchor_item = ( + next( + ( + item + for item in recent_items + if thread_anchor and str(getattr(item, "feishu_message_id", "") or "").strip() == thread_anchor + ), + None, + ) + if thread_anchor + else None + ) + if thread_anchor and anchor_item is None: + return None if not recent_items: reply_text = "当前群里还没有可追踪的飞书处理项。" decision = ROUTING_QUEUE_FOLLOWUP_BLOCKED @@ -31,7 +53,10 @@ class FeishuPipelineFollowupAMixin: mutations: list[dict[str, Any]] = [] results: list[dict[str, Any]] = [] elif wants_continue: - pending_items = [item for item in recent_items if self._normalize_route_status(item.route_status) == ROUTE_STATUS_QUEUED] + if anchor_item is not None and self._normalize_route_status(anchor_item.route_status) == ROUTE_STATUS_QUEUED: + pending_items = [anchor_item] + else: + pending_items = [item for item in recent_items if self._normalize_route_status(item.route_status) == ROUTE_STATUS_QUEUED] if len(pending_items) != 1: reply_text = self._build_followup_pending_clarification(pending_items or recent_items[:3]) decision = ROUTING_QUEUE_FOLLOWUP_BLOCKED @@ -144,14 +169,27 @@ class FeishuPipelineFollowupAMixin: } ] else: - target_item = self._select_followup_status_target(message=message, recent_items=recent_items) - reply_text = await self._build_followup_status_reply(target_item) - decision = ROUTING_QUEUE_FOLLOWUP_QUERY - reason = "命中进度/状态追问,已直接基于真实 DB 返回最近处理项状态。" - classification = "question" - status = "answered" - mutations = [] - results = [] + target_item = anchor_item or self._select_followup_status_target( + message=message, + recent_items=recent_items, + thread_context=thread_context, + ) + if target_item is None: + reply_text = self._build_followup_pending_clarification(recent_items[:3]) + decision = ROUTING_QUEUE_FOLLOWUP_BLOCKED + reason = "状态追问缺少唯一锚点,系统拒绝猜测最近一条处理项。" + classification = "question" + status = "answered" + mutations = [] + results = [] + else: + reply_text = await self._build_followup_status_reply(target_item, message=message) + decision = ROUTING_QUEUE_FOLLOWUP_QUERY + reason = "命中进度/状态追问,已直接基于真实 DB 返回唯一处理项状态。" + classification = "question" + status = "answered" + mutations = [] + results = [] await self._write_processing_log( feishu_message_id=feishu_message_id, @@ -172,7 +210,7 @@ class FeishuPipelineFollowupAMixin: routing_decision=decision, routing_reason=reason, reasoning_bullets=[ - "follow-up 查询/指令命中最近飞书上下文。", + "follow-up 查询/指令命中飞书线程锚点或唯一目标。", "回复内容只基于真实 DB 状态,不使用自由生成。", f"recent_items={len(recent_items)}", ], @@ -203,16 +241,26 @@ class FeishuPipelineFollowupAMixin: "db_mutations": mutations, } - async def _load_recent_chat_items(self, *, source_chat_id: str, limit: int = 5) -> list[FeishuItem]: + async def _load_recent_chat_items( + self, + *, + source_chat_id: str, + source_sender_open_id: Optional[str] = None, + sender: Optional[str] = None, + limit: int = 5, + ) -> list[FeishuItem]: + filters = [ + FeishuItem.tenant_id == self.tenant_id, + FeishuItem.source_chat_id == (source_chat_id or "")[:128], + ] + normalized_open_id = str(source_sender_open_id or "").strip() + if normalized_open_id: + filters.append(FeishuItem.source_sender_open_id == normalized_open_id[:128]) + _ = sender rows = ( await self.db.execute( select(FeishuItem) - .where( - and_( - FeishuItem.tenant_id == self.tenant_id, - FeishuItem.source_chat_id == (source_chat_id or "")[:128], - ) - ) + .where(and_(*filters)) .order_by(FeishuItem.id.desc()) .limit(limit) ) @@ -243,6 +291,15 @@ class FeishuPipelineFollowupAMixin: if level == "command": project_id = confirm_result.get("project_id") task_id = confirm_result.get("task_id") + source_registry_id = confirm_result.get("source_registry_id") + payload = target_item.result_payload if isinstance(target_item.result_payload, dict) else {} + operation_code = str(payload.get("operation_code") or "").strip().lower() + if operation_code == "source.connect": + if source_registry_id and project_id: + return f"已继续处理:信号源注册#{source_registry_id} 已接入项目#{project_id}。" + if project_id: + return f"已继续处理:信号源接入请求已绑定项目#{project_id} 并开始执行。" + return f"已继续处理:命令队列项#{int(target_item.id)} 已确认。" if task_id and project_id: return f"已继续处理:任务#{task_id} 已在项目#{project_id} 中创建。" if project_id: @@ -253,4 +310,3 @@ class FeishuPipelineFollowupAMixin: if signal_id and project_id: return f"已继续处理:信号#{signal_id} 已确认并关联项目#{project_id}。" return f"已继续处理:队列项#{int(target_item.id)} 已确认。" - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py index 3b7ae1f2..3e2377d6 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_followup_b.py @@ -2,22 +2,47 @@ from __future__ import annotations from .shared import * +from app.utils.thread_context import extract_thread_anchor class FeishuPipelineFollowupBMixin: + @staticmethod + def _extract_item_operation_code(item: FeishuItem) -> str: + payload = item.result_payload if isinstance(item.result_payload, dict) else {} + return str(payload.get("operation_code") or "").strip().lower() + @classmethod - def _select_followup_status_target(cls, *, message: str, recent_items: list[FeishuItem]) -> FeishuItem: + def _select_followup_status_target( + cls, + *, + message: str, + recent_items: list[FeishuItem], + thread_context: Optional[dict[str, Any]] = None, + ) -> Optional[FeishuItem]: if not recent_items: - raise ValueError("recent_items_empty") + return None normalized = re.sub(r"\s+", "", str(message or "")).lower() wants_project_create = any(token in normalized for token in ("创建", "建好", "建成", "项目")) wants_source_status = any(token in normalized for token in ("信号源", "web搜索", "webhook", "source")) + matches: list[FeishuItem] = [] + thread_anchor = extract_thread_anchor(thread_context) + if thread_anchor: + anchor_item = next( + ( + item + for item in recent_items + if str(getattr(item, "feishu_message_id", "") or "").strip() == thread_anchor + ), + None, + ) + return anchor_item if wants_source_status: for item in recent_items: operation_code = cls._extract_item_operation_code(item) if operation_code == "source.connect" or "信号源" in str(item.raw_message or ""): - return item + matches.append(item) + return matches[0] if len(matches) == 1 else None if wants_project_create: for item in recent_items: @@ -29,16 +54,42 @@ class FeishuPipelineFollowupBMixin: or operation_code == "project.create" or card_type in {"project_create", "new_project", "new_project_candidate"} ): - return item + matches.append(item) + return matches[0] if len(matches) == 1 else None - return recent_items[0] + return None - async def _build_followup_status_reply(self, item: FeishuItem) -> str: + async def _build_followup_status_reply(self, item: FeishuItem, *, message: str = "") -> str: route_status = self._normalize_route_status(item.route_status) level = str(item.level or "").strip().lower() if level == "project": suggestion = await self._get_project_suggestion(int(item.id)) project_id = suggestion.created_project_id if suggestion and suggestion.created_project_id else item.matched_project_id + project_row = None + if project_id is not None: + project_row = ( + await self.db.execute( + select(Project).where( + and_( + Project.tenant_id == self.tenant_id, + Project.id == int(project_id), + ) + ) + ) + ).scalar_one_or_none() + wants_created_at = bool( + re.search(r"(什么时候.*创建|何时.*创建|创建时间|立项时间|成立时间|创建于|何时立项|什么时候立项)", str(message or ""), re.IGNORECASE) + ) + if wants_created_at and project_row is not None: + created_at = project_row.created_at + if created_at is not None: + if created_at.tzinfo is not None: + created_at = created_at.astimezone(CN_TZ) + return ( + f"项目「{str(project_row.title or project_row.name or f'项目#{int(project_row.id)}')}」" + f"创建于 {created_at.strftime('%Y-%m-%d %H:%M')}。" + ) + return f"项目「{str(project_row.title or project_row.name or f'项目#{int(project_row.id)}')}」已找到,但未记录创建时间。" if route_status == ROUTE_STATUS_AUTO_DONE and project_id: return f"最近一条项目候选已确认,项目#{int(project_id)} 已落库。" if route_status == ROUTE_STATUS_UNDONE: @@ -50,7 +101,17 @@ class FeishuPipelineFollowupBMixin: operation_code = str(payload.get("operation_code") or "").strip() project_id = _safe_int(payload.get("project_id") or item.matched_project_id) summary = str(payload.get("result_summary") or "").strip() + signal_source = payload.get("signal_source") if isinstance(payload.get("signal_source"), dict) else {} + source_registry_id = _safe_int( + payload.get("source_registry_id") + or signal_source.get("registry_id") + ) if route_status == ROUTE_STATUS_AUTO_DONE: + if operation_code == "source.connect" and source_registry_id is not None: + return ( + f"最近一条信号源接入已完成,注册#{int(source_registry_id)}" + + (f",归属项目#{int(project_id)}。" if project_id is not None else "。") + ) return summary or ( f"最近一条命令已执行完成,归属项目#{int(project_id)}。" if project_id is not None @@ -59,6 +120,8 @@ class FeishuPipelineFollowupBMixin: if route_status == ROUTE_STATUS_UNDONE: return "最近一条命令结果已撤销。" if route_status == ROUTE_STATUS_QUEUED: + if operation_code == "source.connect": + return "最近一条信号源接入请求已入队待确认。" return ( f"最近一条命令 {operation_code or '待处理动作'} 已入队待确认。" if operation_code @@ -296,4 +359,3 @@ class FeishuPipelineFollowupBMixin: if operation_code == "notification.create": return f"通知#{row.get('entity_id') or ''}已创建".strip() return message or operation_code - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py index b5327f73..22fa3a50 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_dispatch.py @@ -91,6 +91,7 @@ class FeishuPipelineGateDispatchMixin: projects: list[dict[str, Any]], process_started_at: float, normalized_chat_type: str, + thread_context: Optional[dict[str, Any]] = None, ) -> Optional[dict[str, Any]]: gate_intent = str(intent_gate.get("intent") or INTENT_SIGNAL_INTAKE) if gate_intent == INTENT_SMALLTALK: @@ -155,6 +156,8 @@ class FeishuPipelineGateDispatchMixin: source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=feishu_message_id, + thread_context=thread_context, ) intent_result: dict[str, Any] = {} try: @@ -171,14 +174,14 @@ class FeishuPipelineGateDispatchMixin: question=rag_question, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, ) qa_capture = await self._build_direct_qa_capture( message=message, rag_result=rag_result, - inferred_project_id=inferred_project_id, + inferred_project_id=None, feishu_message_id=feishu_message_id, process_started_at=process_started_at, route_reason="intent gate 判定为 query,直接走 RAG 直答通道,不进入决策队列。", diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py index 7433a131..fe826fb4 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_gate_project.py @@ -66,6 +66,7 @@ class FeishuPipelineGateProjectMixin: "clarification_message": clarification_message, "direct_reply_required": True, "direct_reply_text": clarification_message, + "reply_template_key": "queue_pending_confirm", } if not self._can_execute_high_value_action(operator_user): @@ -130,79 +131,62 @@ class FeishuPipelineGateProjectMixin: "description": payload_description, } trace_id = f"intent-project-{self._stable_positive_int(message, sender, feishu_message_id)}" - execution = await self._execute_controlled_operation( - operator_user=operator_user, - operation_code="project.create", - payload=payload, - trace_id=trace_id, - ) - first = execution["results"][0] if execution.get("results") else {} - status = str(first.get("status") or "").strip().lower() - if status not in {"applied", "skipped"}: - raise ValueError(str(first.get("message") or "project.create failed")) - - project_result = first.get("result") if isinstance(first.get("result"), dict) else {} - project_id = _safe_int(project_result.get("project_id") or first.get("entity_id")) - item_id = await self._record_committed_operation_item( + queued = await self._queue_project_intent_item( message=message, sender=sender, source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, - captured_at=captured_at, - intent=INTENT_CREATE_PROJECT, - operation_code="project.create", - execution_result=first, - matched_project_id=project_id, + occurred_at=occurred_at, + intent_gate=intent_gate, + blocking_reason=clarification_message or "建项目已进入待确认队列,请点击继续执行。", + decision_reason="high_value_action_guard", + ) + item_id = _safe_int(queued.get("item_id")) + reply_text = ( + str(queued.get("clarification_message") or "").strip() + or "建项目已进入待确认队列,请点击继续执行。" ) - reply_text = self._build_operation_commit_reply({"execution": {"results": [first], "applied_count": 1 if status == "applied" else 0, "skipped_count": 1 if status == "skipped" else 0, "failed_count": 0}}) await self._write_processing_log( feishu_message_id=feishu_message_id, feishu_item_id=item_id, raw_message=message, - ai_raw_output=json.dumps({"intent_gate": intent_gate, "execution": execution}, ensure_ascii=False)[:120000], + ai_raw_output=json.dumps({"intent_gate": intent_gate, "queued": queued}, ensure_ascii=False)[:120000], project_score=1.0, signal_confidence=0.0, level="project", - routing_decision=ROUTING_OPERATION_COMMITTED, - routing_reason="建项目已通过 deterministic operation executor 提交,并以真实 DB 结果回执。", + routing_decision=ROUTING_INTENT_GATE_CLARIFICATION, + routing_reason="建项目已转为待确认卡片,等待人工点击继续执行。", reasoning_bullets=[ "intent gate 命中 create_project。", - "执行器仅走 project.create。", - "回复绑定真实 commit 结果。", + "执行器不再直接 commit,统一进入待确认卡片。", + "点击后再走受控 project.create。", ], processing_status="success", error_message=None, duration_ms=int((time.perf_counter() - process_started_at) * 1000), normalized_text=message, - route=ROUTING_OPERATION_COMMITTED, + route=ROUTING_INTENT_GATE_CLARIFICATION, intent=INTENT_CREATE_PROJECT, executor="explicit_service", operation_code="project.create", - operation_result=first if isinstance(first, dict) else None, - reply_source="db_commit", + operation_result={"status": "queued", "queue_item_id": item_id}, + reply_source="deterministic_reply", ) return { "status": "captured", "classification": "project", "project_scope": "project", - "linked_project_ids": [int(project_id)] if project_id is not None else [], + "linked_project_ids": [], "signal_id": None, - "created_project_id": int(project_id) if project_id is not None else None, + "created_project_id": None, "auto_tasks": [], - "ai_trace_id": trace_id, - "results": [ - { - "item_id": item_id, - "level": "command", - "project_id": int(project_id) if project_id is not None else None, - "signal_id": None, - "route_status": ROUTE_STATUS_AUTO_DONE, - } - ], + "ai_trace_id": f"feishu-project-pending-{item_id}", + "results": [queued], "intent": INTENT_CREATE_PROJECT, "direct_reply_required": True, "direct_reply_text": reply_text, - "command_result": {"execution": execution}, + "reply_template_key": "queue_pending_confirm", + "queue_item_id": item_id, + "command_result": {"queue": queued, "trace_id": trace_id}, } - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py index c4afed3e..f61fb9cd 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_intent_gate_core.py @@ -13,6 +13,8 @@ class FeishuPipelineIntentGateCoreMixin: source_chat_id: str = "", source_sender_open_id: Optional[str] = None, sender: str = "", + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: text = str(message or "").strip() if not text: @@ -121,6 +123,8 @@ class FeishuPipelineIntentGateCoreMixin: source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, fields=fields, ) if fields["ready"]: @@ -280,4 +284,3 @@ class FeishuPipelineIntentGateCoreMixin: def _strip_feishu_mention_prefix(text: str) -> str: cleaned = re.sub(r"^]*>[^<]*\s*", "", str(text or ""), flags=re.IGNORECASE).strip() return re.sub(r"\s+", " ", cleaned).strip() - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py index 5d2fee55..caf5c798 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_finalize_routing.py @@ -166,7 +166,7 @@ class FeishuPipelineItemFinalizeRoutingMixin: correction_keywords=[], complexity_reasons=[], ) - if cold_start_enabled or gate.action != "auto_execute" or confidence < signal_auto_enter_threshold or not matched_project_id: + if cold_start_enabled or gate.action != "auto_execute" or not matched_project_id: item.card_type = "signal" item.route_status = ROUTE_STATUS_QUEUED item.queue_visible = True @@ -181,14 +181,10 @@ class FeishuPipelineItemFinalizeRoutingMixin: item.blocking_reason = "当前处于冷启动保护期,信号入库暂不自动执行。" item.next_step_text = "确认后,AI 将继续完成信号入库并推进后续动作。" item.decision_reason = "保守自动化已拦截自动入库,避免早期误关联。" - elif gate.action == "reject": - item.blocking_reason = gate.reason - item.next_step_text = "请补充更明确的执行对象后重试,或人工确认继续处理。" - item.decision_reason = "共享决策内核判定置信度不足,自动执行被拒绝。" else: - item.blocking_reason = gate.reason or "当前置信度不足以自动入库,需要你拍板后再继续。" + item.blocking_reason = gate.reason or "当前信息不足以自动入库,需要你确认后再继续。" item.next_step_text = "确认后,AI 将把信号入库并进入后续生成流程。" - item.decision_reason = "信号虽然可执行,但归属判断仍存在不确定性。" + item.decision_reason = "信号仍需人工确认后再进入自动入库。" item.result_payload = { "result_summary": "信号已进入待确认队列。", "result_count": 0, @@ -222,4 +218,3 @@ class FeishuPipelineItemFinalizeRoutingMixin: signal=signal, ) return int(matched_project_id) - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py index e3594194..3e5bcd92 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_item_processing.py @@ -221,8 +221,12 @@ class FeishuPipelineItemProcessingMixin: feishu_item_id=int(item_id), suggested_name=(project_info.get("suggested_name") or content[:20] or "新项目建议")[:255], suggested_description=(project_info.get("suggested_description") or content[:400])[:4000], - suggested_owner=(project_info.get("suggested_owner") or sender or "待分配")[:120], - suggested_timeline=(project_info.get("suggested_timeline") or "待确认")[:120], + suggested_owner=( + str(project_info.get("suggested_owner") or "").strip()[:120] or None + ), + suggested_timeline=( + str(project_info.get("suggested_timeline") or "").strip()[:120] or None + ), confidence=_safe_float(project_info.get("confidence"), fallback_confidence), reasoning=json.dumps(_normalize_reasoning(project_info.get("reasoning")), ensure_ascii=False), status="pending", @@ -291,4 +295,3 @@ class FeishuPipelineItemProcessingMixin: "signal_id": None, "route_status": item.route_status, } - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py index d70ed30e..667c200b 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_a.py @@ -2,6 +2,7 @@ from __future__ import annotations from .shared import * +from app.utils.thread_context import build_feishu_thread_key class FeishuPipelineMessageUtilsAMixin: @@ -75,16 +76,16 @@ class FeishuPipelineMessageUtilsAMixin: source_chat_id: Optional[str], source_sender_open_id: Optional[str], sender: Optional[str], + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> str: - chat = str(source_chat_id or "").strip() - if not chat: - return "" - actor = str(source_sender_open_id or "").strip() - if not actor: - actor = str(sender or "").strip().lower() - actor = re.sub(r"\s+", "", actor) - actor = actor[:96] if actor else "unknown" - return f"feishu:{chat}:{actor}" + return build_feishu_thread_key( + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, + ) @staticmethod def _is_requirement_like_question(message: str) -> bool: @@ -137,4 +138,3 @@ class FeishuPipelineMessageUtilsAMixin: if converted: return converted return self._normalize_incoming_text(fallback_content) - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py index e6b2fb2c..092b9131 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_message_utils_b.py @@ -164,6 +164,7 @@ class FeishuPipelineMessageUtilsBMixin: matched_project_id: Optional[int], signal_auto_enter_threshold: float, ) -> tuple[str, str, list[str]]: + _ = signal_auto_enter_threshold if cold_start_enabled: return ( ROUTING_PENDING_COLD_START, @@ -174,31 +175,21 @@ class FeishuPipelineMessageUtilsBMixin: "等待人工审核后再入库。", ], ) - if confidence < signal_auto_enter_threshold: - return ( - ROUTING_PENDING_LOW_CONFIDENCE, - "信号置信度低于自动入库阈值,已进入待处理队列。", - [ - f"signal_confidence={confidence:.2f},低于 {signal_auto_enter_threshold:.2f} 阈值。", - "当前信息不足以支持自动决策。", - "需要人工确认后再执行后续动作。", - ], - ) if not matched_project_id: return ( ROUTING_PENDING_UNCERTAIN_PROJECT, - "置信度达标但项目归属不确定,转人工确认。", + "项目归属不确定,转人工确认。", [ - f"signal_confidence={confidence:.2f},达到自动判定线。", + f"signal_confidence={confidence:.2f},仅用于排序参考。", "未识别到明确项目归属。", "已进入待处理队列等待项目确认。", ], ) return ( ROUTING_AUTO_ENTER_SIGNAL, - "信号置信度达标且项目归属明确,系统已自动入库。", + "项目归属明确,系统已自动入库。", [ - f"signal_confidence={confidence:.2f},高于 {signal_auto_enter_threshold:.2f} 阈值。", + f"signal_confidence={confidence:.2f},仅用于排序参考。", f"已匹配项目 #{int(matched_project_id)}。", "已自动建立信号关联并发送通知。", ], @@ -261,4 +252,3 @@ class FeishuPipelineMessageUtilsBMixin: and any(token in text for token in action_tokens) and any(token in text for token in object_tokens) ) - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py index 40fa9279..9ef19db5 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_core.py @@ -15,6 +15,7 @@ class FeishuPipelineProcessMessageCoreMixin: feishu_message_id: Optional[str] = None, captured_at: Optional[datetime] = None, chat_type: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: process_started_at = time.perf_counter() normalized_chat_type = self._normalize_chat_type(chat_type=chat_type, source_chat_id=source_chat_id) @@ -124,8 +125,10 @@ class FeishuPipelineProcessMessageCoreMixin: message=message, sender=sender, source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, feishu_message_id=feishu_message_id, process_started_at=process_started_at, + thread_context=thread_context, ) if followup_capture is not None: await self.db.commit() @@ -159,6 +162,8 @@ class FeishuPipelineProcessMessageCoreMixin: source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=feishu_message_id, + thread_context=thread_context, ) logger.info( "intent_resolved", @@ -186,6 +191,7 @@ class FeishuPipelineProcessMessageCoreMixin: projects=projects, process_started_at=process_started_at, normalized_chat_type=normalized_chat_type, + thread_context=thread_context, ) if gate_capture is not None: return gate_capture @@ -203,5 +209,5 @@ class FeishuPipelineProcessMessageCoreMixin: process_started_at=process_started_at, normalized_chat_type=normalized_chat_type, chat_context_resolution=chat_context_resolution, + thread_context=thread_context, ) - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py index 723f04a8..ce933a49 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py @@ -20,12 +20,15 @@ class FeishuPipelineProcessMessageDefaultMixin: process_started_at: float, normalized_chat_type: str, chat_context_resolution: dict[str, Any], + thread_context: Optional[dict[str, Any]] = None, ) -> dict[str, Any]: rag_service = AdaptiveRAGService(self.db, tenant_id=self.tenant_id) conversation_key = self._build_rag_conversation_key( source_chat_id=source_chat_id, source_sender_open_id=source_sender_open_id, sender=sender, + source_message_id=feishu_message_id, + thread_context=thread_context, ) sidecar_rag_result: Optional[dict[str, Any]] = None @@ -54,7 +57,7 @@ class FeishuPipelineProcessMessageDefaultMixin: question=rag_question_part, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, message_intent="mixed", @@ -69,14 +72,14 @@ class FeishuPipelineProcessMessageDefaultMixin: question=rag_question_part, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, ) return await self._finalize_direct_qa_capture_result( message=message, rag_result=rag_result, - inferred_project_id=inferred_project_id, + inferred_project_id=None, feishu_message_id=feishu_message_id, process_started_at=process_started_at, route_reason="明显问句直接走 RAG 直答通道,不进入决策队列。", @@ -103,7 +106,7 @@ class FeishuPipelineProcessMessageDefaultMixin: question=rag_question_part, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, message_intent="mixed", @@ -118,14 +121,14 @@ class FeishuPipelineProcessMessageDefaultMixin: question=rag_question_part, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, ) return await self._finalize_direct_qa_capture_result( message=message, rag_result=rag_result, - inferred_project_id=inferred_project_id, + inferred_project_id=None, feishu_message_id=feishu_message_id, process_started_at=process_started_at, route_reason="识别为问答消息,已走 RAG 直答通道,不进入决策队列。", @@ -145,7 +148,7 @@ class FeishuPipelineProcessMessageDefaultMixin: question=rag_question_part, asked_by=None, asked_via="feishu", - project_id=inferred_project_id, + project_id=None, source_chat_id=source_chat_id, conversation_key=conversation_key, message_intent="mixed", diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py index c1498566..714b2ca9 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_helpers_b.py @@ -17,18 +17,28 @@ class FeishuPipelineProcessMessageHelpersBMixin: payload = dict(result or {}) direct_reply_text = str(payload.get("direct_reply_text") or "").strip() if direct_reply_text: - payload["direct_reply_text"] = FeishuSignalPipelineService._normalize_direct_reply_text(direct_reply_text) + payload["direct_reply_text"] = FeishuPipelineProcessMessageHelpersBMixin._normalize_direct_reply_text(direct_reply_text) clarification_message = str(payload.get("clarification_message") or "").strip() if clarification_message: - payload["clarification_message"] = FeishuSignalPipelineService._normalize_direct_reply_text(clarification_message) + payload["clarification_message"] = FeishuPipelineProcessMessageHelpersBMixin._normalize_direct_reply_text(clarification_message) followup_reply = str(payload.get("followup_reply") or "").strip() if followup_reply: - payload["followup_reply"] = FeishuSignalPipelineService._normalize_direct_reply_text(followup_reply) + payload["followup_reply"] = FeishuPipelineProcessMessageHelpersBMixin._normalize_direct_reply_text(followup_reply) payload["chat_type"] = chat_type payload["normalized_text"] = normalized_text payload["route"] = route payload["db_mutations"] = db_mutations payload.setdefault("results", []) + first_result = payload["results"][0] if payload["results"] and isinstance(payload["results"][0], dict) else {} + route_status = str(first_result.get("route_status") or "").strip().lower() + try: + raw_queue_item_id = first_result.get("item_id") or payload.get("queue_item_id") + queue_item_id = int(raw_queue_item_id) if raw_queue_item_id not in (None, "") else None + except (TypeError, ValueError): + queue_item_id = None + if route_status == ROUTE_STATUS_QUEUED and queue_item_id is not None: + payload.setdefault("reply_template_key", "queue_pending_confirm") + payload["queue_item_id"] = int(queue_item_id) return payload @staticmethod @@ -186,4 +196,3 @@ class FeishuPipelineProcessMessageHelpersBMixin: sources=rag_result.get("sources") or [], ), } - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py index fc6f8512..b9eafb9e 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_queue_confirm_undo.py @@ -6,12 +6,21 @@ from . import shared as _pipeline class FeishuPipelineQueueConfirmUndoMixin: + @staticmethod + def _build_operation_idempotency_key(*, item: FeishuItem) -> str: + source_message_id = str(getattr(item, "feishu_message_id", "") or "").strip() + if source_message_id: + return f"feishu:{source_message_id}"[:128] + return f"queue-item:{int(item.id)}" + async def confirm_item(self, *, item_id: int, user_id: int, action: Optional[str] = None) -> dict[str, Any]: item = await self._get_item(item_id) if not item: raise ValueError("item_not_found") level = (item.level or "").lower() + if self._normalize_route_status(item.route_status) == _pipeline.ROUTE_STATUS_AUTO_DONE: + return await self._build_existing_confirmation_result(item) result: dict[str, Any] = {"id": str(item.id), "level": level} if level == "project": suggestion = await self._get_project_suggestion(item.id) @@ -34,6 +43,7 @@ class FeishuPipelineQueueConfirmUndoMixin: "description": candidate_fields["description"], }, trace_id=f"confirm-project-{int(item.id)}-{int(user_id)}", + idempotency_key=self._build_operation_idempotency_key(item=item), ) first = execution["results"][0] if execution.get("results") else {} status = str(first.get("status") or "").strip().lower() @@ -61,31 +71,42 @@ class FeishuPipelineQueueConfirmUndoMixin: payload = item.result_payload if isinstance(item.result_payload, dict) else {} operation_code = str(payload.get("operation_code") or "").strip() operation_payload = payload.get("operation_payload") if isinstance(payload.get("operation_payload"), dict) else {} - if operation_code not in {"task.create"}: + if operation_code == "source.connect": + result.update( + await self._confirm_source_connect_item( + item=item, + operator_user=operator_user, + payload=payload, + operation_payload=operation_payload, + ) + ) + elif operation_code == "task.create": + execution = await self._execute_controlled_operation( + operator_user=operator_user, + operation_code=operation_code, + payload=operation_payload, + trace_id=f"confirm-command-{int(item.id)}-{int(user_id)}", + idempotency_key=self._build_operation_idempotency_key(item=item), + ) + first = execution["results"][0] if execution.get("results") else {} + status = str(first.get("status") or "").strip().lower() + if status not in {"applied", "skipped"}: + raise ValueError(str(first.get("message") or "command_execution_failed")) + project_id = _pipeline._safe_int(operation_payload.get("project_id") or payload.get("project_id") or item.matched_project_id) + task_id = _pipeline._safe_int(first.get("entity_id")) + item.matched_project_id = int(project_id) if project_id is not None else item.matched_project_id + item.result_payload = { + **payload, + "executor": "explicit_service", + "result_summary": f"已创建任务 #{task_id}" if task_id is not None else "已创建任务", + "operation_result": first.get("result") if isinstance(first.get("result"), dict) else {}, + "target_url": f"/projects/{project_id}/board" if project_id else None, + "project_id": project_id, + } + result["project_id"] = str(project_id) if project_id is not None else None + result["task_id"] = str(task_id) if task_id is not None else None + else: raise ValueError("command_operation_not_supported") - execution = await self._execute_controlled_operation( - operator_user=operator_user, - operation_code=operation_code, - payload=operation_payload, - trace_id=f"confirm-command-{int(item.id)}-{int(user_id)}", - ) - first = execution["results"][0] if execution.get("results") else {} - status = str(first.get("status") or "").strip().lower() - if status not in {"applied", "skipped"}: - raise ValueError(str(first.get("message") or "command_execution_failed")) - project_id = _pipeline._safe_int(operation_payload.get("project_id") or payload.get("project_id") or item.matched_project_id) - task_id = _pipeline._safe_int(first.get("entity_id")) - item.matched_project_id = int(project_id) if project_id is not None else item.matched_project_id - item.result_payload = { - **payload, - "executor": "explicit_service", - "result_summary": f"已创建任务 #{task_id}" if task_id is not None else "已创建任务", - "operation_result": first.get("result") if isinstance(first.get("result"), dict) else {}, - "target_url": f"/projects/{project_id}/board" if project_id else None, - "project_id": project_id, - } - result["project_id"] = str(project_id) if project_id is not None else None - result["task_id"] = str(task_id) if task_id is not None else None else: signal = await self._get_signal_by_item(item.id) if not signal: @@ -138,6 +159,121 @@ class FeishuPipelineQueueConfirmUndoMixin: await self.db.commit() return result + async def _build_existing_confirmation_result(self, item: FeishuItem) -> dict[str, Any]: + result = { + "id": str(item.id), + "level": str(item.level or "").lower(), + } + payload = item.result_payload if isinstance(item.result_payload, dict) else {} + if result["level"] == "project": + project_id = _pipeline._safe_int(payload.get("project_id") or item.matched_project_id) + result["project_id"] = str(project_id) if project_id is not None else None + return result + if result["level"] == "command": + project_id = _pipeline._safe_int(payload.get("project_id") or item.matched_project_id) + task_id = _pipeline._safe_int(payload.get("task_id") or (payload.get("operation_result") or {}).get("task_id")) + signal_source = payload.get("signal_source") if isinstance(payload.get("signal_source"), dict) else {} + source_registry_id = _pipeline._safe_int( + payload.get("source_registry_id") + or signal_source.get("registry_id") + ) + signal_id = _pipeline._safe_int(payload.get("signal_id")) + result.update( + { + "project_id": str(project_id) if project_id is not None else None, + "task_id": str(task_id) if task_id is not None else None, + "source_registry_id": str(source_registry_id) if source_registry_id is not None else None, + "signal_id": str(signal_id) if signal_id is not None else None, + } + ) + return result + signal = await self._get_signal_by_item(item.id) + if signal is not None: + result["signal_id"] = str(signal.id) + matched_project_id = signal.matched_project_id or item.matched_project_id + result["project_id"] = str(matched_project_id) if matched_project_id is not None else None + return result + + async def _confirm_source_connect_item( + self, + *, + item: FeishuItem, + operator_user: User, + payload: dict[str, Any], + operation_payload: dict[str, Any], + ) -> dict[str, Any]: + from ...signal.signal_source_orchestration import SignalSourceOrchestrationService + + project_id = _pipeline._safe_int( + operation_payload.get("project_id") + or payload.get("project_id") + or item.matched_project_id + ) + if project_id is None: + raise ValueError("source_connect_missing_project") + + signal = await self._get_signal_by_item(item.id) + if signal is None: + signal = Signal( + tenant_id=self.tenant_id, + source="Feishu", + content=str(item.raw_message or "")[:2000] or None, + impact_score=0.6, + priority="medium", + processed=False, + strategic_analysis=None, + tasks_generated=json.dumps([], ensure_ascii=False), + feishu_item_id=int(item.id), + matched_project_id=int(project_id), + title="信号源接入请求", + signal_type="新需求", + summary=str(item.raw_message or "")[:300] or "信号源接入请求", + reasoning=json.dumps(["Feishu 待确认卡片继续执行触发。"], ensure_ascii=False), + confidence=0.8, + recommended_action="enter_only", + status="待处理", + ) + self.db.add(signal) + await self.db.flush() + await self._ensure_signal_link(signal_id=int(signal.id), project_id=int(project_id)) + + orchestration = await SignalSourceOrchestrationService(self.db).orchestrate_from_signal( + tenant_id=self.tenant_id, + project_id=int(project_id), + signal_id=int(signal.id), + initiating_text=str(item.raw_message or ""), + requested_via="feishu", + operator_user_id=int(operator_user.id), + dry_run=False, + ) + orchestration_status = str(orchestration.get("status") or "").strip().lower() + if orchestration_status not in {"completed", "partial", "planned", "pending_manual"}: + raise ValueError(str(orchestration.get("reason") or "source_connect_failed")) + + signal_source = orchestration.get("signal_source") if isinstance(orchestration.get("signal_source"), dict) else {} + registry_id = _pipeline._safe_int(signal_source.get("registry_id")) + result_summary = "信号源接入已开始执行。" + if registry_id is not None: + result_summary = f"信号源注册#{int(registry_id)} 已创建并开始执行。" + item.matched_project_id = int(project_id) + item.result_payload = { + **payload, + "executor": "explicit_service", + "operation_code": "source.connect", + "operation_result": orchestration, + "signal_source": signal_source, + "source_registry_id": registry_id, + "signal_id": int(signal.id), + "result_summary": result_summary, + "target_url": f"/projects/{int(project_id)}/board", + "project_id": int(project_id), + } + return { + "project_id": str(project_id), + "signal_id": str(signal.id), + "source_registry_id": str(registry_id) if registry_id is not None else None, + } + async def undo_auto_processed(self, *, item_id: int) -> dict[str, Any]: item = await self._get_item(item_id) if not item: @@ -191,4 +327,3 @@ class FeishuPipelineQueueConfirmUndoMixin: } await self.db.commit() return {"id": str(item.id), "status": item.route_status} - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py b/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py index 23302dc3..e7d152a4 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/mixin_serialization.py @@ -77,6 +77,7 @@ class FeishuPipelineSerializationMixin: source_chat_id=str(item.source_chat_id or ""), source_sender_open_id=str(item.source_sender_open_id or "") or None, sender=str(item.source_sender or ""), + source_message_id=str(item.feishu_message_id or "") or None, fields=project_fields, ) if suggestion: @@ -201,4 +202,3 @@ class FeishuPipelineSerializationMixin: for history in history_rows ] return row - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/shared.py b/backend/app/services/integrations/feishu_signal_pipeline/shared.py index 63ddf1c5..b317671a 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/shared.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/shared.py @@ -3,3 +3,23 @@ from __future__ import annotations from .shared_core import * from .shared_schema_utils import * +from .shared_schema_utils import ( + _LevelClassificationSchema, + _LevelSplitItemSchema, + _ProjectPrefillSchema, + _SignalExtractionSchema, + _build_undo_deadline, + _compact_text, + _first_non_empty, + _normalize_level_value, + _normalize_reasoning, + _safe_bool, + _safe_dict, + _safe_float, + _safe_int, + _safe_json_object, + _utc_now, + _validate_schema, +) + +__all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py index 32ced794..758941b7 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/shared_core.py @@ -25,6 +25,7 @@ from ....models import ( AIProcessingLog, FeishuReplyOutbox, Integration, + OperationExecutionLedger, Project, ProjectSignalLink, ProjectSuggestion, @@ -213,4 +214,3 @@ AUTO_DONE_ITEM_ROUTE_STATUSES = { DEFAULT_SIGNAL_AUTO_ENTER_THRESHOLD = 0.80 DEFAULT_PROJECT_AUTO_CREATE_THRESHOLD = 0.85 DEFAULT_PROJECT_UNCERTAIN_THRESHOLD = 0.60 - diff --git a/backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py b/backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py index 2657bd61..cbdb8eb8 100644 --- a/backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py +++ b/backend/app/services/integrations/feishu_signal_pipeline/shared_schema_utils.py @@ -1,3 +1,14 @@ +from __future__ import annotations + +import json +from datetime import datetime, timedelta +from typing import Any, Literal, Optional + +from pydantic import BaseModel, Field, ValidationError + +from ....utils.timezone import now_cn_aware + + class _LevelSplitItemSchema(BaseModel): level: Literal["project", "signal"] content: str = Field(min_length=1, max_length=4000) @@ -30,8 +41,8 @@ class _SignalExtractionSchema(BaseModel): class _ProjectPrefillSchema(BaseModel): suggested_name: str = Field(min_length=1, max_length=255) suggested_description: str = Field(min_length=1, max_length=4000) - suggested_owner: str = Field(min_length=1, max_length=120) - suggested_timeline: str = Field(min_length=1, max_length=120) + suggested_owner: Optional[str] = Field(default=None, max_length=120) + suggested_timeline: Optional[str] = Field(default=None, max_length=120) confidence: float = Field(ge=0.0, le=1.0) reasoning: list[str] = Field(min_length=1, max_length=3) @@ -167,5 +178,3 @@ def _build_undo_deadline(value: Optional[datetime], *, minutes: int = 10) -> Opt if value.tzinfo is not None: value = value.astimezone(CN_TZ).replace(tzinfo=None) return value + timedelta(minutes=minutes) - - diff --git a/backend/app/services/llm_service/mixin_cache_circuit.py b/backend/app/services/llm_service/mixin_cache_circuit.py index 3eeca44b..3673e8ec 100644 --- a/backend/app/services/llm_service/mixin_cache_circuit.py +++ b/backend/app/services/llm_service/mixin_cache_circuit.py @@ -4,6 +4,7 @@ from .shared import * class LLMCacheCircuitMixin: _provider_circuit_state: dict[str, dict[str, Any]] = {} + _provider_circuit_config_signature: str = "" def __init__(self) -> None: self.request_timeout = float(os.getenv("LLM_REQUEST_TIMEOUT", "45")) @@ -11,6 +12,7 @@ class LLMCacheCircuitMixin: self._redis: Optional[Any] = None self._prompt_profile_cache: dict[int, tuple[float, dict[str, str]]] = {} self._openclaw_routing_cache: tuple[float, dict[str, Any]] | None = None + type(self)._provider_circuit_config_signature = type(self)._current_model_config_signature() async def _get_redis(self): if not REDIS_AVAILABLE or not self.redis_url: @@ -58,6 +60,30 @@ class LLMCacheCircuitMixin: def _provider_circuit_key(*, provider: str, base_url: str, model: str) -> str: return f"{str(provider or '').strip().lower()}|{str(base_url or '').strip().rstrip('/')}|{str(model or '').strip()}" + @classmethod + def _current_model_config_signature(cls) -> str: + bundle = load_model_config() + config_path = str(bundle.get("config_path") or "").strip() + if not config_path: + return "" + try: + mtime = Path(config_path).expanduser().stat().st_mtime + except Exception: + mtime = -1.0 + version = str(bundle.get("version") or "").strip() + updated_at = str(bundle.get("updated_at") or "").strip() + return f"{config_path}|{mtime}|{version}|{updated_at}" + + @classmethod + def _refresh_provider_circuit_state_if_needed(cls) -> None: + current_signature = cls._current_model_config_signature() + if not current_signature: + return + previous_signature = str(cls._provider_circuit_config_signature or "").strip() + if previous_signature and previous_signature != current_signature: + cls._provider_circuit_state.clear() + cls._provider_circuit_config_signature = current_signature + @staticmethod def _http_status_from_exception(exc: Exception) -> Optional[int]: if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None: @@ -126,6 +152,7 @@ class LLMCacheCircuitMixin: @classmethod def _is_provider_circuit_open(cls, circuit_key: str) -> bool: + cls._refresh_provider_circuit_state_if_needed() state = cls._provider_circuit_state.get(str(circuit_key)) if not state: return False @@ -142,10 +169,12 @@ class LLMCacheCircuitMixin: @classmethod def _record_provider_success(cls, circuit_key: str) -> None: + cls._refresh_provider_circuit_state_if_needed() cls._provider_circuit_state.pop(str(circuit_key), None) @classmethod def _record_provider_failure(cls, circuit_key: str, exc: Exception) -> None: + cls._refresh_provider_circuit_state_if_needed() if cls._is_provider_circuit_open_exception(exc): return if not cls._is_circuit_breaker_worthy_exception(exc): diff --git a/backend/app/services/llm_service/mixin_chat_openai.py b/backend/app/services/llm_service/mixin_chat_openai.py index 86b36495..c9ea5af2 100644 --- a/backend/app/services/llm_service/mixin_chat_openai.py +++ b/backend/app/services/llm_service/mixin_chat_openai.py @@ -54,6 +54,8 @@ class LLMChatOpenAIMixin: current_max_tokens = max(int(max_tokens or 0), 1) http_status_retry_budget = self._http_status_retry_budget_for_function(function_label) http_status_retry_attempt = 0 + network_retry_budget = 1 + network_retry_attempt = 0 use_json_response_format = bool(structured_output) direct_answer_retry_used = False while True: @@ -71,8 +73,39 @@ class LLMChatOpenAIMixin: "timeoutSeconds": int(openclaw_agents_timeout_seconds), } } - async with httpx.AsyncClient(timeout=request_timeout, trust_env=False) as client: - resp = await client.post(endpoint, headers=headers, json=payload) + try: + async with httpx.AsyncClient(timeout=request_timeout, trust_env=False) as client: + resp = await client.post(endpoint, headers=headers, json=payload) + except httpx.RequestError as exc: + if network_retry_budget > 0: + retry_delay = min(0.8 * (2 ** network_retry_attempt), 3.0) + _runtime_log( + "LLM", + ( + "网络异常重试:" + f"function={function_label}, provider={provider_name}, model={model}, " + f"retry_in={retry_delay:.2f}s, left={network_retry_budget - 1}, " + f"error={type(exc).__name__}" + ), + ) + logger.warning( + "llm_network_retry trace_id=%s function_name=%s provider=%s model=%s base_url=%s retry_attempt=%s retry_delay_seconds=%.2f error_type=%s error=%s", + request_trace_id, + function_label, + provider_name, + model, + str(base_url or "")[:120], + network_retry_attempt, + retry_delay, + type(exc).__name__, + str(exc)[:260], + ) + network_retry_budget -= 1 + network_retry_attempt += 1 + await asyncio.sleep(retry_delay) + continue + raise + else: if is_openclaw_gateway_request: elapsed_ms = (time.perf_counter() - request_started_at_perf) * 1000.0 _runtime_log( diff --git a/backend/app/services/llm_service/mixin_openclaw_routing.py b/backend/app/services/llm_service/mixin_openclaw_routing.py index 99232de4..d8059920 100644 --- a/backend/app/services/llm_service/mixin_openclaw_routing.py +++ b/backend/app/services/llm_service/mixin_openclaw_routing.py @@ -18,18 +18,7 @@ class LLMOpenclawRoutingMixin: return runtime def _openclaw_rules_path(self) -> str: - custom = str( - os.getenv( - "OPENCLAW_ROUTING_RULES_PATH", - OPENCLAW_DEFAULT_RULES_RELATIVE_PATH, - ) - or "" - ).strip() - raw_path = custom or OPENCLAW_DEFAULT_RULES_RELATIVE_PATH - candidate = Path(raw_path) - if not candidate.is_absolute(): - candidate = (Path(__file__).resolve().parents[2] / candidate).resolve() - return str(candidate) + return str(resolve_model_config_path()) def _load_openclaw_routing_bundle(self) -> dict[str, Any]: now = time.time() @@ -37,15 +26,7 @@ class LLMOpenclawRoutingMixin: return dict(self._openclaw_routing_cache[1]) path = Path(self._openclaw_rules_path()) - bundle: dict[str, Any] = {} - if path.exists(): - try: - parsed = json.loads(path.read_text(encoding="utf-8")) - if isinstance(parsed, dict): - bundle = parsed - except Exception as exc: - _runtime_log("OpenClaw路由", f"规则文件解析失败:path={path} error={exc}") - bundle = {} + bundle: dict[str, Any] = load_model_config(path=path) self._openclaw_routing_cache = (now + OPENCLAW_ROUTING_CACHE_TTL_SECONDS, dict(bundle)) return dict(bundle) @@ -186,7 +167,9 @@ class LLMOpenclawRoutingMixin: if base_url_override: routed["base_url"] = base_url_override if api_env_override: - routed["api_key"] = str(os.getenv(api_env_override, "") or "").strip() + env_api_key = str(os.getenv(api_env_override, "") or "").strip() + if env_api_key: + routed["api_key"] = env_api_key routed["api_env"] = api_env_override return routed diff --git a/backend/app/services/llm_service/mixin_prompt_profile_runtime.py b/backend/app/services/llm_service/mixin_prompt_profile_runtime.py index 34c74a0b..688570ba 100644 --- a/backend/app/services/llm_service/mixin_prompt_profile_runtime.py +++ b/backend/app/services/llm_service/mixin_prompt_profile_runtime.py @@ -105,21 +105,18 @@ class LLMPromptProfileRuntimeMixin: api_env = str(defaults.get("api_env") or "").strip() api_key = os.getenv(api_env, "") if api_env else "" if provider_name == "glm": - api_key = os.getenv("GLM_API_KEY", os.getenv("ZHIPUAI_API_KEY", api_key)) - - base_url = os.getenv(f"{provider_name.upper()}_BASE_URL", defaults["base_url"]) - model = os.getenv(f"{provider_name.upper()}_MODEL", defaults["model"]) - if provider_name == "ollama": - base_url = os.getenv("OLLAMA_BASE_URL", os.getenv("OLLAMA_HOST", defaults["base_url"])) - model = os.getenv("OLLAMA_MODEL", os.getenv("OLLAMA_CHAT_MODEL", defaults["model"])) + api_key = os.getenv( + "DASHSCOPE_API_KEY", + os.getenv("GLM_API_KEY", os.getenv("ZHIPUAI_API_KEY", api_key)), + ) return { "id": f"env-{provider_name}", "name": f"{defaults['name']} (ENV)", "provider": provider_name, "api_key": str(api_key or "").strip(), - "base_url": str(base_url or defaults["base_url"]).strip().rstrip("/"), - "model": str(model or defaults["model"]).strip(), + "base_url": str(defaults["base_url"]).strip().rstrip("/"), + "model": str(defaults["model"]).strip(), } def _env_engine_runtime(self) -> dict[str, Any]: diff --git a/backend/app/services/llm_service/mixin_tier_engine_selection.py b/backend/app/services/llm_service/mixin_tier_engine_selection.py index 5107e8df..387b660b 100644 --- a/backend/app/services/llm_service/mixin_tier_engine_selection.py +++ b/backend/app/services/llm_service/mixin_tier_engine_selection.py @@ -166,33 +166,10 @@ class LLMTierEngineSelectionMixin: active_engine: dict[str, Any], ) -> Optional[dict[str, Any]]: tier = str(tier or "").strip().lower() - upper = tier.upper() - default_provider_by_tier = { - "low": "glm", - "high": "openai", - "safety": "glm", - } - provider_env = str( - os.getenv( - f"LLM_ROUTING_{upper}_PROVIDER", - default_provider_by_tier.get( - tier, - os.getenv("LLM_PROVIDER", "glm"), - ), - ) - or "" - ).strip() - engine_id = str(os.getenv(f"LLM_ROUTING_{upper}_ENGINE_ID", "") or "").strip() - model_override = str(os.getenv(f"LLM_ROUTING_{upper}_MODEL", "") or "").strip() - base_url_override = str(os.getenv(f"LLM_ROUTING_{upper}_BASE_URL", "") or "").strip().rstrip("/") + tier_defaults = dict(TIER_DEFAULTS.get(tier) or {}) + provider_env = str(tier_defaults.get("provider") or "glm").strip() routed: Optional[dict[str, Any]] = None - if engine_id: - for item in engines: - if str(item.get("id") or "") == engine_id: - routed = self._normalize_engine_runtime(item) - break - if routed is None and provider_env: try: provider = _normalize_provider(provider_env) @@ -213,17 +190,10 @@ class LLMTierEngineSelectionMixin: return None provider = str(routed.get("provider") or "").strip().lower() - if model_override: - routed["model"] = model_override - elif self._openclaw_routing_enabled() and provider == "openai": - default_model = DEFAULT_TIER_MODEL_OVERRIDES.get(tier) - if default_model: - routed["model"] = default_model - if base_url_override: - routed["base_url"] = base_url_override - elif self._openclaw_routing_enabled() and provider == "openai": - configured_openai_base = str(os.getenv("OPENAI_BASE_URL", "") or "").strip().rstrip("/") - routed["base_url"] = configured_openai_base or "https://api.openai.com/v1" + if tier_defaults.get("model"): + routed["model"] = str(tier_defaults.get("model") or routed.get("model") or "").strip() + if tier_defaults.get("base_url"): + routed["base_url"] = str(tier_defaults.get("base_url") or routed.get("base_url") or "").strip().rstrip("/") if str(routed.get("id") or "") != str(active_engine.get("id") or ""): _runtime_log( @@ -262,4 +232,3 @@ class LLMTierEngineSelectionMixin: engine_label="目标引擎", ) raise ValueError("engine 不存在") - diff --git a/backend/app/services/llm_service/mixin_tier_escalation.py b/backend/app/services/llm_service/mixin_tier_escalation.py index 2660aca6..1bf0ebf4 100644 --- a/backend/app/services/llm_service/mixin_tier_escalation.py +++ b/backend/app/services/llm_service/mixin_tier_escalation.py @@ -6,6 +6,15 @@ class LLMTierEscalationMixin: def _low_to_high_escalation_enabled(self) -> bool: return _env_bool("LLM_LOW_TO_HIGH_ESCALATION_ENABLED", True) + @staticmethod + def _same_runtime_engine(a: dict[str, Any], b: dict[str, Any]) -> bool: + return ( + str(a.get("provider") or "").strip().lower() == str(b.get("provider") or "").strip().lower() + and str(a.get("model") or "").strip().lower() == str(b.get("model") or "").strip().lower() + and str(a.get("base_url") or "").strip().rstrip("/").lower() + == str(b.get("base_url") or "").strip().rstrip("/").lower() + ) + def _is_low_tier_function(self, function_name: str) -> bool: rule = self._match_openclaw_rule(function_name) if rule is not None: @@ -36,7 +45,16 @@ class LLMTierEscalationMixin: normalized = re.sub(r"[\s_]+", "", str(model_name or "")).lower() if not normalized: return False - return "glm" in normalized and "4.7" in normalized + if normalized.startswith("qwen"): + return True + return "glm" in normalized and ("4.7" in normalized or "47" in normalized) + + @staticmethod + def _is_glm_5_model(model_name: str) -> bool: + normalized = re.sub(r"[\s_]+", "", str(model_name or "")).lower() + if not normalized: + return False + return normalized in {"glm-5", "glm5"} or normalized.startswith("glm-5") or normalized.startswith("glm5") def _should_retry_glm47_with_high_tier( self, @@ -46,9 +64,6 @@ class LLMTierEscalationMixin: ) -> bool: if not self._low_to_high_escalation_enabled(): return False - fn = str(function_name or "").strip().lower() - if fn in {"classify_intent_ai_pmo_kernel", "classify_intent_complex"}: - return False failed_model = str(failed_engine.get("model") or "").strip() if self._is_glm_47_model(failed_model): return True @@ -58,6 +73,144 @@ class LLMTierEscalationMixin: return "glm" in lowered and "4." in lowered return False + def _prefer_glm5_high_tier_retry_engine( + self, + *, + bundle: dict[str, Any], + engines: list[dict[str, Any]], + function_name: str, + failed_engine: dict[str, Any], + current_engine: dict[str, Any], + ) -> Optional[dict[str, Any]]: + if self._is_glm_5_model(str(current_engine.get("model") or "")): + return current_engine + settings = self._normalize_engine_settings(bundle.get("settings")) + for item in engines: + candidate = dict(item) + if not self._is_glm_5_model(str(candidate.get("model") or "")): + continue + if self._same_runtime_engine(candidate, failed_engine): + continue + try: + candidate = self._ensure_engine_allowed_for_function( + engine=candidate, + function_name=function_name, + settings=settings, + engine_label="forced_glm5_failover", + ) + except Exception as exc: + _runtime_log( + "LLM路由", + ( + "GLM-5强制升级候选被拦截:" + f"function={function_name}, error={type(exc).__name__}:{str(exc)[:180]}" + ), + ) + continue + _runtime_log( + "LLM路由", + ( + "GLM-5强制高阶升级命中:" + f"function={function_name}, provider={candidate.get('provider')}, model={candidate.get('model')}" + ), + ) + return candidate + return None + + def _match_openclaw_high_tier_rule_for_escalation(self, function_name: str) -> Optional[dict[str, Any]]: + bundle = self._load_openclaw_routing_bundle() + rules = bundle.get("rules") if isinstance(bundle.get("rules"), list) else [] + indexed: list[tuple[int, int, dict[str, Any]]] = [] + for idx, row in enumerate(rules): + if not isinstance(row, dict): + continue + if not bool(row.get("enabled", True)): + continue + tier_name = str(row.get("tier") or "").strip().lower() + if tier_name != "high": + continue + if not self._rule_matches_function(row, function_name): + continue + priority = int(row.get("priority") or 0) + indexed.append((priority, -idx, row)) + if not indexed: + _runtime_log("LLM路由", f"OpenClaw高阶规则未命中:function={function_name}") + return None + indexed.sort(reverse=True) + rule = dict(indexed[0][2]) + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶规则候选:" + f"function={function_name}, rule={rule.get('name') or 'unnamed'}, " + f"priority={rule.get('priority')}, provider={rule.get('provider')}, model={rule.get('model')}" + ), + ) + return rule + + async def _resolve_openclaw_high_tier_engine_for_escalation( + self, + *, + bundle: dict[str, Any], + engines: list[dict[str, Any]], + active_engine: dict[str, Any], + function_name: str, + failed_engine: dict[str, Any], + ) -> Optional[dict[str, Any]]: + rule = self._match_openclaw_high_tier_rule_for_escalation(function_name) + if not isinstance(rule, dict): + return None + routed = self._resolve_engine_from_rule( + rule=rule, + engines=engines, + active_engine=active_engine, + ) + if not isinstance(routed, dict): + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶规则命中但未解析到可用引擎:" + f"function={function_name}, rule={rule.get('name') or 'unnamed'}" + ), + ) + return None + routed = self._ensure_openclaw_control_plane_engine(routed) + if self._same_runtime_engine(routed, failed_engine): + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶升级命中但与失败引擎同源,跳过:" + f"function={function_name}, provider={routed.get('provider')}, model={routed.get('model')}" + ), + ) + return None + try: + routed = self._ensure_engine_allowed_for_function( + engine=routed, + function_name=function_name, + settings=self._normalize_engine_settings(bundle.get("settings")), + engine_label="openclaw_high_tier_failover", + ) + except Exception as exc: + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶升级被拦截:" + f"function={function_name}, rule={rule.get('name') or 'unnamed'}, " + f"error={type(exc).__name__}:{str(exc)[:180]}" + ), + ) + return None + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶升级命中:" + f"function={function_name}, rule={rule.get('name') or 'unnamed'}, " + f"provider={routed.get('provider')}, model={routed.get('model')}" + ), + ) + return routed + async def _retry_with_high_tier_after_low_model_failure( self, *, @@ -78,11 +231,34 @@ class LLMTierEscalationMixin: _, bundle = await self._get_or_init_bundle(db, tenant_id=tenant_id) engines = [item for item in bundle.get("engines", []) if isinstance(item, dict)] active_engine = self._resolve_active_engine_from_bundle(bundle) - high_engine = self._resolve_tier_engine( - tier="high", + _runtime_log( + "LLM路由", + ( + "低阶模型失败准备升级高阶:" + f"function={function_name}, from={str(failed_engine.get('provider') or '').strip().lower()}:" + f"{str(failed_engine.get('model') or '').strip()}, reason={str(error)[:120]}" + ), + ) + high_engine = await self._resolve_openclaw_high_tier_engine_for_escalation( + bundle=bundle, engines=engines, active_engine=active_engine, + function_name=function_name, + failed_engine=failed_engine, ) + if not isinstance(high_engine, dict): + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶升级未命中,回退通用 high tier:" + f"function={function_name}, reason={str(error)[:120]}" + ), + ) + high_engine = self._resolve_tier_engine( + tier="high", + engines=engines, + active_engine=active_engine, + ) if not isinstance(high_engine, dict): return None, None high_engine = self._ensure_engine_allowed_for_function( @@ -91,11 +267,27 @@ class LLMTierEscalationMixin: settings=self._normalize_engine_settings(bundle.get("settings")), engine_label="high_tier_failover", ) + forced_glm5_engine = self._prefer_glm5_high_tier_retry_engine( + bundle=bundle, + engines=engines, + function_name=function_name, + failed_engine=failed_engine, + current_engine=high_engine, + ) + if isinstance(forced_glm5_engine, dict): + high_engine = forced_glm5_engine failed_provider = str(failed_engine.get("provider") or "").strip().lower() failed_model = str(failed_engine.get("model") or "").strip() high_provider = str(high_engine.get("provider") or "").strip().lower() high_model = str(high_engine.get("model") or "").strip() if failed_provider == high_provider and failed_model == high_model: + _runtime_log( + "LLM路由", + ( + "高阶升级引擎与失败引擎相同,跳过:" + f"function={function_name}, provider={high_provider}, model={high_model}" + ), + ) return None, None _runtime_log( "LLM路由", @@ -121,6 +313,13 @@ class LLMTierEscalationMixin: raise ValueError("high_tier_empty_response") if self._is_invalid_structured_output(function_name=function_name, response_text=text): raise ValueError("high_tier_invalid_structured_output") + _runtime_log( + "LLM路由", + ( + "高阶升级成功:" + f"function={function_name}, provider={high_provider}, model={high_model}, text_len={len(text)}" + ), + ) return text, high_engine except Exception as retry_exc: _runtime_log( @@ -157,11 +356,36 @@ class LLMTierEscalationMixin: _, bundle = await self._get_or_init_bundle(db, tenant_id=tenant_id) engines = [item for item in bundle.get("engines", []) if isinstance(item, dict)] active_engine = self._resolve_active_engine_from_bundle(bundle) - high_engine = self._resolve_tier_engine( - tier="high", + _runtime_log( + "LLM路由", + ( + "低阶结构化失败准备升级高阶:" + f"function={function_name}, from={str(failed_engine.get('provider') or '').strip().lower()}:" + f"{str(failed_engine.get('model') or '').strip()}, reason={str(error)[:120]}, " + f"force_intent_retry={force_intent_retry}" + ), + ) + high_engine = await self._resolve_openclaw_high_tier_engine_for_escalation( + bundle=bundle, engines=engines, active_engine=active_engine, + function_name=function_name, + failed_engine=failed_engine, ) + if not isinstance(high_engine, dict): + _runtime_log( + "LLM路由", + ( + "OpenClaw高阶结构化升级未命中,回退通用 high tier:" + f"function={function_name}, reason={str(error)[:120]}, " + f"force_intent_retry={force_intent_retry}" + ), + ) + high_engine = self._resolve_tier_engine( + tier="high", + engines=engines, + active_engine=active_engine, + ) if not isinstance(high_engine, dict): return None, None high_engine = self._ensure_engine_allowed_for_function( @@ -170,12 +394,28 @@ class LLMTierEscalationMixin: settings=self._normalize_engine_settings(bundle.get("settings")), engine_label="high_tier_structured_failover", ) + forced_glm5_engine = self._prefer_glm5_high_tier_retry_engine( + bundle=bundle, + engines=engines, + function_name=function_name, + failed_engine=failed_engine, + current_engine=high_engine, + ) + if isinstance(forced_glm5_engine, dict): + high_engine = forced_glm5_engine failed_provider = str(failed_engine.get("provider") or "").strip().lower() failed_model = str(failed_engine.get("model") or "").strip() high_provider = str(high_engine.get("provider") or "").strip().lower() high_model = str(high_engine.get("model") or "").strip() if failed_provider == high_provider and failed_model == high_model: if not force_intent_retry: + _runtime_log( + "LLM路由", + ( + "高阶结构化升级引擎与失败引擎相同,跳过:" + f"function={function_name}, provider={high_provider}, model={high_model}" + ), + ) return None, None fallback_engine: Optional[dict[str, Any]] = None for fallback_tier in ("safety", "low"): @@ -244,6 +484,14 @@ class LLMTierEscalationMixin: f"validation={extraction.validation}, fallback=true" ), ) + _runtime_log( + "LLM路由", + ( + "高阶结构化升级成功:" + f"function={function_name}, provider={high_provider}, model={high_model}, " + f"structured_source={extraction.source_field or 'none'}" + ), + ) return extraction, high_engine except Exception as retry_exc: _runtime_log( diff --git a/backend/app/services/llm_service/shared_part_01.py b/backend/app/services/llm_service/shared_part_01.py index 837369ab..67090313 100644 --- a/backend/app/services/llm_service/shared_part_01.py +++ b/backend/app/services/llm_service/shared_part_01.py @@ -27,6 +27,14 @@ from sqlalchemy import and_, select from sqlalchemy.exc import OperationalError from sqlalchemy.ext.asyncio import AsyncSession +from ..model.model_config import ( + build_provider_runtime, + build_tier_runtime, + get_provider_defaults, + get_tier_defaults, + load_model_config, + resolve_model_config_path, +) from ...config.ai_prompts import build_default_role_prompt_bundle from ...core.database import AsyncSessionLocal from ...models import AICallLog, Integration @@ -44,35 +52,13 @@ except Exception: # pragma: no cover logger = logging.getLogger(__name__) SUPPORTED_PROVIDERS = {"deepseek", "glm", "openai", "ollama"} -OPENCLAW_DEFAULT_LOW_MODEL = "GLM-4.7" -OPENCLAW_DEFAULT_HIGH_MODEL = "MiniMax-M2.5" -OPENCLAW_DEFAULT_SAFETY_MODEL = "deepseek-chat" -PROVIDER_DEFAULTS: dict[str, dict[str, str]] = { - "deepseek": { - "name": "DeepSeek", - "base_url": "https://api.deepseek.com/v1", - "model": "deepseek-chat", - "api_env": "DEEPSEEK_API_KEY", - }, - "glm": { - "name": "GLM", - "base_url": "https://open.bigmodel.cn/api/paas/v4", - "model": "GLM-4.7", - "api_env": "GLM_API_KEY", - }, - "openai": { - "name": "OpenAI", - "base_url": "https://api.openai.com/v1", - "model": "gpt-4o-mini", - "api_env": "OPENAI_API_KEY", - }, - "ollama": { - "name": "Ollama", - "base_url": "http://127.0.0.1:11434", - "model": "llama3:latest", - "api_env": "", - }, -} +MODEL_CONFIG_BUNDLE = load_model_config() +PROVIDER_DEFAULTS: dict[str, dict[str, str]] = get_provider_defaults() +TIER_DEFAULTS: dict[str, dict[str, str]] = get_tier_defaults() +OPENCLAW_DEFAULT_LOW_MODEL = str(TIER_DEFAULTS.get("low", {}).get("model") or "qwen-plus") +OPENCLAW_DEFAULT_HIGH_MODEL = str(TIER_DEFAULTS.get("high", {}).get("model") or "GLM-5") +OPENCLAW_DEFAULT_SAFETY_MODEL = str(TIER_DEFAULTS.get("safety", {}).get("model") or OPENCLAW_DEFAULT_LOW_MODEL) +OPENCLAW_DASHSCOPE_BASE_URL = str(PROVIDER_DEFAULTS.get("glm", {}).get("base_url") or "https://dashscope.aliyuncs.com/compatible-mode/v1") PROVIDER_ALIASES = { "zhipu": "glm", "chatglm": "glm", @@ -86,9 +72,8 @@ OPENCLAW_DEFAULT_RULES_RELATIVE_PATH = "../.openclaw/pmflow-routing-rules.json" OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS = 120 OPENCLAW_AGENTS_TIMEOUT_SECONDS_MAX = 120 DEFAULT_TIER_MODEL_OVERRIDES = { - "low": OPENCLAW_DEFAULT_LOW_MODEL, - "high": OPENCLAW_DEFAULT_HIGH_MODEL, - "safety": OPENCLAW_DEFAULT_SAFETY_MODEL, + tier: str(spec.get("model") or "") + for tier, spec in TIER_DEFAULTS.items() } DEFAULT_HIGH_TIER_HINTS = ( "classify_intent_complex", diff --git a/backend/app/services/model/model_config.py b/backend/app/services/model/model_config.py new file mode 100644 index 00000000..bbb51ed6 --- /dev/null +++ b/backend/app/services/model/model_config.py @@ -0,0 +1,238 @@ +from __future__ import annotations + +import json +import os +from copy import deepcopy +from pathlib import Path +from typing import Any + +DEFAULT_ROUTING_RULES_RELATIVE_PATH = "../.openclaw/pmflow-routing-rules.json" +_APP_ROOT = Path(__file__).resolve().parents[2] +_DEFAULT_ROUTING_RULES_PATH = (_APP_ROOT / DEFAULT_ROUTING_RULES_RELATIVE_PATH).resolve() + +_PROVIDER_ALIASES = { + "zhipu": "glm", + "chatglm": "glm", + "deep-seek": "deepseek", + "local": "ollama", +} + +_DEFAULT_PROVIDER_DEFAULTS: dict[str, dict[str, str]] = { + "deepseek": { + "name": "DeepSeek", + "base_url": "https://api.deepseek.com/v1", + "model": "deepseek-chat", + "api_env": "DEEPSEEK_API_KEY", + }, + "glm": { + "name": "Qwen (DashScope)", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + }, + "openai": { + "name": "OpenAI", + "base_url": "https://api.edgefn.net/v1", + "model": "MiniMax-M2.5", + "api_env": "OPENAI_API_KEY", + }, + "ollama": { + "name": "Ollama", + "base_url": "http://127.0.0.1:11434", + "model": "llama3:latest", + "api_env": "", + }, +} + +_DEFAULT_TIER_DEFAULTS: dict[str, dict[str, str]] = { + "low": { + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_env": "DASHSCOPE_API_KEY", + }, + "high": { + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.edgefn.net/v1", + "api_env": "OPENAI_API_KEY", + }, + "safety": { + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_env": "DASHSCOPE_API_KEY", + }, +} + +_CONFIG_CACHE: dict[str, Any] = {"path": "", "mtime": -1.0, "bundle": None} + + +def _normalize_provider(provider: str) -> str: + value = str(provider or "").strip().lower() + value = _PROVIDER_ALIASES.get(value, value) + if value not in _DEFAULT_PROVIDER_DEFAULTS: + raise ValueError("provider 仅支持 deepseek/glm/openai/ollama") + return value + + +def _normalize_tier(tier: str) -> str: + value = str(tier or "").strip().lower() + if value not in _DEFAULT_TIER_DEFAULTS: + raise ValueError("tier 仅支持 low/high/safety") + return value + + +def resolve_model_config_path(path: str | Path | None = None) -> Path: + raw = path if path is not None else os.getenv("OPENCLAW_ROUTING_RULES_PATH", "") + raw_text = str(raw or "").strip() + if not raw_text: + return _DEFAULT_ROUTING_RULES_PATH + candidate = Path(raw_text).expanduser() + if candidate.is_absolute(): + return candidate + return (_APP_ROOT / candidate).resolve() + + +def _normalize_provider_defaults(raw: Any) -> dict[str, dict[str, str]]: + payload = raw if isinstance(raw, dict) else {} + result: dict[str, dict[str, str]] = {} + for provider, defaults in _DEFAULT_PROVIDER_DEFAULTS.items(): + item = payload.get(provider) if isinstance(payload.get(provider), dict) else {} + result[provider] = { + "name": str(item.get("name") or defaults["name"]).strip(), + "base_url": str(item.get("base_url") or defaults["base_url"]).strip().rstrip("/"), + "model": str(item.get("model") or defaults["model"]).strip(), + "api_env": str(item.get("api_env") or defaults["api_env"]).strip(), + } + return result + + +def _normalize_tier_defaults(raw: Any) -> dict[str, dict[str, str]]: + payload = raw if isinstance(raw, dict) else {} + result: dict[str, dict[str, str]] = {} + for tier, defaults in _DEFAULT_TIER_DEFAULTS.items(): + item = payload.get(tier) if isinstance(payload.get(tier), dict) else {} + provider_raw = str(item.get("provider") or defaults["provider"]).strip() + provider = _normalize_provider(provider_raw) + result[tier] = { + "provider": provider, + "model": str(item.get("model") or defaults["model"]).strip(), + "base_url": str(item.get("base_url") or defaults["base_url"]).strip().rstrip("/"), + "api_env": str(item.get("api_env") or defaults["api_env"]).strip(), + } + return result + + +def _normalize_bundle(raw: Any, *, config_path: Path) -> dict[str, Any]: + payload = raw if isinstance(raw, dict) else {} + bundle = { + "version": str(payload.get("version") or "v3").strip(), + "updated_at": str(payload.get("updated_at") or "").strip(), + "source_of_truth": str(payload.get("source_of_truth") or "openclaw").strip(), + "description": str(payload.get("description") or "").strip(), + "config_path": str(config_path), + "provider_defaults": _normalize_provider_defaults(payload.get("provider_defaults")), + "tier_defaults": _normalize_tier_defaults(payload.get("tier_defaults")), + "rules": [deepcopy(item) for item in payload.get("rules", []) if isinstance(item, dict)], + } + return bundle + + +def _load_raw_payload(config_path: Path) -> dict[str, Any]: + if not config_path.exists(): + return {} + try: + parsed = json.loads(config_path.read_text(encoding="utf-8")) + return parsed if isinstance(parsed, dict) else {} + except Exception: + return {} + + +def load_model_config(*, refresh: bool = False, path: str | Path | None = None) -> dict[str, Any]: + config_path = resolve_model_config_path(path) + mtime = config_path.stat().st_mtime if config_path.exists() else -1.0 + cached = _CONFIG_CACHE + if ( + not refresh + and cached.get("bundle") is not None + and cached.get("path") == str(config_path) + and cached.get("mtime") == mtime + ): + return deepcopy(cached["bundle"]) + + raw = _load_raw_payload(config_path) + bundle = _normalize_bundle(raw, config_path=config_path) + _CONFIG_CACHE.update({"path": str(config_path), "mtime": mtime, "bundle": deepcopy(bundle)}) + return deepcopy(bundle) + + +def get_provider_defaults(provider: str | None = None, *, refresh: bool = False) -> dict[str, Any]: + bundle = load_model_config(refresh=refresh) + defaults = bundle.get("provider_defaults") if isinstance(bundle.get("provider_defaults"), dict) else {} + if provider is None: + return deepcopy(defaults) + provider_name = _normalize_provider(provider) + fallback = _DEFAULT_PROVIDER_DEFAULTS[provider_name] + return deepcopy(defaults.get(provider_name, fallback)) + + +def get_tier_defaults(tier: str | None = None, *, refresh: bool = False) -> dict[str, Any]: + bundle = load_model_config(refresh=refresh) + defaults = bundle.get("tier_defaults") if isinstance(bundle.get("tier_defaults"), dict) else {} + if tier is None: + return deepcopy(defaults) + tier_name = _normalize_tier(tier) + fallback = _DEFAULT_TIER_DEFAULTS[tier_name] + return deepcopy(defaults.get(tier_name, fallback)) + + +def build_provider_runtime( + provider: str, + *, + api_key: str = "", + model: str | None = None, + base_url: str | None = None, +) -> dict[str, Any]: + provider_name = _normalize_provider(provider) + defaults = get_provider_defaults(provider_name) + return { + "id": f"env-{provider_name}", + "name": str(defaults.get("name") or _DEFAULT_PROVIDER_DEFAULTS[provider_name]["name"]).strip(), + "provider": provider_name, + "api_key": str(api_key or "").strip(), + "base_url": str(base_url or defaults.get("base_url") or "").strip().rstrip("/"), + "model": str(model or defaults.get("model") or "").strip(), + "api_env": str(defaults.get("api_env") or _DEFAULT_PROVIDER_DEFAULTS[provider_name]["api_env"]).strip(), + } + + +def build_tier_runtime( + tier: str, + *, + api_key: str = "", +) -> dict[str, Any]: + tier_name = _normalize_tier(tier) + tier_defaults = get_tier_defaults(tier_name) + provider_name = _normalize_provider(str(tier_defaults.get("provider") or "glm")) + provider_defaults = get_provider_defaults(provider_name) + return { + "id": f"tier-{tier_name}", + "name": str(provider_defaults.get("name") or "").strip(), + "provider": provider_name, + "api_key": str(api_key or "").strip(), + "base_url": str(tier_defaults.get("base_url") or provider_defaults.get("base_url") or "").strip().rstrip("/"), + "model": str(tier_defaults.get("model") or provider_defaults.get("model") or "").strip(), + "api_env": str(tier_defaults.get("api_env") or provider_defaults.get("api_env") or "").strip(), + "tier": tier_name, + } + + +__all__ = [ + "build_provider_runtime", + "build_tier_runtime", + "get_provider_defaults", + "get_tier_defaults", + "load_model_config", + "resolve_model_config_path", +] diff --git a/backend/app/services/model/model_routing/service.py b/backend/app/services/model/model_routing/service.py index f92d55ee..c2fb2fde 100644 --- a/backend/app/services/model/model_routing/service.py +++ b/backend/app/services/model/model_routing/service.py @@ -9,6 +9,7 @@ from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession from ....models import Integration +from ..model_config import get_tier_defaults from ....utils.timezone import now_cn_naive ALLOWED_PROVIDERS = {"openai", "glm", "deepseek", "ollama"} @@ -123,13 +124,14 @@ class ModelRoutingService: @classmethod def _effective_from_rule(cls, *, function_name: str, rule: dict[str, Any] | None) -> dict[str, Any]: if rule is None: + low_defaults = get_tier_defaults("low") return { "function_name": cls._normalize_function_name(function_name), - "provider": "glm", - "model": "GLM-4.7", - "api_env": "GLM_API_KEY", + "provider": str(low_defaults.get("provider") or "glm").strip().lower(), + "model": str(low_defaults.get("model") or "qwen-plus").strip(), + "api_env": str(low_defaults.get("api_env") or "DASHSCOPE_API_KEY").strip(), "tier": "high", - "base_url": "", + "base_url": str(low_defaults.get("base_url") or "https://dashscope.aliyuncs.com/compatible-mode/v1").strip(), "source": "default", "updated_at": "", "updated_by": None, diff --git a/backend/app/services/project_knowledge_graph_service/mixin_guardrails.py b/backend/app/services/project_knowledge_graph_service/mixin_guardrails.py index 8484c98b..1ffca44b 100644 --- a/backend/app/services/project_knowledge_graph_service/mixin_guardrails.py +++ b/backend/app/services/project_knowledge_graph_service/mixin_guardrails.py @@ -31,59 +31,24 @@ class PKGGuardrailsMixin: @classmethod def _resolve_guardrail_target_models(cls) -> dict[str, dict[str, str]]: - specs: dict[str, dict[str, str]] = { - "low": { - "tier": "low", - "provider": str(os.getenv("LLM_ROUTING_LOW_PROVIDER", "glm") or "glm").strip(), - "model": str(os.getenv("LLM_ROUTING_LOW_MODEL", "GLM-4.7") or "GLM-4.7").strip(), - "api_env": str(os.getenv("LLM_ROUTING_LOW_API_ENV", "GLM_API_KEY") or "GLM_API_KEY").strip(), - "source": "env_default", - "source_path": "", + bundle = load_model_config() + provider_defaults = bundle.get("provider_defaults") if isinstance(bundle.get("provider_defaults"), dict) else {} + tier_defaults = bundle.get("tier_defaults") if isinstance(bundle.get("tier_defaults"), dict) else {} + config_path = str(bundle.get("config_path") or "") + specs: dict[str, dict[str, str]] = {} + for tier in ("low", "high"): + tier_spec = tier_defaults.get(tier) if isinstance(tier_defaults.get(tier), dict) else {} + provider = str(tier_spec.get("provider") or "glm").strip() + provider_spec = provider_defaults.get(provider) if isinstance(provider_defaults.get(provider), dict) else {} + specs[tier] = { + "tier": tier, + "provider": provider, + "model": str(tier_spec.get("model") or provider_spec.get("model") or "").strip(), + "api_env": str(tier_spec.get("api_env") or provider_spec.get("api_env") or "").strip(), + "source": "routing_file", + "source_path": config_path, "rule_name": "", - }, - "high": { - "tier": "high", - "provider": str(os.getenv("LLM_ROUTING_HIGH_PROVIDER", "openai") or "openai").strip(), - "model": str(os.getenv("LLM_ROUTING_HIGH_MODEL", "MiniMax-M2.5") or "MiniMax-M2.5").strip(), - "api_env": str(os.getenv("LLM_ROUTING_HIGH_API_ENV", "OPENAI_API_KEY") or "OPENAI_API_KEY").strip(), - "source": "env_default", - "source_path": "", - "rule_name": "", - }, - } - - for path in cls._routing_rule_candidate_paths(): - try: - content = path.read_text(encoding="utf-8") - payload = json.loads(content) - except Exception: - continue - rows = payload.get("rules") if isinstance(payload, dict) and isinstance(payload.get("rules"), list) else [] - if not rows: - continue - enabled_rows = [row for row in rows if isinstance(row, dict) and bool(row.get("enabled", True))] - enabled_rows.sort(key=lambda item: int(item.get("priority") or 0), reverse=True) - resolved_any = False - for tier in ("low", "high"): - for row in enabled_rows: - if str(row.get("tier") or "").strip().lower() != tier: - continue - model = str(row.get("model") or "").strip() - if not model: - continue - specs[tier] = { - "tier": tier, - "provider": str(row.get("provider") or specs[tier]["provider"]).strip(), - "model": model, - "api_env": str(row.get("api_env") or specs[tier]["api_env"]).strip(), - "source": "routing_rule", - "source_path": str(path), - "rule_name": str(row.get("name") or "").strip(), - } - resolved_any = True - break - if resolved_any and all(str(specs[tier].get("source") or "") == "routing_rule" for tier in ("low", "high")): - break + } return specs async def _sync_model_guardrail_entities(self) -> dict[str, Any]: @@ -165,7 +130,7 @@ class PKGGuardrailsMixin: for tier in ("low", "high"): spec = model_specs.get(tier) if isinstance(model_specs.get(tier), dict) else {} - model_name = str(spec.get("model") or "").strip() or ("GLM-4.7" if tier == "low" else "MiniMax-M2.5") + model_name = str(spec.get("model") or "").strip() or ("qwen-plus" if tier == "low" else "MiniMax-M2.5") provider = str(spec.get("provider") or "").strip() api_env = str(spec.get("api_env") or "").strip() source = str(spec.get("source") or "env_default").strip() @@ -280,4 +245,3 @@ class PKGGuardrailsMixin: ) await self.db.commit() return payload - diff --git a/backend/app/services/project_knowledge_graph_service/shared.py b/backend/app/services/project_knowledge_graph_service/shared.py index 2b72b460..79aa1734 100644 --- a/backend/app/services/project_knowledge_graph_service/shared.py +++ b/backend/app/services/project_knowledge_graph_service/shared.py @@ -36,6 +36,7 @@ from ...models import ( ) from ..context_cache_service import context_cache_service from ..signal.symbol_index import SymbolIndexService +from ..model.model_config import load_model_config from ...utils.timezone import now_cn_naive diff --git a/backend/app/services/signal/draft_generator_service.py b/backend/app/services/signal/draft_generator_service.py index 15f574ce..9a0aee29 100644 --- a/backend/app/services/signal/draft_generator_service.py +++ b/backend/app/services/signal/draft_generator_service.py @@ -4,6 +4,8 @@ from __future__ import annotations import asyncio import json +import re +from difflib import SequenceMatcher from datetime import timedelta from typing import Any, Optional @@ -43,12 +45,21 @@ class DraftGeneratorService: project = world_model.get("project") if isinstance(world_model.get("project"), dict) else {} project_title = str(project.get("title") or project.get("name") or "未指定项目").strip() or "未指定项目" project_description = str(project.get("description") or "").strip() or "暂无项目描述" + active_projects = self._normalize_active_projects(world_model.get("active_projects")) + similar_project_candidates = self._build_similar_project_candidates( + project_title=project_title, + project_description=project_description, + message_text=str(message_text or ""), + active_projects=active_projects, + ) members = await self._load_members(world_model=world_model) recent_signals = self._normalize_recent_signals(world_model.get("recent_signals")) prompt = PROMPT_DRAFT_GENERATOR.format( project_title=project_title, project_description=project_description, + active_projects_json=json.dumps(active_projects, ensure_ascii=False), + similar_project_candidates_json=json.dumps(similar_project_candidates, ensure_ascii=False), members_json=json.dumps(members, ensure_ascii=False), recent_signals=json.dumps(recent_signals, ensure_ascii=False), user_message=str(message_text or "")[:2000], @@ -60,6 +71,8 @@ class DraftGeneratorService: draft = self._normalize_draft_payload( payload=payload, members=members, + active_projects=active_projects, + similar_project_candidates=similar_project_candidates, original_text=message_text, version=next_version, ) @@ -205,6 +218,8 @@ class DraftGeneratorService: *, payload: dict[str, Any], members: list[dict[str, Any]], + active_projects: list[dict[str, Any]], + similar_project_candidates: list[dict[str, Any]], original_text: str, version: int, ) -> dict[str, Any]: @@ -231,6 +246,42 @@ class DraftGeneratorService: if not assignee_reason: assignee_reason = "基于当前负载与任务匹配度给出的建议。" + similar_projects = self._normalize_similar_projects( + payload.get("similar_projects"), + fallback_candidates=similar_project_candidates, + active_projects=active_projects, + original_text=original_text, + title=title, + ) + mvp_items = self._normalize_string_list( + payload.get("mvp_items"), + fallback=[ + "明确项目目标与边界", + "完成同名/相似项目查重", + "交付最小可验证闭环", + ], + max_items=5, + max_length=80, + ) + prospect_assessment = str(payload.get("prospect_assessment") or "").strip()[:240] + if not prospect_assessment: + prospect_assessment = "该项目具备落地价值,但建议先确认范围边界和现有项目复用情况,再决定是否推进。" + requirement_items = self._normalize_string_list( + payload.get("requirement_items"), + fallback=[ + "项目定位与目标", + "MVP 功能范围", + "需求边界与验收标准", + ], + max_items=8, + max_length=100, + ) + missing_info = self._normalize_string_list( + payload.get("missing_info"), + fallback=[], + max_items=5, + max_length=80, + ) followup_plan = self._normalize_followup_plan(payload.get("followup_plan")) if not followup_plan: followup_plan = [ @@ -245,6 +296,11 @@ class DraftGeneratorService: return { "title": title, "summary": summary, + "similar_projects": similar_projects, + "mvp_items": mvp_items, + "prospect_assessment": prospect_assessment, + "requirement_items": requirement_items, + "missing_info": missing_info, "feasibility": feasibility, "feasibility_reason": feasibility_reason, "recommended_assignee_id": assignee_id, @@ -260,6 +316,11 @@ class DraftGeneratorService: def render_draft_message(draft: dict[str, Any]) -> str: title = str(draft.get("title") or "未命名需求").strip() or "未命名需求" summary = str(draft.get("summary") or "暂无摘要").strip() or "暂无摘要" + similar_projects = draft.get("similar_projects") if isinstance(draft.get("similar_projects"), list) else [] + mvp_items = draft.get("mvp_items") if isinstance(draft.get("mvp_items"), list) else [] + prospect_assessment = str(draft.get("prospect_assessment") or "").strip() + requirement_items = draft.get("requirement_items") if isinstance(draft.get("requirement_items"), list) else [] + missing_info = draft.get("missing_info") if isinstance(draft.get("missing_info"), list) else [] feasibility = str(draft.get("feasibility") or "待讨论").strip() or "待讨论" feasibility_reason = str(draft.get("feasibility_reason") or "").strip() assignee_name = str(draft.get("recommended_assignee_name") or "待指定").strip() or "待指定" @@ -280,13 +341,55 @@ class DraftGeneratorService: workload_text = f"{assignee_workload:.1f}" if assignee_workload % 1 else f"{int(assignee_workload)}" feasibility_reason_line = feasibility_reason if feasibility_reason else "需补充约束后再确认。" assignee_reason_line = assignee_reason if assignee_reason else "建议结合成员技能与当前负载综合判断。" + similar_lines = DraftGeneratorService._render_bullet_items( + similar_projects, + default_line="· 未发现同名或高相似项目,可作为新立项继续评审。", + formatter=DraftGeneratorService._render_similar_project_line, + max_items=3, + ) + mvp_lines = DraftGeneratorService._render_bullet_items( + mvp_items, + default_line="· 待定:先明确目标后补齐 MVP 范围", + formatter=DraftGeneratorService._render_text_item_line, + max_items=5, + ) + requirement_lines = DraftGeneratorService._render_bullet_items( + requirement_items, + default_line="· 待定:补充需求清单", + formatter=DraftGeneratorService._render_text_item_line, + max_items=8, + ) + missing_info_lines = DraftGeneratorService._render_bullet_items( + missing_info, + default_line="· 暂无", + formatter=DraftGeneratorService._render_text_item_line, + max_items=5, + ) + prospect_lines = DraftGeneratorService._split_paragraph_lines( + prospect_assessment or "该项目前景尚可,但建议先确认范围边界。" + ) return "\n".join( [ - "📋 已为你起草需求,请确认:", + "📋 我先按产品经理视角起草一版评审建议,请确认:", "", f"📌 {title}", summary, "", + "🔎 同名 / 相似项目:", + *similar_lines, + "", + "🚀 MVP 建议:", + *mvp_lines, + "", + "📈 前景判断:", + *prospect_lines, + "", + "🧩 需求清单:", + *requirement_lines, + "", + "❓ 还缺的信息:", + *missing_info_lines, + "", f"✅ 可行性:{feasibility}", feasibility_reason_line, "", @@ -335,6 +438,252 @@ class DraftGeneratorService: normalized.append({"when": when, "action": action}) return normalized + @staticmethod + def _normalize_active_projects(value: Any) -> list[dict[str, Any]]: + if not isinstance(value, list): + return [] + normalized: list[dict[str, Any]] = [] + for row in value[:20]: + if not isinstance(row, dict): + continue + project_id = DraftGeneratorService._safe_int(row.get("id")) + title = str(row.get("title") or row.get("name") or "").strip()[:120] + if project_id is None or not title: + continue + normalized.append( + { + "project_id": project_id, + "title": title, + "name": str(row.get("name") or title).strip()[:120], + "description": str(row.get("description") or "").strip()[:300], + "status": str(row.get("status") or "").strip()[:20], + } + ) + return normalized + + def _build_similar_project_candidates( + self, + *, + project_title: str, + project_description: str, + message_text: str, + active_projects: list[dict[str, Any]], + ) -> list[dict[str, Any]]: + query = " ".join( + part + for part in [str(project_title or "").strip(), str(project_description or "").strip(), str(message_text or "").strip()] + if part + ) + if not query: + return [] + candidates: list[dict[str, Any]] = [] + for row in active_projects: + project_id = self._safe_int(row.get("project_id")) + title = str(row.get("title") or row.get("name") or "").strip() + if project_id is None or not title: + continue + description = str(row.get("description") or "").strip() + candidate_text = f"{title} {description}".strip() + score = self._score_similarity(query, candidate_text) + if score < 0.22: + continue + reuse_suggestion = "建议先评估是否复用现有项目,避免重复建设。" + if score >= 0.55: + reuse_suggestion = "名称或目标高度接近,优先确认是否复用或合并。" + difference = "与当前需求主题接近,建议重点确认范围边界。" + if description and len(description) > 20: + difference = "现有项目已有一定描述,建议对比当前需求的目标、范围和交付边界。" + risk = "若范围重叠,可能造成重复建设或维护分散。" + candidates.append( + { + "project_id": project_id, + "title": title, + "difference": difference, + "risk": risk, + "reuse_suggestion": reuse_suggestion, + "match_score": round(min(score, 1.0), 3), + } + ) + candidates.sort(key=lambda item: (-float(item.get("match_score") or 0.0), int(item.get("project_id") or 0))) + return candidates[:3] + + @staticmethod + def _score_similarity(left: str, right: str) -> float: + left_norm = re.sub(r"\s+", "", str(left or "").lower()) + right_norm = re.sub(r"\s+", "", str(right or "").lower()) + if not left_norm or not right_norm: + return 0.0 + base = SequenceMatcher(None, left_norm, right_norm).ratio() + if left_norm in right_norm or right_norm in left_norm: + base = max(base, 0.75) + shared = set(re.findall(r"[\u4e00-\u9fff]{2,}", left_norm)) & set(re.findall(r"[\u4e00-\u9fff]{2,}", right_norm)) + if shared: + base = min(1.0, base + min(len(shared) * 0.08, 0.2)) + return base + + @staticmethod + def _normalize_similar_projects( + value: Any, + *, + fallback_candidates: list[dict[str, Any]], + active_projects: list[dict[str, Any]], + original_text: str, + title: str, + ) -> list[dict[str, Any]]: + normalized: list[dict[str, Any]] = [] + if isinstance(value, list): + for row in value[:5]: + if isinstance(row, dict): + project_id = DraftGeneratorService._safe_int(row.get("project_id") or row.get("id")) + project_title = str(row.get("title") or row.get("name") or "").strip()[:120] + if project_id is None or not project_title: + continue + normalized.append( + { + "project_id": project_id, + "title": project_title, + "difference": str(row.get("difference") or "").strip()[:160] or "需确认范围差异。", + "risk": str(row.get("risk") or "").strip()[:160] or "可能存在重复建设风险。", + "reuse_suggestion": str(row.get("reuse_suggestion") or row.get("suggestion") or "").strip()[:160] + or "建议先评估是否复用或合并。", + } + ) + elif isinstance(row, str) and row.strip(): + normalized.append( + { + "project_id": None, + "title": row.strip()[:120], + "difference": "模型未提供详细差异,建议人工确认。", + "risk": "存在重复建设的可能。", + "reuse_suggestion": "建议先核对项目是否同名或高度相似。", + } + ) + if normalized: + return normalized[:3] + + if fallback_candidates: + return [ + { + "project_id": int(row.get("project_id")), + "title": str(row.get("title") or "").strip()[:120], + "difference": str(row.get("difference") or "").strip()[:160] or "需确认范围差异。", + "risk": str(row.get("risk") or "").strip()[:160] or "可能存在重复建设风险。", + "reuse_suggestion": str(row.get("reuse_suggestion") or "").strip()[:160] or "建议先评估是否复用或合并。", + } + for row in fallback_candidates[:3] + if DraftGeneratorService._safe_int(row.get("project_id")) is not None + ] + + query = " ".join(part for part in [title, original_text] if str(part or "").strip()) + for row in active_projects[:3]: + project_title = str(row.get("title") or row.get("name") or "").strip() + if not project_title: + continue + if query and (project_title in query or query in project_title): + normalized.append( + { + "project_id": int(row.get("project_id") or 0), + "title": project_title, + "difference": "名称高度接近,建议确认是否为同一项目范围。", + "risk": "若直接新建,容易造成重复建设。", + "reuse_suggestion": "建议复用现有项目或先合并范围。", + } + ) + return normalized[:3] + + @staticmethod + def _normalize_string_list( + value: Any, + *, + fallback: list[str], + max_items: int, + max_length: int, + ) -> list[str]: + items: list[str] = [] + if isinstance(value, list): + for row in value: + if isinstance(row, str): + text = " ".join(row.split()).strip()[:max_length] + elif isinstance(row, dict): + text = " ".join( + str(row.get(key) or "").strip() + for key in ("text", "title", "content", "summary", "name") + if str(row.get(key) or "").strip() + ).strip()[:max_length] + if not text: + text = " ".join(str(row.get("value") or "").split()).strip()[:max_length] + else: + text = "" + if text: + items.append(text) + if len(items) >= max_items: + break + if items: + return items[:max_items] + return [str(item).strip()[:max_length] for item in fallback if str(item).strip()][:max_items] + + @staticmethod + def _render_bullet_items( + items: list[Any], + *, + default_line: str, + formatter: Any, + max_items: int, + ) -> list[str]: + lines: list[str] = [] + if isinstance(items, list): + for item in items[:max_items]: + line = formatter(item) + if line: + lines.append(line) + if not lines: + lines = [default_line] + return lines + + @staticmethod + def _render_text_item_line(item: Any) -> str: + if isinstance(item, str): + text = " ".join(item.split()).strip() + elif isinstance(item, dict): + text = " ".join( + str(item.get(key) or "").strip() + for key in ("text", "title", "content", "summary", "name") + if str(item.get(key) or "").strip() + ).strip() + if not text: + text = " ".join(str(item.get("value") or "").split()).strip() + else: + text = "" + if not text: + return "" + return f"· {text[:120]}" + + @staticmethod + def _render_similar_project_line(item: Any) -> str: + if not isinstance(item, dict): + return DraftGeneratorService._render_text_item_line(item) + project_id = DraftGeneratorService._safe_int(item.get("project_id")) + title = str(item.get("title") or "").strip()[:60] + difference = str(item.get("difference") or "").strip()[:120] + risk = str(item.get("risk") or "").strip()[:80] + reuse = str(item.get("reuse_suggestion") or "").strip()[:100] + parts = [] + if project_id is not None: + parts.append(f"#{project_id}") + if title: + parts.append(title) + prefix = " ".join(parts) or "相似项目" + detail_parts = [part for part in [difference, risk, reuse] if part] + detail = ";".join(detail_parts) if detail_parts else "建议先确认范围边界。" + return f"· {prefix}:{detail}" + + @staticmethod + def _split_paragraph_lines(text: str) -> list[str]: + normalized = " ".join(str(text or "").split()).strip() + if not normalized: + return ["· 暂无"] + return [f"· {chunk.strip()}" for chunk in re.split(r"[。;;]\s*", normalized) if chunk.strip()][:3] + @staticmethod def _safe_int(value: Any) -> Optional[int]: try: diff --git a/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py index 7f8ce81f..2f3cf05c 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py +++ b/backend/app/services/signal/signal_capture_service/mixin_capture_entry.py @@ -21,6 +21,7 @@ class SignalCaptureEntryMixin: force_sync: bool = False, message_sent_at: Optional[Any] = None, session_snapshot: Optional[Dict[str, Any]] = None, + thread_context: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """ 从飞书群聊捕获信号 @@ -68,6 +69,10 @@ class SignalCaptureEntryMixin: enqueue_session = await kernel._get_or_create_session( tenant_id=int(tenant_id), sender=proposer_name, + source_chat_id=group_chat_id, + source_sender_open_id=proposer_open_id, + source_message_id=source_message_id, + thread_context=thread_context, ) should_sync_process = kernel._is_short_confirmation_message( message_text=cleaned_message, @@ -106,6 +111,7 @@ class SignalCaptureEntryMixin: chat_type=chat_type, message_sent_at=effective_message_sent_at, session_snapshot=effective_session_snapshot, + thread_context=thread_context, ) if self._is_non_requirement_message(cleaned_message) and not self._is_question_like(cleaned_message): @@ -120,6 +126,10 @@ class SignalCaptureEntryMixin: enqueue_session = await kernel._get_or_create_session( tenant_id=int(tenant_id), sender=proposer_name, + source_chat_id=group_chat_id, + source_sender_open_id=proposer_open_id, + source_message_id=source_message_id, + thread_context=thread_context, ) enqueue_message_sent_at = self._resolve_enqueue_message_sent_at( captured_dt=captured_dt, @@ -145,13 +155,16 @@ class SignalCaptureEntryMixin: chat_type=chat_type, captured_at=captured_dt, project_hint_id=None, + thread_context=thread_context, max_retries=3, ) if created: base_payload = run_row.result_payload if isinstance(run_row.result_payload, dict) else {} + task_payload = base_payload.get("task_payload") if isinstance(base_payload.get("task_payload"), dict) else {} run_row.result_payload = { **base_payload, "task_payload": { + **task_payload, "message_sent_at": enqueue_message_sent_at.isoformat(), "session_snapshot": enqueue_session_snapshot, }, @@ -184,6 +197,8 @@ class SignalCaptureEntryMixin: group_chat_id=group_chat_id, sender=proposer_name, sender_open_id=proposer_open_id, + source_message_id=source_message_id, + thread_context=thread_context, message=cleaned_message, pipeline_result=pipeline_result, ) @@ -238,6 +253,7 @@ class SignalCaptureEntryMixin: chat_type: Optional[str], message_sent_at: Optional[Any] = None, session_snapshot: Optional[Dict[str, Any]] = None, + thread_context: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: pipeline_result = await kernel.process_feishu_message( group_chat_id=group_chat_id, @@ -249,6 +265,7 @@ class SignalCaptureEntryMixin: chat_type=chat_type, message_sent_at=message_sent_at, session_snapshot=session_snapshot, + thread_context=thread_context, ) if not bool(pipeline_result.get("handled")): self._emit_ai_think( @@ -280,6 +297,8 @@ class SignalCaptureEntryMixin: group_chat_id=group_chat_id, sender=proposer_name, sender_open_id=proposer_open_id, + source_message_id=feishu_message_id, + thread_context=thread_context, message=cleaned_message, pipeline_result=pipeline_result, ) diff --git a/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py b/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py index 1570cbff..7a4b4cef 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py +++ b/backend/app/services/signal/signal_capture_service/mixin_pipeline_cache.py @@ -10,6 +10,8 @@ class SignalCapturePipelineCacheMixin: group_chat_id: str, sender: str, sender_open_id: Optional[str], + source_message_id: Optional[str], + thread_context: Optional[Dict[str, Any]], message: str, pipeline_result: Dict[str, Any], ) -> None: @@ -20,6 +22,8 @@ class SignalCapturePipelineCacheMixin: source_chat_id=group_chat_id, source_sender_open_id=sender_open_id, sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, ) external_session_id = AdaptiveRAGService._external_session_id_from_key(conversation_key) if external_session_id is None: @@ -257,4 +261,3 @@ class SignalCapturePipelineCacheMixin: } ) return items - diff --git a/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py index 1bc00669..acfc8b3f 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py +++ b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_main.py @@ -3,6 +3,191 @@ from __future__ import annotations from .shared import * class SignalCaptureTaskOrchestrationMainMixin: + @staticmethod + def _compact_orchestration_text(value: Any, *, max_len: int = 120) -> str: + text = str(value or "").strip() + if not text: + return "" + text = re.sub(r"\s+", " ", text) + return text[:max_len] + + @classmethod + def _compact_orchestration_items( + cls, + value: Any, + *, + max_items: int = 6, + max_len: int = 120, + ) -> List[str]: + if isinstance(value, list): + raw_items = value + elif isinstance(value, str): + raw_items = re.split(r"[\n\r,,;;、/|]+", value) + else: + raw_items = [] + + items: List[str] = [] + seen: set[str] = set() + for raw in raw_items: + text = cls._compact_orchestration_text(raw, max_len=max_len) + if not text: + continue + key = text.lower() + if key in seen: + continue + seen.add(key) + items.append(text) + if len(items) >= max_items: + break + return items + + @classmethod + def _build_task_orchestration_charter_context( + cls, + charter: Optional[TruthNode], + ) -> Dict[str, Any]: + if charter is None: + return {} + + content = charter.content + if isinstance(content, str): + try: + content = json.loads(content) + except Exception: + content = {} + if not isinstance(content, dict): + content = {} + + modules = content.get("modules") + if not isinstance(modules, dict): + modules = {} + + module_1 = modules.get("module_1") if isinstance(modules.get("module_1"), dict) else {} + module_2 = modules.get("module_2") if isinstance(modules.get("module_2"), dict) else {} + module_4 = modules.get("module_4") if isinstance(modules.get("module_4"), dict) else {} + module_5 = modules.get("module_5") if isinstance(modules.get("module_5"), dict) else {} + module_6 = modules.get("module_6") if isinstance(modules.get("module_6"), dict) else {} + + summary = cls._compact_orchestration_text( + content.get("summary") + or content.get("rationale") + or content.get("background") + or module_1.get("problem_statement") + or "", + max_len=180, + ) + objectives = cls._compact_orchestration_items( + content.get("objectives") + or module_1.get("goal_statement") + or [], + max_items=5, + max_len=100, + ) + in_scope = cls._compact_orchestration_items( + content.get("in_scope") + or module_2.get("in_scope") + or [], + max_items=5, + max_len=100, + ) + out_of_scope = cls._compact_orchestration_items( + content.get("out_of_scope") + or module_2.get("out_of_scope") + or [], + max_items=5, + max_len=100, + ) + success_criteria = cls._compact_orchestration_items( + content.get("success_criteria") + or content.get("success_metrics") + or content.get("core_metrics") + or module_1.get("kpis") + or [], + max_items=6, + max_len=100, + ) + constraints = cls._compact_orchestration_items( + content.get("constraints") + or module_5.get("execution_rule") + or module_6.get("governance_rule") + or module_2.get("alignment_qa") + or [], + max_items=6, + max_len=100, + ) + milestones = cls._compact_orchestration_items( + content.get("milestones") + or module_4.get("milestones") + or [], + max_items=5, + max_len=100, + ) + risks = cls._compact_orchestration_items( + content.get("risks") + or module_5.get("risk_register") + or [], + max_items=5, + max_len=100, + ) + scope = cls._compact_orchestration_text( + content.get("scope") + or module_2.get("alignment_qa") + or "", + max_len=240, + ) + + return { + "id": int(charter.id), + "title": cls._compact_orchestration_text(charter.title, max_len=120) or "未命名宪章", + "version": int(charter.version or 1), + "status": cls._compact_orchestration_text(charter.status, max_len=32) or "unknown", + "summary": summary, + "objectives": objectives, + "scope": scope, + "in_scope": in_scope, + "out_of_scope": out_of_scope, + "success_criteria": success_criteria, + "constraints": constraints, + "milestones": milestones, + "risks": risks, + } + + async def _load_task_orchestration_charter_context( + self, + *, + tenant_id: int, + project_id: int, + charter_id: int, + ) -> Dict[str, Any]: + charter = ( + await self.db.execute( + select(TruthNode).where( + and_( + TruthNode.tenant_id == tenant_id, + TruthNode.project_id == project_id, + TruthNode.id == charter_id, + TruthNode.node_type == "charter", + ) + ) + ) + ).scalar_one_or_none() + return self._build_task_orchestration_charter_context(charter) + + @staticmethod + def _build_task_orchestration_prompt( + *, + project_payload: Dict[str, Any], + charter_payload: Dict[str, Any], + signal_payload: Dict[str, Any], + team_members: List[Dict[str, Any]], + ) -> str: + return PROMPT_FEISHU_TASK_ORCHESTRATION.format( + project_json=json.dumps(project_payload, ensure_ascii=False), + charter_json=json.dumps(charter_payload, ensure_ascii=False), + signal_json=json.dumps(signal_payload, ensure_ascii=False), + members_json=json.dumps(team_members, ensure_ascii=False), + ) + async def _auto_orchestrate_project_tasks( self, *, @@ -58,11 +243,17 @@ class SignalCaptureTaskOrchestrationMainMixin: "tasks_created": 0, } + charter_context = await self._load_task_orchestration_charter_context( + tenant_id=tenant_id, + project_id=project_id, + charter_id=int(charter_id), + ) project_payload = { "id": int(project.id), "title": (project.title or project.name or "").strip(), "description": (project.description or "").strip()[:600], "assigned_role": (project.assigned_role or "").strip(), + "status": (project.status or "").strip(), } signal_payload = { "signal_id": signal_id, @@ -71,10 +262,11 @@ class SignalCaptureTaskOrchestrationMainMixin: "proposer": proposer_name, "captured_at": captured_at.isoformat(), } - prompt = PROMPT_FEISHU_TASK_ORCHESTRATION.format( - project_json=json.dumps(project_payload, ensure_ascii=False), - signal_json=json.dumps(signal_payload, ensure_ascii=False), - members_json=json.dumps(team_members, ensure_ascii=False), + prompt = self._build_task_orchestration_prompt( + project_payload=project_payload, + charter_payload=charter_context, + signal_payload=signal_payload, + team_members=team_members, ) llm_status = "success" @@ -109,6 +301,7 @@ class SignalCaptureTaskOrchestrationMainMixin: tasks_payload = self._build_task_orchestration_fallback( cleaned_message=cleaned_message, classification=classification, + charter_context=charter_context, ) created_items: List[Dict[str, Any]] = [] @@ -226,4 +419,3 @@ class SignalCaptureTaskOrchestrationMainMixin: "llm_error": llm_error[:300] if llm_error else "", } return created_items, execution_meta - diff --git a/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py index 9a87e1a9..658a3738 100644 --- a/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py +++ b/backend/app/services/signal/signal_capture_service/mixin_task_orchestration_skill.py @@ -107,15 +107,29 @@ class SignalCaptureTaskOrchestrationSkillMixin: *, cleaned_message: str, classification: Dict[str, Any], + charter_context: Optional[Dict[str, Any]] = None, ) -> List[Dict[str, Any]]: summary = self._normalize_single_line_text( str(classification.get("summary") or cleaned_message), max_len=120, ) + charter_title = "" + charter_summary = "" + if isinstance(charter_context, dict): + charter_title = self._normalize_single_line_text( + str(charter_context.get("title") or ""), + max_len=80, + ) + charter_summary = self._normalize_single_line_text( + str(charter_context.get("summary") or charter_context.get("scope") or ""), + max_len=120, + ) + charter_anchor = charter_title or charter_summary or "当前项目宪章" + charter_prefix = f"基于宪章「{charter_anchor}」" return [ { "title": "需求澄清与范围确认", - "description": f"基于飞书信号补全需求边界、验收口径与优先级。上下文:{summary}", + "description": f"{charter_prefix},补全需求边界、验收口径与优先级。上下文:{summary}", "priority": "high", "estimated_hours": 4, "required_skill_names": ["产品管理", "项目管理"], @@ -125,7 +139,7 @@ class SignalCaptureTaskOrchestrationSkillMixin: }, { "title": "核心方案落地", - "description": f"围绕需求信号推进可交付实现,拆分后端/前端/AI 子任务并排期。上下文:{summary}", + "description": f"{charter_prefix},围绕关键约束拆分后端/前端/AI 子任务并排期。上下文:{summary}", "priority": "medium", "estimated_hours": 12, "required_skill_names": ["FastAPI", "React", "LangChain"], @@ -172,4 +186,3 @@ class SignalCaptureTaskOrchestrationSkillMixin: return int(value) except Exception: return None - diff --git a/backend/app/services/signal/signal_capture_service/shared.py b/backend/app/services/signal/signal_capture_service/shared.py index e0247dc0..1fc10d97 100644 --- a/backend/app/services/signal/signal_capture_service/shared.py +++ b/backend/app/services/signal/signal_capture_service/shared.py @@ -47,6 +47,7 @@ from ..signal_source_orchestration import SignalSourceOrchestrationService from ...ai_pmo.ai_pmo_kernel import AIPMOKernelService from ....utils.project_members import ensure_project_default_members from ....utils.sequence import ensure_project_id_sequence, ensure_signal_id_sequence +from app.utils.thread_context import build_feishu_thread_key from ....utils.timezone import CN_TZ, now_cn_aware @@ -160,16 +161,16 @@ def _build_feishu_rag_conversation_key( source_chat_id: Optional[str], source_sender_open_id: Optional[str], sender: Optional[str], + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, ) -> str: - chat = str(source_chat_id or "").strip() - if not chat: - return "" - actor = str(source_sender_open_id or "").strip() - if not actor: - actor = str(sender or "").strip().lower() - actor = re.sub(r"\s+", "", actor) - actor = actor[:96] if actor else "unknown" - return f"feishu:{chat}:{actor}" + return build_feishu_thread_key( + source_chat_id=source_chat_id, + source_sender_open_id=source_sender_open_id, + sender=sender, + source_message_id=source_message_id, + thread_context=thread_context, + ) __all__ = [name for name in globals() if not name.startswith("__")] diff --git a/backend/app/services/signal/signal_planner/mixin_analyze.py b/backend/app/services/signal/signal_planner/mixin_analyze.py index 65d91826..38485c1e 100644 --- a/backend/app/services/signal/signal_planner/mixin_analyze.py +++ b/backend/app/services/signal/signal_planner/mixin_analyze.py @@ -49,6 +49,11 @@ class SignalPlannerAnalyzeMixin: tenant_id=int(tenant_id), project_id=project_id, ) + charter_json = await cls._load_latest_charter_payload( + db, + tenant_id=int(tenant_id), + project_id=project_id, + ) recent_signals = await cls._load_recent_signal_summaries( db, tenant_id=int(tenant_id), @@ -63,6 +68,7 @@ class SignalPlannerAnalyzeMixin: prompt = cls._build_prompt( signal=signal, project_title=project_title, + charter_json=charter_json, recent_signals=recent_signals, members=members, ) diff --git a/backend/app/services/signal/signal_planner/mixin_loaders.py b/backend/app/services/signal/signal_planner/mixin_loaders.py index 43a1f37f..2acae7ca 100644 --- a/backend/app/services/signal/signal_planner/mixin_loaders.py +++ b/backend/app/services/signal/signal_planner/mixin_loaders.py @@ -9,6 +9,7 @@ from .shared import ( Project, Signal, SkillTag, + TruthNode, User, UserProject, UserSkillBinding, @@ -16,6 +17,7 @@ from .shared import ( and_, select, ) +from ...sstc.charter_payload import _build_charter_context_payload class SignalPlannerLoadersMixin: @@ -62,10 +64,38 @@ class SignalPlannerLoadersMixin: ) ) .limit(1) - ) + ) ).scalar_one_or_none() return str(row or "") + @classmethod + async def _load_latest_charter_payload( + cls, + db: AsyncSession, + *, + tenant_id: int, + project_id: Optional[int], + ) -> dict[str, Any]: + if project_id is None: + return {} + charter = ( + await db.execute( + select(TruthNode) + .where( + and_( + TruthNode.tenant_id == int(tenant_id), + TruthNode.project_id == int(project_id), + TruthNode.node_type == "charter", + ) + ) + .order_by(TruthNode.updated_at.desc(), TruthNode.id.desc()) + .limit(1) + ) + ).scalar_one_or_none() + if charter is None: + return {} + return _build_charter_context_payload(charter) + @classmethod async def _load_recent_signal_summaries( cls, diff --git a/backend/app/services/signal/signal_planner/mixin_prompt.py b/backend/app/services/signal/signal_planner/mixin_prompt.py index bcccb24d..542dd9d0 100644 --- a/backend/app/services/signal/signal_planner/mixin_prompt.py +++ b/backend/app/services/signal/signal_planner/mixin_prompt.py @@ -14,6 +14,7 @@ class SignalPlannerPromptMixin: *, signal: Signal, project_title: str, + charter_json: dict[str, Any], recent_signals: list[dict[str, Any]], members: list[dict[str, Any]], ) -> str: @@ -35,6 +36,7 @@ class SignalPlannerPromptMixin: ), project_title=str(project_title or ""), project_scope=str(ai_analysis.get("requirement_project_scope") or ""), + charter_json=json.dumps(charter_json or {}, ensure_ascii=False), recent_signals=json.dumps(recent_signals, ensure_ascii=False), member_profiles=json.dumps(members, ensure_ascii=False), now_cn=now_cn_naive().isoformat(), diff --git a/backend/app/services/signal/signal_planner/shared.py b/backend/app/services/signal/signal_planner/shared.py index 627f139b..64241a63 100644 --- a/backend/app/services/signal/signal_planner/shared.py +++ b/backend/app/services/signal/signal_planner/shared.py @@ -17,6 +17,7 @@ from ....models import ( ProjectSignalLink, Signal, SkillTag, + TruthNode, User, UserProject, UserSkillBinding, @@ -45,6 +46,7 @@ __all__ = [ "Signal", "SignalStrategicAnalysisService", "SkillTag", + "TruthNode", "User", "UserProject", "UserSkillBinding", diff --git a/backend/app/services/sstc/charter_data.py b/backend/app/services/sstc/charter_data.py index 12579e3c..e414343a 100644 --- a/backend/app/services/sstc/charter_data.py +++ b/backend/app/services/sstc/charter_data.py @@ -2,9 +2,21 @@ from __future__ import annotations from .shared import * +from ...config.ai_prompts import PROMPT_DECOMPOSE_TASKS from .signal_core import _as_dict, _extract_signal_attachments, _normalize_planner_status, _signal_display_type, _signal_project_scope, _signal_source_context from .signal_filters import _signal_proposer_name, _signal_title_text +_DEFAULT_TASK_BREAKDOWN_SKILLS = ( + "Python", + "TypeScript", + "FastAPI", + "React", + "SQLAlchemy", + "LangChain", + "Docker", + "LLM调优", +) + async def _fetch_valid_project_signal_ids( db: AsyncSession, *, @@ -53,6 +65,102 @@ def _build_charter_summary_payload(charter: TruthNode) -> dict[str, Any]: "version": charter.version, } + +def _build_charter_context_payload(charter: TruthNode) -> dict[str, Any]: + content = _as_dict(charter.content) + modules = content.get("modules") + if not isinstance(modules, dict): + modules = {} + module_1 = modules.get("module_1") if isinstance(modules.get("module_1"), dict) else {} + module_2 = modules.get("module_2") if isinstance(modules.get("module_2"), dict) else {} + module_4 = modules.get("module_4") if isinstance(modules.get("module_4"), dict) else {} + module_5 = modules.get("module_5") if isinstance(modules.get("module_5"), dict) else {} + module_6 = modules.get("module_6") if isinstance(modules.get("module_6"), dict) else {} + + signal_ids = content.get("signal_ids") + normalized_signal_ids: list[int] = [] + if isinstance(signal_ids, list): + for sid in signal_ids: + try: + normalized_signal_ids.append(int(sid)) + except Exception: + continue + + return { + "id": int(charter.id), + "title": _compact_task_breakdown_text(charter.title or "", max_len=120) or "未命名宪章", + "version": int(charter.version or 1), + "status": _compact_task_breakdown_text(charter.status or "", max_len=32) or "unknown", + "summary": _compact_task_breakdown_text( + content.get("summary") + or content.get("rationale") + or content.get("background") + or module_1.get("problem_statement") + or "", + max_len=180, + ), + "objectives": _compact_task_breakdown_items( + content.get("objectives") + or module_1.get("goal_statement") + or [], + max_items=5, + max_len=100, + ), + "scope": _compact_task_breakdown_text( + content.get("scope") + or module_2.get("alignment_qa") + or "", + max_len=240, + ), + "in_scope": _compact_task_breakdown_items( + content.get("in_scope") + or module_2.get("in_scope") + or [], + max_items=5, + max_len=100, + ), + "out_of_scope": _compact_task_breakdown_items( + content.get("out_of_scope") + or module_2.get("out_of_scope") + or [], + max_items=5, + max_len=100, + ), + "success_criteria": _compact_task_breakdown_items( + content.get("success_criteria") + or content.get("success_metrics") + or content.get("core_metrics") + or module_1.get("kpis") + or [], + max_items=6, + max_len=100, + ), + "constraints": _compact_task_breakdown_items( + content.get("constraints") + or module_5.get("execution_rule") + or module_6.get("governance_rule") + or module_2.get("alignment_qa") + or [], + max_items=6, + max_len=100, + ), + "milestones": _compact_task_breakdown_items( + content.get("milestones") + or module_4.get("milestones") + or [], + max_items=5, + max_len=100, + ), + "risks": _compact_task_breakdown_items( + content.get("risks") + or module_5.get("risk_register") + or [], + max_items=5, + max_len=100, + ), + "signal_ids": normalized_signal_ids, + } + def _serialize_charter_signal(signal: Signal) -> dict[str, Any]: return { "id": str(signal.id), @@ -135,3 +243,163 @@ def _serialize_alignment_record( "comments": record.comments, "confirmed_at": confirmed_at, } + + +def _compact_task_breakdown_text(value: object, *, max_len: int = 120) -> str: + text = str(value or "").strip() + if not text: + return "" + text = re.sub(r"\s+", " ", text) + return text[:max_len] + + +def _compact_task_breakdown_items( + value: object, + *, + max_items: int = 6, + max_len: int = 120, +) -> list[str]: + if isinstance(value, list): + raw_items = value + elif isinstance(value, str): + raw_items = re.split(r"[\n\r,,;;、/|]+", value) + else: + raw_items = [] + + items: list[str] = [] + seen: set[str] = set() + for raw in raw_items: + text = _compact_task_breakdown_text(raw, max_len=max_len) + if not text: + continue + key = text.lower() + if key in seen: + continue + seen.add(key) + items.append(text) + if len(items) >= max_items: + break + return items + + +def _build_task_breakdown_context( + *, + description: str, + project: Optional[Project] = None, + charter: Optional[TruthNode] = None, +) -> dict[str, Any]: + project_payload: dict[str, Any] = {} + if project is not None: + project_payload = { + "id": int(project.id), + "title": _compact_task_breakdown_text(project.title or project.name or "", max_len=120) or "未命名项目", + "description": _compact_task_breakdown_text(project.description or "", max_len=240), + "status": _compact_task_breakdown_text(project.status or "", max_len=32), + "assigned_role": _compact_task_breakdown_text(project.assigned_role or "", max_len=32), + } + + charter_payload: dict[str, Any] = {} + if charter is not None: + charter_payload = _build_charter_context_payload(charter) + + return { + "request": { + "description": _compact_task_breakdown_text(description, max_len=1200), + }, + "project": project_payload, + "charter": charter_payload, + } + + +async def _load_available_skill_names( + db: AsyncSession, + *, + tenant_id: int, +) -> str: + rows = ( + await db.execute( + select(SkillTag.name) + .where(SkillTag.tenant_id == tenant_id) + .order_by(SkillTag.sort_order.asc(), SkillTag.name.asc()) + ) + ).scalars().all() + names: list[str] = [] + seen: set[str] = set() + for raw in rows: + name = str(raw or "").strip() + if not name: + continue + key = name.lower() + if key in seen: + continue + seen.add(key) + names.append(name) + if not names: + names = list(_DEFAULT_TASK_BREAKDOWN_SKILLS) + return ", ".join(names) + + +async def _load_task_breakdown_context( + db: AsyncSession, + *, + tenant_id: int, + description: str, + project_id: Optional[int] = None, + charter_id: Optional[int] = None, +) -> dict[str, Any]: + project = None + if project_id is not None: + project = ( + await db.execute( + select(Project).where( + and_( + Project.tenant_id == int(tenant_id), + Project.id == int(project_id), + ) + ) + ) + ).scalar_one_or_none() + + charter = None + if charter_id is not None: + charter_query = select(TruthNode).where( + and_( + TruthNode.tenant_id == int(tenant_id), + TruthNode.id == int(charter_id), + TruthNode.node_type == "charter", + ) + ) + if project_id is not None: + charter_query = charter_query.where(TruthNode.project_id == int(project_id)) + charter = (await db.execute(charter_query)).scalar_one_or_none() + elif project_id is not None: + charter = ( + await db.execute( + select(TruthNode).where( + and_( + TruthNode.tenant_id == int(tenant_id), + TruthNode.project_id == int(project_id), + TruthNode.node_type == "charter", + ) + ).order_by(TruthNode.updated_at.desc(), TruthNode.id.desc()) + ) + ).scalars().first() + + return _build_task_breakdown_context( + description=description, + project=project, + charter=charter, + ) + + +def _build_task_breakdown_prompt( + *, + project_id: Optional[int], + context: dict[str, Any], + available_skills: str, +) -> str: + return PROMPT_DECOMPOSE_TASKS.format( + project_id=str(project_id or "未指定"), + charter_content=json.dumps(context, ensure_ascii=False), + available_skills=available_skills, + ) diff --git a/backend/app/services/sstc/charter_payload.py b/backend/app/services/sstc/charter_payload.py new file mode 100644 index 00000000..6eb88899 --- /dev/null +++ b/backend/app/services/sstc/charter_payload.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +import json +import re +from typing import Any + +from ...models import TruthNode + + +def _as_dict(value: Any) -> dict[str, Any]: + if isinstance(value, dict): + return value + if isinstance(value, str): + try: + parsed = json.loads(value) + except (TypeError, ValueError): + return {"text": value} + return parsed if isinstance(parsed, dict) else {"text": value} + return {} + + +def _compact_text(value: object, *, max_len: int = 120) -> str: + text = str(value or "").strip() + if not text: + return "" + text = re.sub(r"\s+", " ", text) + return text[:max_len] + + +def _compact_items( + value: object, + *, + max_items: int = 6, + max_len: int = 120, +) -> list[str]: + if isinstance(value, list): + raw_items = value + elif isinstance(value, str): + raw_items = re.split(r"[\n\r,,;;、/|]+", value) + else: + raw_items = [] + + items: list[str] = [] + seen: set[str] = set() + for raw in raw_items: + text = _compact_text(raw, max_len=max_len) + if not text: + continue + key = text.lower() + if key in seen: + continue + seen.add(key) + items.append(text) + if len(items) >= max_items: + break + return items + + +def _build_charter_context_payload(charter: TruthNode) -> dict[str, Any]: + content = _as_dict(charter.content) + modules = content.get("modules") + if not isinstance(modules, dict): + modules = {} + module_1 = modules.get("module_1") if isinstance(modules.get("module_1"), dict) else {} + module_2 = modules.get("module_2") if isinstance(modules.get("module_2"), dict) else {} + module_4 = modules.get("module_4") if isinstance(modules.get("module_4"), dict) else {} + module_5 = modules.get("module_5") if isinstance(modules.get("module_5"), dict) else {} + module_6 = modules.get("module_6") if isinstance(modules.get("module_6"), dict) else {} + + signal_ids = content.get("signal_ids") + normalized_signal_ids: list[int] = [] + if isinstance(signal_ids, list): + for sid in signal_ids: + try: + normalized_signal_ids.append(int(sid)) + except Exception: + continue + + return { + "id": int(charter.id), + "title": _compact_text(charter.title or "", max_len=120) or "未命名宪章", + "version": int(charter.version or 1), + "status": _compact_text(charter.status or "", max_len=32) or "unknown", + "summary": _compact_text( + content.get("summary") + or content.get("rationale") + or content.get("background") + or module_1.get("problem_statement") + or "", + max_len=180, + ), + "objectives": _compact_items( + content.get("objectives") + or module_1.get("goal_statement") + or [], + max_items=5, + max_len=100, + ), + "scope": _compact_text( + content.get("scope") + or module_2.get("alignment_qa") + or "", + max_len=240, + ), + "in_scope": _compact_items( + content.get("in_scope") + or module_2.get("in_scope") + or [], + max_items=5, + max_len=100, + ), + "out_of_scope": _compact_items( + content.get("out_of_scope") + or module_2.get("out_of_scope") + or [], + max_items=5, + max_len=100, + ), + "success_criteria": _compact_items( + content.get("success_criteria") + or content.get("success_metrics") + or content.get("core_metrics") + or module_1.get("kpis") + or [], + max_items=6, + max_len=100, + ), + "constraints": _compact_items( + content.get("constraints") + or module_5.get("execution_rule") + or module_6.get("governance_rule") + or module_2.get("alignment_qa") + or [], + max_items=6, + max_len=100, + ), + "milestones": _compact_items( + content.get("milestones") + or module_4.get("milestones") + or [], + max_items=5, + max_len=100, + ), + "risks": _compact_items( + content.get("risks") + or module_5.get("risk_register") + or [], + max_items=5, + max_len=100, + ), + "signal_ids": normalized_signal_ids, + } + + +__all__ = ["_build_charter_context_payload"] diff --git a/backend/app/services/sstc/charter_prefill_utils.py b/backend/app/services/sstc/charter_prefill_utils.py index 8504e550..3dfdbbc0 100644 --- a/backend/app/services/sstc/charter_prefill_utils.py +++ b/backend/app/services/sstc/charter_prefill_utils.py @@ -69,6 +69,115 @@ def _default_escalation_trigger() -> str: ] ) +async def _load_charter_project_context( + *, + db: AsyncSession, + tenant_id: int, + project_id: int, +) -> dict[str, Any]: + from ...models import Project + + row = ( + await db.execute( + select(Project).where( + and_( + Project.tenant_id == int(tenant_id), + Project.id == int(project_id), + ) + ) + ) + ).scalar_one_or_none() + if row is None: + return {} + return { + "id": int(row.id), + "name": str(getattr(row, "name", "") or "").strip(), + "title": str(getattr(row, "title", "") or getattr(row, "name", "") or "").strip(), + "description": str(getattr(row, "description", "") or "").strip(), + "status": str(getattr(row, "status", "") or "").strip(), + "owner_id": getattr(row, "owner_id", None), + } + +async def _load_active_projects_context( + *, + db: AsyncSession, + tenant_id: int, + project_id: int, + limit: int = 10, +) -> list[dict[str, Any]]: + from ...models import Project + + rows = ( + await db.execute( + select( + Project.id, + Project.name, + Project.title, + Project.description, + Project.status, + Project.owner_id, + ) + .where( + and_( + Project.tenant_id == int(tenant_id), + Project.status != "inactive", + Project.id != int(project_id), + ) + ) + .order_by(Project.id.desc()) + .limit(max(1, min(20, int(limit)))) + ) + ).all() + active_projects: list[dict[str, Any]] = [] + for row in rows: + active_projects.append( + { + "id": int(row.id), + "name": str(row.name or "").strip(), + "title": str(row.title or row.name or "").strip(), + "description": str(row.description or "").strip(), + "status": str(row.status or "").strip(), + "owner_id": getattr(row, "owner_id", None), + } + ) + return active_projects + +def _build_charter_project_context_text( + project_context: dict[str, Any], + active_projects: list[dict[str, Any]], +) -> str: + blocks: list[str] = [] + if isinstance(project_context, dict) and project_context: + blocks.append( + "\n".join( + [ + "[当前项目]", + f"ID:{project_context.get('id')}", + f"名称:{project_context.get('title') or project_context.get('name') or '未命名项目'}", + f"描述:{str(project_context.get('description') or '暂无描述').strip()}", + f"状态:{project_context.get('status') or 'unknown'}", + f"负责人ID:{project_context.get('owner_id') or '未指定'}", + ] + ).strip() + ) + + if active_projects: + lines = ["[活跃项目(用于查重/复用)]"] + for idx, project in enumerate(active_projects[:10], start=1): + title = str(project.get("title") or project.get("name") or "未命名项目").strip() + description = str(project.get("description") or "").strip() + status = str(project.get("status") or "").strip() or "unknown" + owner_id = project.get("owner_id") + row_parts = [f"{idx}. {title}", f"状态:{status}"] + if description: + row_parts.append(f"描述:{description[:80]}") + if owner_id is not None: + row_parts.append(f"负责人ID:{owner_id}") + lines.append(" | ".join(row_parts)) + blocks.append("\n".join(lines)) + + return "\n\n".join(blocks).strip() + async def _generate_charter_seed( *, db: AsyncSession, @@ -79,9 +188,26 @@ async def _generate_charter_seed( if not signal_context.strip(): return {} + project_context = await _load_charter_project_context( + db=db, + tenant_id=tenant_id, + project_id=project_id, + ) + active_projects = await _load_active_projects_context( + db=db, + tenant_id=tenant_id, + project_id=project_id, + ) + project_context_text = _build_charter_project_context_text( + project_context=project_context, + active_projects=active_projects, + ) + active_projects_json = json.dumps(active_projects, ensure_ascii=False) + prompt = ( "你是 AI 项目经理,请基于输入信号生成可落地的宪章草稿。" "所有自然语言字段必须使用简体中文,禁止英文句子;模型名称或技术名词可保留英文。" + "必须先结合当前项目上下文与活跃项目列表判断是否重复建设或应当合并。" "只输出 JSON,不要 markdown。\n" "字段:\n" "{\n" @@ -97,6 +223,8 @@ async def _generate_charter_seed( ' "primary_model": "模型名",\n' ' "fallback_model": "备用模型名"\n' "}\n\n" + f"当前项目上下文:\n{project_context_text or '无'}\n\n" + f"现有活跃项目(JSON):\n{active_projects_json or '[]'}\n\n" f"信号输入:\n{signal_context[:6000]}" ) try: @@ -122,6 +250,9 @@ async def _generate_charter_seed( signal_context, tenant_id=tenant_id, db=db, + project_id=project_id, + project_context=project_context_text, + active_projects_json=active_projects_json, ) if isinstance(ai_seed, dict): if _needs_seed_localize_zh(ai_seed): @@ -132,4 +263,3 @@ async def _generate_charter_seed( return {} except Exception: return {} - diff --git a/backend/app/utils/thread_context.py b/backend/app/utils/thread_context.py new file mode 100644 index 00000000..e8ab8d32 --- /dev/null +++ b/backend/app/utils/thread_context.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import Any, Optional + +import re + + +def _normalize_token(value: Any) -> str: + text = re.sub(r"\s+", "", str(value or "").strip()) + return text[:128] + + +def extract_thread_anchor(thread_context: Optional[dict[str, Any]] = None) -> str: + if not isinstance(thread_context, dict): + return "" + for key in ("root_message_id", "thread_id", "reply_to_message_id"): + value = _normalize_token(thread_context.get(key)) + if value: + return value + return "" + + +def build_feishu_thread_key( + *, + source_chat_id: Optional[str], + source_sender_open_id: Optional[str], + sender: Optional[str], + source_message_id: Optional[str] = None, + thread_context: Optional[dict[str, Any]] = None, +) -> str: + chat = _normalize_token(source_chat_id) + anchor = extract_thread_anchor(thread_context) + if anchor: + return f"feishu:{chat}:{anchor}" if chat else f"feishu:{anchor}" + + message_id = _normalize_token(source_message_id) + if message_id: + return f"feishu:{chat}:{message_id}" if chat else f"feishu:{message_id}" + + actor = _normalize_token(source_sender_open_id) + if not actor: + actor = _normalize_token(sender).lower() + if actor: + return f"feishu:{chat}:{actor}" if chat else f"feishu:{actor}" + return "" + diff --git a/backend/check_drafts.py b/backend/check_drafts.py new file mode 100644 index 00000000..312d865e --- /dev/null +++ b/backend/check_drafts.py @@ -0,0 +1,52 @@ +"""Check and handle draft confirmation.""" +import asyncio + +async def main(): + from app.core.database import AsyncSessionLocal + from sqlalchemy import select, and_ + from app.models import WorkItem, IngestEvent + + tenant_id = 1 + + async with AsyncSessionLocal() as db: + # Check for pending drafts + stmt = ( + select(WorkItem, IngestEvent) + .join(IngestEvent, IngestEvent.id == WorkItem.ingest_event_id) + .where( + and_( + WorkItem.tenant_id == tenant_id, + WorkItem.state == "draft_pending_confirm", + ) + ) + .order_by(WorkItem.id.desc()) + .limit(5) + ) + result = await db.execute(stmt) + rows = result.all() + + if not rows: + print("✅ 当前没有待确认的草稿") + print("\n最近处理的工作项:") + # Show recent work items + recent_stmt = ( + select(WorkItem) + .where(WorkItem.tenant_id == tenant_id) + .order_by(WorkItem.id.desc()) + .limit(3) + ) + recent_result = await db.execute(recent_stmt) + recent_items = recent_result.scalars().all() + for item in recent_items: + print(f" - ID: {item.id}, State: {item.state}, Intent: {item.intent}") + else: + print(f"找到 {len(rows)} 个待确认草稿:\n") + for work_item, ingest_event in rows: + print(f"Work Item ID: {work_item.id}") + print(f"Intent: {work_item.intent}") + print(f"Draft Content: {work_item.draft_content}") + print(f"Created: {work_item.created_at}") + print("-" * 50) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/handle_confirm.py b/backend/handle_confirm.py new file mode 100644 index 00000000..627d71c3 --- /dev/null +++ b/backend/handle_confirm.py @@ -0,0 +1,66 @@ +"""Handle draft confirmation directly.""" +import asyncio +import os + +async def main(): + from app.core.database import AsyncSessionLocal + from app.services.ai_pmo.ai_pmo_kernel import AIPMOKernelService + from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler + from sqlalchemy import select, and_ + from app.models import WorkItem, IngestEvent + + tenant_id = 1 + + async with AsyncSessionLocal() as db: + # Find the pending draft work item + stmt = ( + select(WorkItem, IngestEvent) + .join(IngestEvent, IngestEvent.id == WorkItem.ingest_event_id) + .where( + and_( + WorkItem.tenant_id == tenant_id, + WorkItem.state == "draft_pending_confirm", + ) + ) + .order_by(WorkItem.id.desc()) + .limit(1) + ) + result = await db.execute(stmt) + row = result.first() + + if not row: + print("No pending draft found") + return + + work_item, ingest_event = row + print(f"Found pending draft: work_item_id={work_item.id}, intent={work_item.intent}") + + # Handle confirmation + kernel = AIPMOKernelService(db, tenant_id=tenant_id) + handler = DraftConfirmHandler(kernel) + + result = await handler.handle_text_reply( + message_text="确认", + sender="林宇航", + source_system="feishu", + source_message_id="om_x100b53013d259114c4d68abc6d1af6a", + channel_ref="oc_c487d87563e48c2f9566ccd5a758212c", + source_sender_open_id="ou_b6d2d3aa8c02d18256def525e85d558f", + expected_version=None, + thread_context={ + "reply_to_message_id": "om_x100b53013e7a4cb0c34d331fdf31ca9" + } + ) + + print(f"\nResult: {result}") + await db.commit() + + if result.get("handled"): + print(f"\n✅ Success: {result.get('followup_reply')}") + print(f"Signal ID: {result.get('signal_id')}") + print(f"Project ID: {result.get('project_id')}") + else: + print(f"\n❌ Failed: {result}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/handle_confirm2.py b/backend/handle_confirm2.py new file mode 100644 index 00000000..591f0351 --- /dev/null +++ b/backend/handle_confirm2.py @@ -0,0 +1,65 @@ +"""Handle draft confirmation directly.""" +import asyncio + +async def main(): + from app.core.database import AsyncSessionLocal + from app.services.ai_pmo.ai_pmo_kernel import AIPMOKernelService + from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler + from sqlalchemy import select, and_ + from app.models import WorkItem, IngestEvent + + tenant_id = 1 + + async with AsyncSessionLocal() as db: + # Find the latest pending draft work item + stmt = ( + select(WorkItem, IngestEvent) + .join(IngestEvent, IngestEvent.id == WorkItem.ingest_event_id) + .where( + and_( + WorkItem.tenant_id == tenant_id, + WorkItem.state == "draft_pending_confirm", + ) + ) + .order_by(WorkItem.id.desc()) + .limit(1) + ) + result = await db.execute(stmt) + row = result.first() + + if not row: + print("No pending draft found") + return + + work_item, ingest_event = row + print(f"Found pending draft: work_item_id={work_item.id}, intent={work_item.intent}") + + # Handle confirmation + kernel = AIPMOKernelService(db, tenant_id=tenant_id) + handler = DraftConfirmHandler(kernel) + + result = await handler.handle_text_reply( + message_text="确认,按这版详细方案入库,并保留里程碑、风险和验收标准。", + sender="林宇航", + source_system="feishu", + source_message_id="om_x100b530cb976dc80c2a9f8c5bc69309", + channel_ref="oc_c487d87563e48c2f9566ccd5a758212c", + source_sender_open_id="ou_b6d2d3aa8c02d18256def525e85d558f", + expected_version=None, + thread_context={ + "reply_to_message_id": "om_x100b530c8cefdca4c3b0892f8e9a726" + } + ) + + print(f"\nResult: {result}") + await db.commit() + + if result.get("handled"): + print(f"\n✅ Success: {result.get('followup_reply')}") + print(f"Signal ID: {result.get('signal_id')}") + print(f"Project ID: {result.get('project_id')}") + else: + print(f"\n❌ Failed: {result}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/handle_confirm3.py b/backend/handle_confirm3.py new file mode 100644 index 00000000..b81e64ca --- /dev/null +++ b/backend/handle_confirm3.py @@ -0,0 +1,67 @@ +"""Handle draft confirmation with better matching.""" +import asyncio + +async def main(): + from app.core.database import AsyncSessionLocal + from app.services.ai_pmo.ai_pmo_kernel import AIPMOKernelService + from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler + from sqlalchemy import select, and_ + from app.models import WorkItem, IngestEvent + + tenant_id = 1 + + async with AsyncSessionLocal() as db: + # Find the most recent project_create draft (not project_signal) + stmt = ( + select(WorkItem, IngestEvent) + .join(IngestEvent, IngestEvent.id == WorkItem.ingest_event_id) + .where( + and_( + WorkItem.tenant_id == tenant_id, + WorkItem.state == "draft_pending_confirm", + WorkItem.intent.like("project_create%"), + ) + ) + .order_by(WorkItem.id.desc()) + .limit(1) + ) + result = await db.execute(stmt) + row = result.first() + + if not row: + print("No pending project_create draft found") + return + + work_item, ingest_event = row + print(f"Found pending draft: work_item_id={work_item.id}, intent={work_item.intent}") + print(f"Summary: {work_item.draft_content.get('summary', 'N/A')}") + + # Handle confirmation + kernel = AIPMOKernelService(db, tenant_id=tenant_id) + handler = DraftConfirmHandler(kernel) + + result = await handler.handle_text_reply( + message_text="确认,按这版详细方案入库,并保留里程碑、风险和验收标准。", + sender="林宇航", + source_system="feishu", + source_message_id="om_x100b530d1d6020acc14e0a90435704b", + channel_ref="oc_c487d87563e48c2f9566ccd5a758212c", + source_sender_open_id="ou_b6d2d3aa8c02d18256def525e85d558f", + expected_version=None, + thread_context={ + "reply_to_message_id": "om_x100b530d7cc744a0c39a8052f539e52" + } + ) + + print(f"\nResult: {result}") + await db.commit() + + if result.get("handled"): + print(f"\n✅ Success: {result.get('followup_reply')}") + print(f"Signal ID: {result.get('signal_id')}") + print(f"Project ID: {result.get('project_id')}") + else: + print(f"\n❌ Failed: {result}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/handle_draft_confirm.py b/backend/handle_draft_confirm.py new file mode 100644 index 00000000..c457cc34 --- /dev/null +++ b/backend/handle_draft_confirm.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Handle draft confirmation directly.""" +import asyncio +import os +import sys + +# Add backend to path +sys.path.insert(0, '/Users/yuuuu/Documents/AIProjectManger/backend') + +async def main(): + from dotenv import load_dotenv + load_dotenv('/Users/yuuuu/Documents/AIProjectManger/.env') + + from app.core.database import AsyncSessionLocal + from app.services.ai_pmo.ai_pmo_kernel import AIPMOKernelService + from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler + from sqlalchemy import select, and_ + from app.models import WorkItem, IngestEvent + + tenant_id = 1 + + async with AsyncSessionLocal() as db: + # Find the pending draft work item + stmt = ( + select(WorkItem, IngestEvent) + .join(IngestEvent, IngestEvent.id == WorkItem.ingest_event_id) + .where( + and_( + WorkItem.tenant_id == tenant_id, + WorkItem.state == "draft_pending_confirm", + ) + ) + .order_by(WorkItem.id.desc()) + .limit(1) + ) + result = await db.execute(stmt) + row = result.first() + + if not row: + print("No pending draft found") + return + + work_item, ingest_event = row + print(f"Found pending draft: work_item_id={work_item.id}, intent={work_item.intent}") + print(f"Draft content: {work_item.draft_content}") + + # Handle confirmation + kernel = AIPMOKernelService(db, tenant_id=tenant_id) + handler = DraftConfirmHandler(kernel) + + result = await handler.handle_text_reply( + message_text="确认", + sender="林宇航", + source_system="feishu", + source_message_id="om_x100b53013d259114c4d68abc6d1af6a", + channel_ref="oc_c487d87563e48c2f9566ccd5a758212c", + source_sender_open_id="ou_b6d2d3aa8c02d18256def525e85d558f", + expected_version=None, + thread_context={ + "reply_to_message_id": "om_x100b53013e7a4cb0c34d331fdf31ca9" + } + ) + + print(f"\nResult: {result}") + await db.commit() + + if result.get("handled"): + print(f"\n✅ Success: {result.get('followup_reply')}") + print(f"Signal ID: {result.get('signal_id')}") + print(f"Project ID: {result.get('project_id')}") + else: + print(f"\n❌ Failed: {result}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backend/requirements.txt b/backend/requirements.txt index fdbac24e..d797a21c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -8,6 +8,7 @@ pgvector>=0.3.5 python-dotenv>=1.0.1 redis>=5.0.8 httpx>=0.27.0 +certifi>=2024.7.4 pydantic>=2.8.2 python-multipart>=0.0.9 structlog>=24.2.0 diff --git a/backend/tests/test_adaptive_rag_mainline_contract.py b/backend/tests/test_adaptive_rag_mainline_contract.py index 6540dc0a..1e3aa9e1 100644 --- a/backend/tests/test_adaptive_rag_mainline_contract.py +++ b/backend/tests/test_adaptive_rag_mainline_contract.py @@ -1,7 +1,12 @@ import pytest +from types import SimpleNamespace from app.services.adaptive_rag_service.mixin_answer_entry import AdaptiveRAGAnswerEntryMixin from app.services.adaptive_rag_service.mixin_answer_mainline import AdaptiveRAGAnswerMainlineMixin +from app.services.adaptive_rag_service.mixin_deep_guard_answer import AdaptiveRAGDeepGuardAnswerMixin +from app.services.adaptive_rag_service.mixin_deterministic_query_core import ( + AdaptiveRAGDeterministicQueryCoreMixin, +) class _FakeAdaptiveRAGService( @@ -95,3 +100,149 @@ async def test_answer_entry_feishu_write_mode_still_goes_base_mainline() -> None assert service.base_calls == 1 assert int(service.base_kwargs["deep_retrieval_steps"]) <= 3 assert int(service.base_kwargs["deep_vector_top_k"]) <= 8 + + +@pytest.mark.asyncio +async def test_deterministic_query_prefers_project_created_at_answer() -> None: + calls: list[str] = [] + + class _FakeDeterministicService(AdaptiveRAGDeterministicQueryCoreMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + async def _try_fast_project_created_at_answer(self, *, clean_q: str, project_id): + calls.append("created_at") + _ = (clean_q, project_id) + return {"answer": "创建于 2026-03-01 09:30。", "sources": [], "missing_fields": []} + + async def _try_fast_project_progress_answer(self, *, clean_q: str, project_id): + calls.append("progress") + _ = (clean_q, project_id) + return {"answer": "进度回答", "sources": []} + + async def _try_fast_project_existence_answer(self, *, clean_q: str, project_id): + calls.append("existence") + _ = (clean_q, project_id) + return {"answer": "存在性回答", "sources": []} + + service = _FakeDeterministicService() + result = await service._run_deterministic_query( + question="他是什么时候创建的?", + project_id=7, + query_type="status", + ) + + assert result is not None + assert "创建于 2026-03-01 09:30" in str(result["answer"]) + assert calls[0] == "created_at" + + +def test_query_type_classifies_colloquial_list_request_as_list() -> None: + service = AdaptiveRAGDeterministicQueryCoreMixin() + + assert service._classify_query_type("我感觉很多重复的项目在系统,你能列一下吗?") == "list" + + +@pytest.mark.asyncio +async def test_deep_guard_answer_raises_on_empty_llm_response() -> None: + class _FakeLLM: + async def generate(self, prompt: str, **kwargs): + _ = (prompt, kwargs) + return "" + + class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + self.llm = _FakeLLM() + + def _normalize_thinking_mode(self, thinking_mode: str) -> str: + return "normal" if str(thinking_mode or "").strip().lower() != "deep" else "deep" + + async def get_project_manifest(self, project_id): + _ = project_id + return {} + + def _select_scene_prompt(self, **kwargs): + _ = kwargs + return "external_trend", "请回答问题" + + def _enforce_feature_evidence_in_answer(self, *, question: str, answer_text: str, retrieved, system_snapshot): + _ = (question, retrieved, system_snapshot) + return answer_text + + service = _FakeService() + + with pytest.raises(ValueError, match="adaptive_rag_answer_empty_response"): + await service._generate_answer( + question="最近AI行业有什么动态", + retrieved=[], + conversation=[], + project_id=None, + asked_via="feishu", + ) + + +@pytest.mark.asyncio +async def test_deep_guard_answer_prefers_member_workload_direct_answer() -> None: + class _FakeResult: + def __init__(self, rows=None, scalar=None): + self._rows = rows or [] + self._scalar = scalar + + def all(self): + return self._rows + + def first(self): + return self._rows[0] if self._rows else None + + def scalar_one_or_none(self): + return self._scalar + + class _FakeDB: + def __init__(self) -> None: + self.calls = 0 + + async def execute(self, stmt, params=None): # noqa: ANN001 + self.calls += 1 + if self.calls == 1: + return _FakeResult(rows=[SimpleNamespace(id=24, name="杨宇", username="yangyu")]) + return _FakeResult(rows=[(265.56, 95.6, 60.0)]) + + class _FakeLLM: + async def generate(self, prompt: str, **kwargs): # pragma: no cover - should not be called + raise AssertionError("LLM should not be called for direct member workload answer") + + class _FakeService(AdaptiveRAGDeepGuardAnswerMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + self.llm = _FakeLLM() + + def _normalize_thinking_mode(self, thinking_mode: str) -> str: + return "normal" + + async def get_project_manifest(self, project_id): + _ = project_id + return {} + + def _select_scene_prompt(self, **kwargs): + _ = kwargs + return "member_workload", "member workload prompt" + + def _enforce_feature_evidence_in_answer(self, *, question: str, answer_text: str, retrieved, system_snapshot): + _ = (question, retrieved, system_snapshot) + return answer_text + + service = _FakeService() + result = await service._generate_answer( + question="杨宇现在负载情况如何?", + retrieved=[], + conversation=[], + project_id=None, + asked_via="feishu", + ) + + assert "杨宇当前负载265.56%" in result + assert "严重超载" in result diff --git a/backend/tests/test_ai_orchestration_run_service.py b/backend/tests/test_ai_orchestration_run_service.py new file mode 100644 index 00000000..4c0597d1 --- /dev/null +++ b/backend/tests/test_ai_orchestration_run_service.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from types import SimpleNamespace + +import pytest + +from app.services.ai_orchestration_run.service import AIOrchestrationRunService + + +class _FakeDB: + def __init__(self) -> None: + self.added = None + + def add(self, row) -> None: # noqa: ANN001 + self.added = row + + async def flush(self) -> None: + return None + + +@pytest.mark.asyncio +async def test_enqueue_ingress_run_persists_thread_context_in_task_payload(monkeypatch) -> None: + db = _FakeDB() + service = AIOrchestrationRunService(db) + + async def _fake_get_run(*, tenant_id, trace_id): # noqa: ARG001 + return None + + monkeypatch.setattr(service, "get_run", _fake_get_run) + + row, created = await service.enqueue_ingress_run( + tenant_id=1, + source_system="feishu", + source_message_id="msg-1", + sender="林宇航", + message_text="它是什么时候创建的?", + channel_ref="chat-1", + source_sender_open_id="ou-1", + chat_type="group", + captured_at=None, + project_hint_id=None, + thread_context={ + "thread_id": "th-1", + "root_message_id": "root-1", + "reply_to_message_id": "reply-1", + }, + ) + + assert created is True + assert db.added is row + assert isinstance(row.result_payload, dict) + assert row.result_payload["task_payload"]["thread_context"] == { + "thread_id": "th-1", + "root_message_id": "root-1", + "reply_to_message_id": "reply-1", + } diff --git a/backend/tests/test_ai_pmo_ingress_mainline.py b/backend/tests/test_ai_pmo_ingress_mainline.py index 6676951d..873e91cf 100644 --- a/backend/tests/test_ai_pmo_ingress_mainline.py +++ b/backend/tests/test_ai_pmo_ingress_mainline.py @@ -1,3 +1,4 @@ +from datetime import datetime, timedelta from types import SimpleNamespace import pytest @@ -5,8 +6,19 @@ from fastapi import HTTPException from app.api.ai_pmo_api import routes_core from app.api.ai_pmo_api.schemas import PMOProcessTextRequest +from app.services.ai_pmo.ai_pmo_kernel.mixins.intent.ai_pmo_kernel_mixins_intent_rules import ( + KernelIntentRulesMixin, +) +from app.services.ai_pmo.ai_pmo_kernel.ai_agent_kernel import gate_execution +from app.services.ai_pmo.ai_pmo_kernel.ai_pmo_reply_renderer import ReplyRenderer from app.services.ai_pmo.ai_pmo_kernel_ingress_flow import mixin_entry +from app.services.ai_pmo.ai_pmo_kernel_ingress_flow import mixin_prepare_manual from app.services.ai_pmo.ai_pmo_typed_executor_execute import mixin_execute_query_project +from app.services.ai_pmo.ai_pmo_intent import CanonicalIntent +from app.services.llm_service import ChatCompletionResult, StructuredExtractionResult +from app.services.llm_service import mixin_tier_escalation +from app.services.llm_service.mixin_tier_escalation import LLMTierEscalationMixin +from app.utils.thread_context import build_feishu_thread_key @pytest.mark.asyncio @@ -128,6 +140,7 @@ async def test_prepare_mixed_sidecar_context_passes_mixed_contract(monkeypatch) answer_kwargs = captured["answer_kwargs"] assert isinstance(answer_kwargs, dict) assert answer_kwargs["asked_via"] == "pmflow_manual" + assert answer_kwargs["project_id"] is None assert answer_kwargs["message_intent"] == "mixed" assert answer_kwargs["mixed_signal_part"] == "新增任务确认流" assert answer_kwargs["conversation_key"] == "manual-msg-2" @@ -181,6 +194,7 @@ async def test_process_ingress_prioritizes_draft_pending_confirm(monkeypatch) -> sender="tester", channel_ref="chat-1", source_sender_open_id="ou_xxx", + ingress_options={"thread_context": {"reply_to_message_id": "msg-root-1"}}, ) assert kernel.draft_called is True @@ -188,6 +202,264 @@ async def test_process_ingress_prioritizes_draft_pending_confirm(monkeypatch) -> assert "草稿" in result["direct_reply_text"] +def test_should_route_to_draft_confirmation_requires_anchor_or_explicit_id() -> None: + session = SimpleNamespace(last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM) + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="确认", + ingress_options=None, + ) is False + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="确认", + ingress_options={"thread_context": {"thread_id": "th_123"}}, + ) is False + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="确认", + ingress_options={"thread_context": {"root_message_id": "root_123"}}, + ) is False + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="创建时间是多少?", + ingress_options={"thread_context": {"reply_to_message_id": "msg-123"}}, + ) is False + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="确认", + ingress_options={"thread_context": {"reply_to_message_id": "msg-123"}}, + ) is True + + assert mixin_entry.KernelIngressFlowEntryMixin._should_route_to_draft_confirmation( + session=session, + message_text="工作项ID: 88 创建时间是多少?", + ingress_options=None, + ) is True + + +def test_short_confirmation_requires_reply_anchor() -> None: + session = SimpleNamespace( + last_intent=mixin_entry.STATE_DRAFT_PENDING_CONFIRM, + last_active_at=datetime.now(), + ) + sent_at = datetime.now() + timedelta(seconds=1) + + assert ( + KernelIntentRulesMixin._is_short_confirmation_message( + message_text="确认", + session=session, + message_sent_at=sent_at, + session_snapshot=None, + reply_anchor_present=False, + ) + is False + ) + assert ( + KernelIntentRulesMixin._is_short_confirmation_message( + message_text="确认", + session=session, + message_sent_at=sent_at, + session_snapshot=None, + reply_anchor_present=True, + ) + is True + ) + + +def test_reply_renderer_formats_project_delete_pending_and_success() -> None: + renderer = ReplyRenderer( + state_waiting_manual="waiting_manual", + state_failed="failed", + receipt_phase_action="action", + receipt_phase_pending="pending", + receipt_phase_error="error", + ) + intent = CanonicalIntent( + name="project.delete", + confidence=0.96, + project_id=None, + signal_title="", + signal_summary="", + metadata={"llm_parameters": {"project_id": 12, "project_title": "重复项目"}}, + ) + + pending = renderer.render( + work_state="waiting_manual", + intent=intent, + decision_record={"approval_requirement": {"message": "检测到删除或归档类写操作,必须进入显式审批/人工确认。"}}, + execution_result={ + "status": "waiting_manual", + "action_contract": { + "operation_code": "project.delete", + "params": {"project_id": 12, "project_title": "重复项目"}, + }, + }, + ) + assert pending["template_key"] == "pending_manual" + assert "重复项目" in pending["text"] + assert "人工确认" in pending["text"] + + success = renderer.render( + work_state="succeeded", + intent=intent, + decision_record={"approval_requirement": {}}, + execution_result={"status": "succeeded", "operation_code": "project.delete", "project_id": 12}, + ) + assert success["template_key"] == "action_succeeded" + assert "已删除项目#12《重复项目》" in success["text"] + + +@pytest.mark.asyncio +async def test_finalize_waiting_manual_passes_execution_result_to_renderer(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeReviewService: + async def review_work_item(self, **kwargs): + _ = kwargs + return {"verified": False, "issues": ["waiting_manual"], "next_step": "请确认"} + + class FakeRenderer: + def render(self, **kwargs): + captured["render_kwargs"] = kwargs + return { + "receipt_phase": "pending", + "template_key": "pending_manual", + "payload": {"intent": kwargs["intent"].name}, + "text": "ok", + } + + class FakeKernel(mixin_prepare_manual.KernelIngressFlowPrepareManualMixin): + def __init__(self) -> None: + class _FakeDB: + async def commit(self): + return None + + self.db = _FakeDB() + self.tenant_id = 7 + self.review_service = FakeReviewService() + self.reply_renderer = FakeRenderer() + + async def _maybe_emit_risk_cadence_snapshot(self, **kwargs): + _ = kwargs + + async def _claim_reply_receipt(self, **kwargs): + _ = kwargs + return True + + async def _write_processing_log(self, **kwargs): + _ = kwargs + + async def _update_session(self, **kwargs): + _ = kwargs + + async def _upsert_operation_execution_ledger(self, **kwargs): + _ = kwargs + return None + + def _extract_execution_operation_code(self, execution_result): + if not isinstance(execution_result, dict): + return None + action_contract = execution_result.get("action_contract") if isinstance(execution_result.get("action_contract"), dict) else {} + if isinstance(action_contract, dict): + operation_code = str(action_contract.get("operation_code") or "").strip().lower() + if operation_code: + return operation_code + operation_code = str(execution_result.get("operation_code") or "").strip().lower() + return operation_code or None + + def _normalize_execution_ledger_status(self, *, work_item_state, execution_status): + _ = execution_status + return "pending_manual" if str(work_item_state or "").strip().lower() == "waiting_manual" else "processing" + + async def _project_waiting_manual(self, **kwargs): + captured["project_waiting_manual"] = kwargs + + def _apply_execution_action_contract(self, **kwargs): + execution_result = dict(kwargs["execution_result"]) + execution_result["action_contract"] = { + "operation_code": "project.delete", + "params": {"project_id": 12, "project_title": "重复项目"}, + } + return execution_result + + def _build_capture_result(self, *, work_item, intent, execution_result, render, direct_reply_required, status, classification): + _ = (work_item, intent, execution_result, render, direct_reply_required, classification) + return {"status": status, "render": render} + + kernel = FakeKernel() + work_item = SimpleNamespace( + state=None, + action_bundle=None, + execution_result=None, + review_summary=None, + reply_template_key=None, + id=99, + project_id=12, + signal_id=None, + intent="project.delete", + source_message_id="msg-1", + draft_version=None, + draft_expires_at=None, + ) + session = SimpleNamespace() + ingest_event = SimpleNamespace() + decision_payload = { + "policy": {"approval_requirement": {"message": "检测到删除或归档类写操作,必须进入显式审批/人工确认。"}}, + "execution_plan": {"actions": []}, + "simulation": {}, + } + workflow_route = SimpleNamespace(key="project.delete", action="delete") + intent = CanonicalIntent( + name="project.delete", + confidence=0.94, + project_id=12, + signal_title="", + signal_summary="", + metadata={"llm_parameters": {"project_id": 12, "project_title": "重复项目"}}, + ) + + result = await kernel._finalize_waiting_manual_ingress( + work_item=work_item, + session=session, + intent=intent, + world_model={}, + decision_payload=decision_payload, + workflow_route=workflow_route, + decision_trace={"reason": "test"}, + source_system="feishu", + source_message_id="msg-1", + message_text="删除重复项目", + sender="tester", + ingest_event=ingest_event, + started=0.0, + ) + + assert result["status"] == "captured" + render_kwargs = captured["render_kwargs"] + assert isinstance(render_kwargs, dict) + assert render_kwargs["execution_result"]["action_contract"]["operation_code"] == "project.delete" + assert render_kwargs["execution_result"]["action_contract"]["params"]["project_title"] == "重复项目" + + +def test_gate_execution_ignores_low_confidence_but_keeps_risk_review() -> None: + low_confidence = gate_execution(confidence=0.02, risk_level="medium") + assert low_confidence.action == "auto_execute" + assert low_confidence.auto_execute is True + assert low_confidence.gate_code == "auto_execute_allowed" + assert "置信度" not in low_confidence.reason + + high_risk = gate_execution(confidence=0.02, risk_level="high") + assert high_risk.action == "auto_execute" + assert high_risk.auto_execute is True + assert high_risk.gate_code == "auto_execute_allowed" + assert "人工确认" not in high_risk.reason + + @pytest.mark.asyncio async def test_execute_query_intent_honors_rag_request_context(monkeypatch) -> None: captured: dict[str, object] = {} @@ -249,3 +521,501 @@ async def test_execute_query_intent_honors_rag_request_context(monkeypatch) -> N assert answer_kwargs["sync_requirement"] is True assert answer_kwargs["message_intent"] == "mixed" assert answer_kwargs["mixed_signal_part"] == "新增任务确认流" + + +@pytest.mark.asyncio +async def test_execute_query_intent_feishu_fallback_uses_thread_key(monkeypatch) -> None: + captured: dict[str, object] = {} + + class FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + _ = db + captured["tenant_id"] = tenant_id + + async def answer_question(self, **kwargs): + captured["answer_kwargs"] = kwargs + return {"answer": "fallback-answer", "session_id": 7} + + class FakeExecutor(mixin_execute_query_project.ExecuteQueryProjectMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 3 + + monkeypatch.setattr(mixin_execute_query_project, "AdaptiveRAGService", FakeAdaptiveRAGService) + executor = FakeExecutor() + intent = SimpleNamespace(project_id=None, metadata={"thread_context": {"root_message_id": "root-1"}}) + result = await executor._execute_query_intent( + intent=intent, + message_text="最近AI行业有什么动态", + operator_user_id=999, + source_system="feishu", + source_message_id="msg-feishu-1", + ) + + assert result["status"] == "succeeded" + answer_kwargs = captured["answer_kwargs"] + assert isinstance(answer_kwargs, dict) + assert answer_kwargs["asked_by"] is None + assert answer_kwargs["asked_via"] == "feishu" + assert answer_kwargs["source_chat_id"] is None + assert answer_kwargs["conversation_key"] == build_feishu_thread_key( + source_chat_id=None, + source_sender_open_id=None, + sender=None, + source_message_id="msg-feishu-1", + thread_context={"root_message_id": "root-1"}, + ) + + +@pytest.mark.asyncio +async def test_low_tier_rag_failure_prefers_glm5_openclaw_failover(monkeypatch) -> None: + captured: dict[str, object] = {"logs": []} + + monkeypatch.setattr( + mixin_tier_escalation, + "_runtime_log", + lambda scope, message: captured["logs"].append((scope, message)), + ) + + low_engine = { + "id": "low-1", + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://open.bigmodel.cn/api/paas/v4", + "api_key": "low-key", + } + high_engine = { + "id": "high-1", + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.openai.com/v1", + "api_key": "high-key", + } + bundle = { + "settings": {}, + "active_engine_id": "low-1", + "engines": [low_engine, high_engine], + } + openclaw_bundle = { + "rules": [ + { + "name": "engine_high_reasoning_glm5", + "enabled": True, + "priority": 220, + "tier": "high", + "provider": "openai", + "model": "GLM-5", + "api_env": "OPENAI_API_KEY", + "match_any": ["adaptive_rag_answer"], + } + ] + } + + class FakeLLM(LLMTierEscalationMixin): + def _low_to_high_escalation_enabled(self) -> bool: + return True + + def _is_low_tier_function(self, function_name: str) -> bool: + return function_name == "adaptive_rag_answer_normal" + + @staticmethod + def _rule_matches_function(rule, function_name: str) -> bool: + fn = str(function_name or "").strip().lower() + if not fn: + return False + if bool(rule.get("match_all", False)): + return True + exact = str(rule.get("function_exact") or "").strip().lower() + if exact and fn == exact: + return True + prefix = str(rule.get("function_prefix") or "").strip().lower() + if prefix and fn.startswith(prefix): + return True + match_any = rule.get("match_any") + if isinstance(match_any, list): + for hint in match_any: + token = str(hint or "").strip().lower() + if token and token in fn: + return True + return False + + async def _get_or_init_bundle(self, db, *, tenant_id: int): + _ = (db, tenant_id) + return SimpleNamespace(), bundle + + def _resolve_active_engine_from_bundle(self, bundle_value): + _ = bundle_value + return dict(low_engine) + + def _load_openclaw_routing_bundle(self): + return dict(openclaw_bundle) + + def _resolve_engine_from_rule(self, *, rule, engines, active_engine): + _ = active_engine + for item in engines: + if ( + str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower() + and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip() + ): + return dict(item) + return None + + def _ensure_openclaw_control_plane_engine(self, engine): + return dict(engine) + + def _normalize_engine_settings(self, raw): + _ = raw + return {} + + def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label): + _ = (function_name, settings, engine_label) + return dict(engine) + + def _is_invalid_structured_output(self, *, function_name, response_text): + _ = (function_name, response_text) + return False + + async def _chat_completion(self, **kwargs): + captured["chat_kwargs"] = kwargs + return "GLM-5 answer" + + llm = FakeLLM() + text, resolved_engine = await llm._retry_with_high_tier_after_low_model_failure( + db=object(), + tenant_id=1, + function_name="adaptive_rag_answer_normal", + failed_engine=dict(low_engine), + prompt="请回答一个问题", + system_prompt="system prompt", + temperature=0.2, + max_tokens=512, + response_mode="complete", + error=ValueError("LLM 返回空内容"), + ) + + assert text == "GLM-5 answer" + assert isinstance(resolved_engine, dict) + assert resolved_engine["provider"] == "openai" + assert resolved_engine["model"] == "GLM-5" + assert captured["chat_kwargs"]["model"] == "GLM-5" + log_text = "\n".join(message for _, message in captured["logs"]) + assert "OpenClaw高阶升级命中" in log_text + assert "高阶升级成功" in log_text + + +@pytest.mark.asyncio +async def test_low_tier_intent_failure_forces_glm5_retry_even_when_generic_high_tier_differs(monkeypatch) -> None: + captured: dict[str, object] = {"logs": []} + + monkeypatch.setattr( + mixin_tier_escalation, + "_runtime_log", + lambda scope, message: captured["logs"].append((scope, message)), + ) + + low_engine = { + "id": "low-1", + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://open.bigmodel.cn/api/paas/v4", + "api_key": "low-key", + } + mini_engine = { + "id": "high-mini-1", + "provider": "openai", + "model": "MiniMax-M2.5", + "base_url": "https://api.openai.com/v1", + "api_key": "mini-key", + } + glm5_engine = { + "id": "high-glm5-1", + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.openai.com/v1", + "api_key": "glm5-key", + } + bundle = { + "settings": {}, + "active_engine_id": "low-1", + "engines": [low_engine, mini_engine, glm5_engine], + } + openclaw_bundle = { + "rules": [ + { + "name": "engine_low_intent_kernel_exact", + "enabled": True, + "priority": 310, + "tier": "low", + "provider": "glm", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + "function_exact": "classify_intent_ai_pmo_kernel", + } + ] + } + + class FakeLLM(LLMTierEscalationMixin): + def _low_to_high_escalation_enabled(self) -> bool: + return True + + def _is_low_tier_function(self, function_name: str) -> bool: + return function_name == "classify_intent_ai_pmo_kernel" + + @staticmethod + def _rule_matches_function(rule, function_name: str) -> bool: + fn = str(function_name or "").strip().lower() + if not fn: + return False + if bool(rule.get("match_all", False)): + return True + exact = str(rule.get("function_exact") or "").strip().lower() + if exact and fn == exact: + return True + prefix = str(rule.get("function_prefix") or "").strip().lower() + if prefix and fn.startswith(prefix): + return True + match_any = rule.get("match_any") + if isinstance(match_any, list): + for hint in match_any: + token = str(hint or "").strip().lower() + if token and token in fn: + return True + return False + + async def _get_or_init_bundle(self, db, *, tenant_id: int): + _ = (db, tenant_id) + return SimpleNamespace(), bundle + + def _resolve_active_engine_from_bundle(self, bundle_value): + _ = bundle_value + return dict(low_engine) + + def _load_openclaw_routing_bundle(self): + return dict(openclaw_bundle) + + def _resolve_engine_from_rule(self, *, rule, engines, active_engine): + _ = active_engine + for item in engines: + if ( + str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower() + and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip() + ): + return dict(item) + return None + + def _resolve_tier_engine(self, *, tier, engines, active_engine): + _ = (engines, active_engine) + if str(tier).strip().lower() == "high": + return dict(mini_engine) + return dict(low_engine) + + def _ensure_openclaw_control_plane_engine(self, engine): + return dict(engine) + + def _normalize_engine_settings(self, raw): + _ = raw + return {} + + def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label): + _ = (function_name, settings, engine_label) + return dict(engine) + + def _is_invalid_structured_output(self, *, function_name, response_text): + _ = (function_name, response_text) + return False + + async def _chat_completion(self, **kwargs): + captured["chat_kwargs"] = kwargs + return "GLM-5 answer" + + llm = FakeLLM() + text, resolved_engine = await llm._retry_with_high_tier_after_low_model_failure( + db=object(), + tenant_id=1, + function_name="classify_intent_ai_pmo_kernel", + failed_engine=dict(low_engine), + prompt="请返回结构化意图", + system_prompt="system prompt", + temperature=0.2, + max_tokens=512, + response_mode="complete", + error=ValueError("LLM 返回空内容"), + ) + + assert text == "GLM-5 answer" + assert isinstance(resolved_engine, dict) + assert resolved_engine["model"] == "GLM-5" + assert captured["chat_kwargs"]["model"] == "GLM-5" + log_text = "\n".join(message for _, message in captured["logs"]) + assert "GLM-5强制高阶升级命中" in log_text or "OpenClaw高阶升级命中" in log_text + assert "高阶升级成功" in log_text + + +@pytest.mark.asyncio +async def test_low_tier_structured_failure_forces_glm5_retry(monkeypatch) -> None: + captured: dict[str, object] = {"logs": []} + + monkeypatch.setattr( + mixin_tier_escalation, + "_runtime_log", + lambda scope, message: captured["logs"].append((scope, message)), + ) + + low_engine = { + "id": "low-1", + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://open.bigmodel.cn/api/paas/v4", + "api_key": "low-key", + } + mini_engine = { + "id": "high-mini-1", + "provider": "openai", + "model": "MiniMax-M2.5", + "base_url": "https://api.openai.com/v1", + "api_key": "mini-key", + } + glm5_engine = { + "id": "high-glm5-1", + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.openai.com/v1", + "api_key": "glm5-key", + } + bundle = { + "settings": {}, + "active_engine_id": "low-1", + "engines": [low_engine, mini_engine, glm5_engine], + } + openclaw_bundle = { + "rules": [ + { + "name": "engine_low_intent_kernel_exact", + "enabled": True, + "priority": 310, + "tier": "low", + "provider": "glm", + "model": "qwen-plus", + "api_env": "DASHSCOPE_API_KEY", + "function_exact": "classify_intent_ai_pmo_kernel", + } + ] + } + + class FakeLLM(LLMTierEscalationMixin): + def _low_to_high_escalation_enabled(self) -> bool: + return True + + def _is_low_tier_function(self, function_name: str) -> bool: + return function_name == "classify_intent_ai_pmo_kernel" + + @staticmethod + def _rule_matches_function(rule, function_name: str) -> bool: + fn = str(function_name or "").strip().lower() + if not fn: + return False + if bool(rule.get("match_all", False)): + return True + exact = str(rule.get("function_exact") or "").strip().lower() + if exact and fn == exact: + return True + prefix = str(rule.get("function_prefix") or "").strip().lower() + if prefix and fn.startswith(prefix): + return True + match_any = rule.get("match_any") + if isinstance(match_any, list): + for hint in match_any: + token = str(hint or "").strip().lower() + if token and token in fn: + return True + return False + + async def _get_or_init_bundle(self, db, *, tenant_id: int): + _ = (db, tenant_id) + return SimpleNamespace(), bundle + + def _resolve_active_engine_from_bundle(self, bundle_value): + _ = bundle_value + return dict(low_engine) + + def _load_openclaw_routing_bundle(self): + return dict(openclaw_bundle) + + def _resolve_engine_from_rule(self, *, rule, engines, active_engine): + _ = active_engine + for item in engines: + if ( + str(item.get("provider") or "").strip().lower() == str(rule.get("provider") or "").strip().lower() + and str(item.get("model") or "").strip() == str(rule.get("model") or "").strip() + ): + return dict(item) + return None + + def _resolve_tier_engine(self, *, tier, engines, active_engine): + _ = (engines, active_engine) + if str(tier).strip().lower() == "high": + return dict(mini_engine) + return dict(low_engine) + + def _ensure_openclaw_control_plane_engine(self, engine): + return dict(engine) + + def _normalize_engine_settings(self, raw): + _ = raw + return {} + + def _ensure_engine_allowed_for_function(self, *, engine, function_name, settings, engine_label): + _ = (function_name, settings, engine_label) + return dict(engine) + + def _is_invalid_structured_output(self, *, function_name, response_text): + _ = (function_name, response_text) + return False + + def _extract_structured_object_from_response( + self, + *, + function_name, + response, + fallback_text, + provider, + model, + ): + _ = (function_name, response, fallback_text) + return StructuredExtractionResult( + payload={"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"}, + source_field="generated_text", + provider=provider, + model=model, + raw_text='{"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"}', + validation="ok", + ) + + async def _chat_completion_raw(self, **kwargs): + captured["chat_kwargs"] = kwargs + raw_text = '{"confidence": 0.91, "reasoning": "ok", "intent": "query.read_only"}' + return ChatCompletionResult(text=raw_text, raw_response=raw_text) + + llm = FakeLLM() + result, resolved_engine = await llm._retry_structured_with_high_tier_after_low_model_failure( + db=object(), + tenant_id=1, + function_name="classify_intent_ai_pmo_kernel", + failed_engine=dict(low_engine), + prompt="请返回结构化意图", + system_prompt="system prompt", + temperature=0.2, + max_tokens=512, + response_mode="complete", + error=ValueError("LLM 返回空内容"), + ) + + assert getattr(result, "payload", None) is not None + assert result.payload["confidence"] == pytest.approx(0.91) + assert result.model == "GLM-5" + assert isinstance(resolved_engine, dict) + assert resolved_engine["model"] == "GLM-5" + assert captured["chat_kwargs"]["model"] == "GLM-5" + log_text = "\n".join(message for _, message in captured["logs"]) + assert "GLM-5强制高阶升级命中" in log_text or "OpenClaw高阶升级命中" in log_text + assert "高阶结构化升级成功" in log_text diff --git a/backend/tests/test_ai_pmo_signal_source_onboard_contract.py b/backend/tests/test_ai_pmo_signal_source_onboard_contract.py new file mode 100644 index 00000000..ebf40b18 --- /dev/null +++ b/backend/tests/test_ai_pmo_signal_source_onboard_contract.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import pytest + +from app.services.ai_pmo.ai_pmo_typed_executor_execute.mixin_execute_signal_source import ( + ExecuteSignalSourceMixin, +) + + +def test_signal_source_onboard_idempotency_key_prefers_message_key() -> None: + key_one = ExecuteSignalSourceMixin._build_signal_source_onboard_idempotency_key( + source_system="feishu", + source_message_id="msg-123", + ) + key_two = ExecuteSignalSourceMixin._build_signal_source_onboard_idempotency_key( + source_system="feishu", + source_message_id="msg-123", + ) + bare_key = ExecuteSignalSourceMixin._build_signal_source_onboard_idempotency_key( + source_system="", + source_message_id="msg-123", + ) + + assert key_one == "feishu:msg-123" + assert key_two == "feishu:msg-123" + assert bare_key == "msg-123" + + +@pytest.mark.asyncio +async def test_signal_source_onboard_replays_existing_ledger_without_reexecution() -> None: + class _FakeLedger: + def __init__(self) -> None: + self.status = "applied" + self.result_payload = { + "status": "succeeded", + "signal_id": 88, + "project_id": 11, + "signal_source_workflow": { + "status": "completed", + "run_status": "completed", + }, + "signal_source_orchestration": { + "signal_source": { + "registry_id": 501, + "source_name": "Webhook A", + } + }, + "auto_tasks": [], + } + + class _FakeWorkflow: + def __init__(self) -> None: + self.called = False + + async def run(self, **kwargs): + self.called = True + raise AssertionError("signal_source_workflow.run should not be called on replay") + + class _FakeDB: + def __init__(self) -> None: + self.committed = False + + async def commit(self): + self.committed = True + + class _FakeExecutor(ExecuteSignalSourceMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + self.signal_source_workflow = _FakeWorkflow() + self.loaded_key = None + + async def _load_signal_source_onboard_ledger(self, *, idempotency_key): + self.loaded_key = idempotency_key + return _FakeLedger() + + async def _claim_signal_source_onboard_ledger(self, **kwargs): + raise AssertionError("ledger claim should not be reached when replaying an applied result") + + def _build_signal_source_semantic_payload(self, **kwargs): + raise AssertionError("semantic payload should not be rebuilt on replay") + + async def _create_signal(self, **kwargs): + raise AssertionError("signal creation should not be reached on replay") + + @staticmethod + def _normalize_title_candidate(value): + return str(value or "").strip() + + @staticmethod + def _is_acceptable_title(text): + return bool(text) + + @staticmethod + def _extract_auto_tasks(openclaw_payload): + return [] + + kernel = _FakeExecutor() + result = await kernel._execute_signal_source_onboard_intent( + intent=type("Intent", (), {"project_id": 11, "signal_summary": "摘要", "signal_title": "标题"})(), + message_text="请接入这个信号源", + operator_user_id=9, + source_system="feishu", + source_message_id="msg-abc", + channel_ref="oc-1", + ) + + assert result["status"] == "succeeded" + assert result["signal_id"] == 88 + assert kernel.loaded_key == "feishu:msg-abc" + assert kernel.db.committed is False + assert kernel.signal_source_workflow.called is False diff --git a/backend/tests/test_ai_prompt_registry_contract.py b/backend/tests/test_ai_prompt_registry_contract.py new file mode 100644 index 00000000..e143c7ef --- /dev/null +++ b/backend/tests/test_ai_prompt_registry_contract.py @@ -0,0 +1,58 @@ +from app.config.ai_prompt_registry import ( + PROMPT_REGISTRY_MAP, + get_prompt_registry, + get_prompt_registry_entry, + iter_prompt_registry_keys, +) +from app.config.ai_prompts_catalog import __all__ as PROMPT_CATALOG_EXPORTS + + +def test_prompt_registry_covers_all_catalog_exports() -> None: + catalog_keys = set(PROMPT_CATALOG_EXPORTS) + registry_keys = set(iter_prompt_registry_keys()) + + assert registry_keys == catalog_keys + assert set(PROMPT_REGISTRY_MAP) == catalog_keys + + for key in catalog_keys: + entry = get_prompt_registry_entry(key) + assert entry is not None + assert entry["prompt_key"] == key + + +def test_prompt_registry_returns_detached_copies() -> None: + registry = get_prompt_registry() + original_key = registry[0]["prompt_key"] + + registry[0]["prompt_key"] = "BROKEN_KEY" + + assert PROMPT_REGISTRY_MAP[original_key]["prompt_key"] == original_key + + +def test_high_frequency_registry_entries_have_complete_metadata() -> None: + required_keys = ( + "PROMPT_SIGNAL_PLANNER", + "PROMPT_GENERATE_CHARTER", + "PROMPT_DECOMPOSE_TASKS", + "PROMPT_PROJECT_STATUS_QA", + "PROMPT_MEMBER_WORKLOAD_QA", + "PROMPT_AI_PMO_QA", + "PROMPT_EXTERNAL_TREND_QA", + "PROMPT_INDUSTRY_TREND_QA", + ) + + for prompt_key in required_keys: + entry = get_prompt_registry_entry(prompt_key) + assert entry is not None + assert entry["module"] + assert entry["business_scenario"] + assert entry["input_contract"] + assert entry["output_contract"] + assert isinstance(entry["call_sites"], tuple) + assert entry["call_sites"], prompt_key + + signal_planner_entry = get_prompt_registry_entry("PROMPT_SIGNAL_PLANNER") + assert signal_planner_entry is not None + assert "charter_json" in signal_planner_entry["input_contract"] + assert any("mixin_prompt.py" in site for site in signal_planner_entry["call_sites"]) + assert any("mixin_analyze.py" in site for site in signal_planner_entry["call_sites"]) diff --git a/backend/tests/test_ai_service_core_runtime.py b/backend/tests/test_ai_service_core_runtime.py index ab969cd2..50cc39b1 100644 --- a/backend/tests/test_ai_service_core_runtime.py +++ b/backend/tests/test_ai_service_core_runtime.py @@ -1,7 +1,16 @@ import pytest +from pathlib import Path +import time +import httpx + +from app.api.llm_providers import _build_provider_payload +from app.core.startup import model_probe +from app.services.llm_service import mixin_chat_openai from app.services.ai_service_core import AIService +from app.services.llm_service import mixin_cache_circuit from app.services.llm_service import llm_service +from app.services.model.model_config import load_model_config @pytest.mark.asyncio @@ -67,3 +76,232 @@ async def test_stream_chat_yields_single_chunk_from_invoke(monkeypatch) -> None: chunks.append(chunk) assert chunks == ["stream-result"] + + +def test_model_config_bundle_centralizes_provider_and_tier_defaults() -> None: + bundle = load_model_config() + provider_defaults = bundle.get("provider_defaults") + tier_defaults = bundle.get("tier_defaults") + + assert provider_defaults["glm"]["model"] == "qwen-plus" + assert provider_defaults["openai"]["model"] == "MiniMax-M2.5" + assert tier_defaults["low"]["model"] == "qwen-plus" + assert tier_defaults["high"]["model"] == "GLM-5" + + +def test_provider_payload_reads_canonical_model_config(monkeypatch) -> None: + monkeypatch.setenv("DASHSCOPE_MODEL", "wrong-value") + monkeypatch.setenv("OPENAI_MODEL", "wrong-high-value") + + payload = _build_provider_payload() + + assert payload["low"] == "qwen-plus" + assert payload["high"] == "GLM-5" + assert payload["base_url"] == "https://api.edgefn.net/v1" + assert payload["providers"]["glm"]["model"] == "qwen-plus" + assert payload["providers"]["glm"]["base_url"] == "https://dashscope.aliyuncs.com/compatible-mode/v1" + assert payload["providers"]["openai"]["model"] == "MiniMax-M2.5" + + +@pytest.mark.asyncio +async def test_ai_service_runtime_prefers_canonical_config_over_env(monkeypatch) -> None: + service = AIService() + monkeypatch.setenv("OPENCLAW_ROUTING_ENABLED", "false") + monkeypatch.setenv("LLM_DIRECT_EMERGENCY_ENABLED", "true") + monkeypatch.setenv("LLM_PROVIDER", "glm") + monkeypatch.setenv("DASHSCOPE_MODEL", "wrong-value") + monkeypatch.setenv("DASHSCOPE_BASE_URL", "https://example.invalid") + + runtime = service._resolve_llm_runtime(api_key=None, model=None, base_url=None) + + assert runtime["model"] == "qwen-plus" + assert runtime["base_url"] == "https://dashscope.aliyuncs.com/compatible-mode/v1" + + +def test_openclaw_rule_keeps_existing_api_key_when_env_missing(monkeypatch) -> None: + monkeypatch.delenv("DASHSCOPE_API_KEY", raising=False) + + rule = { + "tier": "low", + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_env": "DASHSCOPE_API_KEY", + } + engines = [ + { + "id": "low-1", + "name": "Qwen 低阶引擎", + "provider": "glm", + "model": "GLM-4.7", + "api_key": "db-key", + "base_url": "https://api.edgefn.net/v1", + } + ] + active_engine = { + "id": "high-1", + "name": "OpenAI 高阶引擎", + "provider": "openai", + "model": "GLM-5", + "api_key": "high-key", + "base_url": "https://api.edgefn.net/v1", + } + + routed = llm_service._resolve_engine_from_rule( + rule=rule, + engines=engines, + active_engine=active_engine, + ) + + assert routed is not None + assert routed["provider"] == "glm" + assert routed["model"] == "qwen-plus" + assert routed["base_url"] == "https://dashscope.aliyuncs.com/compatible-mode/v1" + assert routed["api_key"] == "db-key" + + +def test_provider_circuit_state_clears_when_model_config_changes(monkeypatch, tmp_path) -> None: + circuit_key = "glm|https://dashscope.aliyuncs.com/compatible-mode/v1|qwen-plus" + original_state = dict(llm_service._provider_circuit_state) + original_signature = llm_service._provider_circuit_config_signature + try: + llm_service._provider_circuit_state[circuit_key] = { + "consecutive_failures": 2, + "open_until": time.monotonic() + 300.0, + "last_error": "stale", + "updated_at_monotonic": time.monotonic(), + } + llm_service._provider_circuit_config_signature = "stale-signature" + + config_path = tmp_path / "pmflow-routing-rules.json" + config_path.write_text("{}", encoding="utf-8") + monkeypatch.setattr( + mixin_cache_circuit, + "load_model_config", + lambda refresh=False: { + "config_path": str(config_path), + "version": "v9", + "updated_at": "2026-03-25T12:06:00+08:00", + }, + ) + + assert llm_service._is_provider_circuit_open(circuit_key) is False + assert circuit_key not in llm_service._provider_circuit_state + finally: + llm_service._provider_circuit_state.clear() + llm_service._provider_circuit_state.update(original_state) + llm_service._provider_circuit_config_signature = original_signature + + +@pytest.mark.asyncio +async def test_startup_model_probe_does_not_poison_provider_circuit(monkeypatch) -> None: + class DummySession: + async def __aenter__(self): + return object() + + async def __aexit__(self, exc_type, exc, tb): + return False + + original_state = dict(llm_service._provider_circuit_state) + original_signature = llm_service._provider_circuit_config_signature + try: + llm_service._provider_circuit_state["glm|https://dashscope.aliyuncs.com/compatible-mode/v1|qwen-plus"] = { + "consecutive_failures": 2, + "open_until": time.monotonic() + 300.0, + "last_error": "stale", + "updated_at_monotonic": time.monotonic(), + } + + async def fake_resolve_engine_for_call(db, *, tenant_id: int, function_name: str): + _ = (db, tenant_id) + if function_name == "classify_intent_ai_pmo_kernel": + return { + "provider": "glm", + "model": "qwen-plus", + "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "api_key": "db-key", + } + return { + "provider": "openai", + "model": "GLM-5", + "base_url": "https://api.edgefn.net/v1", + "api_key": "high-key", + } + + async def fake_chat_completion(*args, **kwargs): + raise ValueError("boom") + + monkeypatch.setattr(model_probe, "AsyncSessionLocal", lambda: DummySession()) + monkeypatch.setattr(llm_service, "_resolve_engine_for_call", fake_resolve_engine_for_call) + monkeypatch.setattr(llm_service, "_chat_completion", fake_chat_completion) + + await model_probe.probe_model_connectivity_on_startup() + + assert "glm|https://dashscope.aliyuncs.com/compatible-mode/v1|qwen-plus" in llm_service._provider_circuit_state + finally: + llm_service._provider_circuit_state.clear() + llm_service._provider_circuit_state.update(original_state) + llm_service._provider_circuit_config_signature = original_signature + + +@pytest.mark.asyncio +async def test_openai_compatible_chat_retries_once_on_connect_error(monkeypatch) -> None: + calls = {"count": 0} + + class FakeResponse: + status_code = 200 + + def json(self): + return { + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "ok", "reasoning_content": ""}, + "finish_reason": "stop", + } + ] + } + + class FakeClient: + def __init__(self, *args, **kwargs): + _ = (args, kwargs) + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + return False + + async def post(self, *args, **kwargs): + _ = (args, kwargs) + calls["count"] += 1 + if calls["count"] == 1: + raise httpx.ConnectError("boom", request=httpx.Request("POST", "https://example.com")) + return FakeResponse() + + async def fake_sleep(_seconds: float): + return None + + monkeypatch.setattr(mixin_chat_openai.httpx, "AsyncClient", FakeClient) + monkeypatch.setattr(mixin_chat_openai.asyncio, "sleep", fake_sleep) + + result = await llm_service._chat_completion_via_openai_compatible( + key="test-key", + base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", + model="qwen-plus", + temperature=0.0, + max_tokens=16, + messages=[{"role": "user", "content": "ping"}], + request_timeout=1.0, + structured_output=False, + function_label="unit_test_connect_retry", + provider_name="glm", + is_openclaw_gateway_request=False, + openclaw_agents_timeout_seconds=120, + request_started_at_perf=time.perf_counter(), + request_trace_id="trace-1", + openclaw_agent_timeout=None, + ) + + assert calls["count"] == 2 + assert result.text == "ok" diff --git a/backend/tests/test_draft_confirm_handler.py b/backend/tests/test_draft_confirm_handler.py index 24609902..26eaab1e 100644 --- a/backend/tests/test_draft_confirm_handler.py +++ b/backend/tests/test_draft_confirm_handler.py @@ -1,6 +1,17 @@ +import pytest + +from app.config.ai_prompts import ( + PROMPT_EXTERNAL_TREND_QA, + PROMPT_FEISHU_DIRECT_QA, + PROMPT_FEISHU_MIXED_QA, + PROMPT_PROJECT_STATUS_QA, +) from app.services.ai_pmo.draft_confirm_handler import DraftConfirmHandler from app.services.ai_pmo.ai_pmo_kernel_ingress_flow import mixin_auto from app.services.adaptive_rag_service.mixin_deep_guard_answer import AdaptiveRAGDeepGuardAnswerMixin +from app.services.integrations.feishu_integration_service.mixin_reply_llm_core import ( + FeishuReplyLLMCoreMixin, +) def test_parse_action_confirm() -> None: @@ -27,6 +38,96 @@ def test_parse_action_yes_as_confirm() -> None: assert note == "" +def test_draft_confirm_handler_thread_anchor_match() -> None: + ingest = type( + "Ingest", + (), + { + "payload": { + "thread_id": "th_1", + "root_message_id": "root_1", + "reply_to_message_id": "reply_1", + }, + "source_message_id": "src_1", + }, + )() + work_item = type("WorkItem", (), {"source_message_id": "wi_1"})() + + assert DraftConfirmHandler._matches_thread_anchor( + work_item=work_item, + ingest=ingest, + anchor_context={"reply_to_message_id": "reply_1"}, + ) is True + assert DraftConfirmHandler._matches_thread_anchor( + work_item=work_item, + ingest=ingest, + anchor_context={"reply_to_message_id": "missing"}, + ) is False + assert DraftConfirmHandler._normalize_thread_context( + {"thread_id": "th_1", "root_message_id": "root_1", "reply_to_message_id": "reply_1"} + ) == { + "thread_id": "th_1", + "root_message_id": "root_1", + "reply_to_message_id": "reply_1", + } + + +def test_draft_confirm_handler_thread_anchor_matches_draft_reply_message_id() -> None: + ingest = type( + "Ingest", + (), + { + "payload": { + "thread_id": "th_1", + "root_message_id": "draft_msg_1", + "reply_to_message_id": "draft_msg_1", + }, + "source_message_id": "original_msg_1", + }, + )() + work_item = type("WorkItem", (), {"source_message_id": "original_msg_1"})() + + assert DraftConfirmHandler._matches_thread_anchor( + work_item=work_item, + ingest=ingest, + anchor_context={"root_message_id": "draft_msg_1"}, + draft_reply_message_id="draft_msg_1", + ) is True + + +def test_reply_outbox_extract_sent_message_id() -> None: + from app.services.integrations.feishu_integration_service.mixin_reply_outbox import FeishuReplyOutboxMixin + + assert FeishuReplyOutboxMixin._extract_sent_message_id({"data": {"message_id": "msg_123"}}) == "msg_123" + assert FeishuReplyOutboxMixin._extract_sent_message_id({"message_id": "msg_456"}) == "msg_456" + + +@pytest.mark.asyncio +async def test_draft_confirm_handler_ignores_non_draft_reply_without_target(monkeypatch) -> None: + kernel = type("Kernel", (), {"db": object(), "tenant_id": 1})() + handler = DraftConfirmHandler(kernel) + + async def _fake_resolve_pending_draft(**kwargs): + _ = kwargs + return None + + monkeypatch.setattr(handler, "_resolve_pending_draft", _fake_resolve_pending_draft) + + result = await handler.handle_text_reply( + message_text="创建时间是多少?", + sender="林宇航", + source_system="feishu", + source_message_id="msg-1", + channel_ref="chat-1", + source_sender_open_id="ou_xxx", + thread_context={"reply_to_message_id": "reply-1"}, + ) + + assert result["handled"] is False + assert result["status"] == "ignored" + assert result["followup_reply"] == "" + + def test_kernel_ingress_mixin_auto_safe_int_regression() -> None: assert callable(mixin_auto._safe_int) assert mixin_auto._safe_int("123") == 123 @@ -88,3 +189,159 @@ def test_adaptive_rag_select_scene_prompt_ai_pmo_manual_entry() -> None: ) assert scene_key == "ai_pmo" assert "帮我总结这个项目" in prompt + + +def test_adaptive_rag_select_scene_prompt_prefers_decision_choice() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="如果 AI 助手第一次上线只允许一种提醒,你会把它做成桌面通知还是飞书消息?为什么?", + project_id=None, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text="无", + system_snapshot={}, + user_snapshot={}, + system_snapshot_text="{}", + user_snapshot_text="{}", + relevance_max=0.0, + ) + assert scene_key == "decision_choice" + assert "明确选择" in prompt + assert "飞书消息" in prompt + + +def test_adaptive_rag_select_scene_prompt_prefers_industry_trend_without_project_context() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="帮我看看宠物行业最新动态", + project_id=None, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text="无", + system_snapshot={"assistant_profile": {"name": "PMFlow"}}, + user_snapshot={"projects": [{"id": 1}]}, + system_snapshot_text="{}", + user_snapshot_text="{}", + relevance_max=0.0, + ) + assert scene_key == "industry_trend" + assert "当前未接入实时数据" in prompt + assert "project_context" not in prompt + + +def test_adaptive_rag_select_scene_prompt_filters_member_context_noise() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="杨宇现在负载情况如何?", + project_id=None, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text=( + "[1] 来源: project\n相关度: 0.991\n内容: Enterprise Knowledge Base 项目列表\n" + "[2] 来源: workload\n相关度: 0.884\n内容: 杨宇 当前负载 265.56% 超载\n" + ), + system_snapshot={"team_members": []}, + user_snapshot={"projects": [{"id": 1}]}, + system_snapshot_text="{\"team_members\": []}", + user_snapshot_text="{\"projects\": [{\"id\": 1}]}", + relevance_max=0.0, + ) + assert scene_key == "member_workload" + assert "Enterprise Knowledge Base 项目列表" not in prompt + assert "杨宇 当前负载 265.56% 超载" in prompt + assert '"projects"' not in prompt + + +def test_adaptive_rag_select_scene_prompt_ignores_negated_project_context_for_trend() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="帮我看看宠物行业最新动态,重点只说消费侧和产品侧的变化,不要提项目。", + project_id=7, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text="无", + system_snapshot={"assistant_profile": {"name": "PMFlow"}}, + user_snapshot={"projects": [{"id": 1}]}, + system_snapshot_text="{}", + user_snapshot_text="{}", + relevance_max=0.0, + ) + assert scene_key == "industry_trend" + assert "当前未接入实时数据" in prompt + assert "project_context" not in prompt + + +def test_adaptive_rag_select_scene_prompt_uses_project_trend_when_project_context_explicit() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="这个项目所在行业最新动态怎么样?", + project_id=7, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text="无", + system_snapshot={"assistant_profile": {"name": "PMFlow"}}, + user_snapshot={"projects": [{"id": 1}]}, + system_snapshot_text="{}", + user_snapshot_text="{}", + relevance_max=0.0, + ) + assert scene_key == "external_trend" + assert "项目上下文" in prompt + assert "外部搜索结果" in prompt + + +def test_adaptive_rag_select_scene_prompt_prefers_project_status_for_progress_questions() -> None: + mixin = AdaptiveRAGDeepGuardAnswerMixin() + scene_key, prompt = mixin._select_scene_prompt( + question="这个项目最新进展怎么样?", + project_id=7, + asked_via="feishu", + message_intent=None, + mixed_signal_part=None, + conversation_text="无", + context_blocks_text="无", + system_snapshot={"assistant_profile": {"name": "PMFlow"}}, + user_snapshot={"projects": [{"id": 1}]}, + system_snapshot_text="{}", + user_snapshot_text="{}", + relevance_max=0.0, + query_type="status", + ) + assert scene_key == "project_status" + assert "项目状态分析助手" in prompt + assert "完成率" in prompt + assert "当前阶段" in prompt + + +def test_feishu_qa_prompts_prefer_professional_answer_style() -> None: + assert "先给结论" in PROMPT_FEISHU_DIRECT_QA + assert "信息不足,缺少" in PROMPT_FEISHU_DIRECT_QA + assert "适合飞书消息阅读" in PROMPT_FEISHU_DIRECT_QA + assert "先回答问题部分" in PROMPT_FEISHU_MIXED_QA + assert "[已收到需求]" in PROMPT_FEISHU_MIXED_QA + assert "专业" in PROMPT_FEISHU_MIXED_QA + assert "XXXX年" not in PROMPT_EXTERNAL_TREND_QA + assert "通用行业认知" in PROMPT_EXTERNAL_TREND_QA + assert "项目状态分析助手" in PROMPT_PROJECT_STATUS_QA + + +def test_feishu_reply_llm_fallback_is_failure_oriented() -> None: + reply_text = FeishuReplyLLMCoreMixin._fallback_reply_text( + { + "status": "captured", + "classification": "project", + } + ) + assert "当前回复" in reply_text + assert "已收到需求并完成初步识别" not in reply_text + assert "已入库处理" not in reply_text diff --git a/backend/tests/test_draft_generator_service.py b/backend/tests/test_draft_generator_service.py new file mode 100644 index 00000000..36847ebf --- /dev/null +++ b/backend/tests/test_draft_generator_service.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import json +from types import SimpleNamespace + +import pytest + +from app.config.ai_prompts import build_default_role_prompt_bundle +from app.services.ai_service_core import AIService +from app.services.sstc import charter_prefill_utils +from app.services.signal.draft_generator_service import DraftGeneratorService + + +def test_default_role_prompt_bundle_is_pm_reviewer() -> None: + bundle = build_default_role_prompt_bundle() + assert bundle["active_role_key"] == "pm_dispatcher" + role = next((item for item in bundle["roles"] if item.get("role_key") == "pm_dispatcher"), {}) + assert "立项评审" in str(role.get("role_name") or "") + assert "同名或相似项目" in str(role.get("system_prompt") or "") + assert "MVP" in str(role.get("system_prompt") or "") + + +@pytest.mark.asyncio +async def test_generate_and_render_draft_includes_pm_sections(monkeypatch) -> None: + service = DraftGeneratorService(db=object(), tenant_id=1) + captured: dict[str, str] = {} + + async def fake_load_members(*, world_model): + _ = world_model + return [ + {"id": 7, "name": "Alice", "workload_percent": 25.0}, + {"id": 8, "name": "Bob", "workload_percent": 60.0}, + ] + + async def fake_generate_draft_payload(*, prompt): + captured["prompt"] = prompt + return {} + + monkeypatch.setattr(service, "_load_members", fake_load_members) + monkeypatch.setattr(service, "_generate_draft_payload", fake_generate_draft_payload) + + work_item = SimpleNamespace( + draft_version=0, + draft_content={}, + draft_expires_at=None, + confirm_message_id=None, + execution_result={}, + ) + world_model = { + "project": {"title": "企业知识库问答平台", "description": "面向内部知识库的问答能力"}, + "active_projects": [ + { + "id": 51, + "name": "AI劝退助手", + "title": "AI劝退助手", + "description": "项目进展与风险提醒", + "status": "active", + "owner_id": 10, + }, + { + "id": 52, + "name": "企业知识库问答平台 V2", + "title": "企业知识库问答平台 V2", + "description": "旧版知识库问答平台", + "status": "active", + "owner_id": 11, + }, + ], + "recent_signals": [ + {"id": 91, "title": "文档检索性能优化", "signal_type": "新需求", "status": "open"}, + ], + "capacity": [], + } + + generated = await service.generate_and_persist( + work_item=work_item, + intent_name="project.create", + message_text="我想做一个企业知识库问答项目,先帮我评估一下。", + world_model=world_model, + ) + + prompt = captured["prompt"] + assert "现有活跃项目(JSON)" in prompt + assert "候选相似项目(JSON)" in prompt + assert "同名/相似项目列表" in prompt + assert generated["draft"]["similar_projects"] + assert "🔎 同名 / 相似项目:" in generated["draft_text"] + assert "🚀 MVP 建议:" in generated["draft_text"] + assert "📈 前景判断:" in generated["draft_text"] + assert "🧩 需求清单:" in generated["draft_text"] + assert "❓ 还缺的信息:" in generated["draft_text"] + assert generated["draft"]["recommended_assignee_name"] == "Alice" + assert generated["draft"]["recommended_assignee_workload"] == 25.0 + + +@pytest.mark.asyncio +async def test_generate_charter_seed_uses_project_context(monkeypatch) -> None: + captured: dict[str, str] = {} + + async def fake_load_project_context(*, db, tenant_id, project_id): + _ = db, tenant_id, project_id + return { + "id": 7, + "name": "kb-v2", + "title": "企业知识库问答平台", + "description": "内部知识库问答升级", + "status": "active", + "owner_id": 10, + } + + async def fake_load_active_projects_context(*, db, tenant_id, project_id, limit=10): + _ = db, tenant_id, project_id, limit + return [ + { + "id": 51, + "name": "ai-warn", + "title": "AI劝退助手", + "description": "项目进展与风险提醒", + "status": "active", + "owner_id": 11, + } + ] + + async def fake_generate(prompt, **kwargs): + captured["prompt"] = prompt + return json.dumps( + { + "title": "内部知识库问答升级", + "summary": "围绕内部知识库问答能力升级", + "background": "背景说明", + "objectives": ["提升检索质量"], + "scope": "范围说明", + "success_metrics": ["命中率提升"], + "risks": ["重复建设风险"], + "dependencies": ["知识库索引"], + "primary_model": "gpt", + "fallback_model": "gpt-mini", + }, + ensure_ascii=False, + ) + + monkeypatch.setattr(charter_prefill_utils, "_load_charter_project_context", fake_load_project_context) + monkeypatch.setattr(charter_prefill_utils, "_load_active_projects_context", fake_load_active_projects_context) + monkeypatch.setattr(charter_prefill_utils.llm_service, "generate", fake_generate) + + result = await charter_prefill_utils._generate_charter_seed( + db=object(), + tenant_id=1, + project_id=7, + signal_context="AI 劝退诉求:请升级内部知识库问答能力。", + ) + + prompt = captured["prompt"] + assert "当前项目上下文" in prompt + assert "企业知识库问答平台" in prompt + assert "现有活跃项目(JSON)" in prompt + assert "AI劝退助手" in prompt + assert result["summary"] == "围绕内部知识库问答能力升级" + + +@pytest.mark.asyncio +async def test_generate_charter_from_signal_includes_project_context(monkeypatch) -> None: + service = AIService() + captured: dict[str, object] = {} + + async def fake_invoke(self, prompt, **kwargs): + _ = self + captured["prompt"] = prompt + captured["kwargs"] = kwargs + return json.dumps( + { + "title": "内部知识库问答升级", + "summary": "围绕内部知识库问答能力升级", + "background": "背景说明", + "objectives": ["提升检索质量"], + "scope": "范围说明", + "success_metrics": ["命中率提升"], + "risks": ["重复建设风险"], + "dependencies": ["知识库索引"], + "primary_model": "gpt", + "fallback_model": "gpt-mini", + }, + ensure_ascii=False, + ) + + monkeypatch.setattr(AIService, "_invoke_with_retry", fake_invoke) + + result = await service.generate_charter_from_signal( + "AI 劝退诉求:请升级内部知识库问答能力。", + tenant_id=1, + db=object(), + project_id=7, + project_context="当前项目上下文:企业知识库问答平台", + active_projects_json='[{"id":51,"title":"AI劝退助手"}]', + ) + + prompt = str(captured["prompt"] or "") + kwargs = captured["kwargs"] if isinstance(captured.get("kwargs"), dict) else {} + assert "当前项目上下文:企业知识库问答平台" in prompt + assert "现有活跃项目(JSON)" in prompt + assert kwargs.get("project_id") == 7 + assert result["summary"] == "围绕内部知识库问答能力升级" diff --git a/backend/tests/test_feishu_card_action_contract.py b/backend/tests/test_feishu_card_action_contract.py index 24a16166..f6c16495 100644 --- a/backend/tests/test_feishu_card_action_contract.py +++ b/backend/tests/test_feishu_card_action_contract.py @@ -99,6 +99,26 @@ def test_build_action_card_for_pending_manual_continue() -> None: assert (actions[0].get("value") or {}).get("work_item_id") == 99 +def test_build_action_card_for_queue_pending_confirm() -> None: + card = FeishuCardActionMixin._build_action_card_from_capture_result( + capture_result={ + "reply_template_key": "queue_pending_confirm", + "results": [{"item_id": 201, "route_status": "queued_for_decision"}], + "direct_reply_text": "需要你确认后继续执行。", + }, + mention_open_id=None, + mention_name=None, + ) + assert isinstance(card, dict) + assert card["header"]["title"]["content"] == "待确认动作" + action_row = next((item for item in card["elements"] if item.get("tag") == "action"), {}) + actions = action_row.get("actions") if isinstance(action_row.get("actions"), list) else [] + action_names = {((btn.get("value") or {}).get("action")) for btn in actions if isinstance(btn, dict)} + assert {"continue", "cancel"} <= action_names + queue_ids = {((btn.get("value") or {}).get("queue_item_id")) for btn in actions if isinstance(btn, dict)} + assert queue_ids == {201} + + @pytest.mark.asyncio async def test_handle_card_action_trigger_continue_dispatches_resume(monkeypatch) -> None: called: dict[str, object] = {} @@ -154,3 +174,61 @@ async def test_handle_card_action_trigger_continue_dispatches_resume(monkeypatch assert ingress_kwargs["source_system"] == "ai_pmo_ui" assert called["reply_text"] == "已继续执行。" assert called["reply_chat"] == "oc_continue_1" + + +@pytest.mark.asyncio +async def test_handle_card_action_trigger_queue_continue_dispatches_pipeline(monkeypatch) -> None: + called: dict[str, object] = {} + + class DummyDB: + async def commit(self) -> None: + called["committed"] = True + + async def rollback(self) -> None: + called["rolled_back"] = True + + service = FeishuIntegrationService(db=DummyDB()) + service.tenant_id = 7 + + async def _fake_reply_text(**kwargs): + called["reply_text"] = kwargs.get("text") + called["reply_chat"] = kwargs.get("chat_id") + + monkeypatch.setattr(service, "_safe_reply_text", _fake_reply_text) + + class _FakeOperator: + id = 77 + + class _FakePipeline: + def __init__(self, db, tenant_id): + called["pipeline_tenant_id"] = tenant_id + + async def _resolve_command_operator_user(self, *, sender): + called["operator_sender"] = sender + return _FakeOperator() + + async def confirm_item(self, *, item_id, user_id): + called["confirm_item"] = {"item_id": item_id, "user_id": user_id} + return {"project_id": "18", "source_registry_id": "501"} + + monkeypatch.setattr( + "app.services.integrations.feishu_signal_pipeline.FeishuSignalPipelineService", + _FakePipeline, + ) + + result = await service._handle_card_action_trigger( + { + "open_message_id": "om_queue_1", + "open_chat_id": "oc_queue_1", + "operator": {"open_id": "ou_queue", "name": "队列用户"}, + "action": {"value": {"action": "continue", "queue_item_id": 201}}, + } + ) + + assert result["code"] == 0 + data = result.get("data") if isinstance(result.get("data"), dict) else {} + assert data.get("handled") is True + assert data.get("queue_item_id") == 201 + assert called["confirm_item"] == {"item_id": 201, "user_id": 77} + assert called["reply_text"] == "已继续处理:信号源注册#501 已接入项目#18。" + assert called["reply_chat"] == "oc_queue_1" diff --git a/backend/tests/test_feishu_long_connection_tls_contract.py b/backend/tests/test_feishu_long_connection_tls_contract.py new file mode 100644 index 00000000..bb1939cb --- /dev/null +++ b/backend/tests/test_feishu_long_connection_tls_contract.py @@ -0,0 +1,264 @@ +import asyncio +import os + +import certifi +import pytest + +from app.services.integrations.feishu_integration_service.mixin_config import FeishuConfigMixin +from app.services.integrations.feishu_long_connection_core.mixin_lifecycle_connection import ( + LongConnectionLifecycleConnectionMixin, +) +from app.services.integrations.feishu_long_connection_core.mixin_lifecycle_control import ( + LongConnectionLifecycleControlMixin, +) +from app.services.integrations.feishu_long_connection_core.mixin_runtime import ( + LongConnectionRuntimeMixin, +) +from app.services.integrations.feishu_long_connection_core.shared import ( + ensure_feishu_tls_trust_bundle, +) + + +@pytest.fixture(autouse=True) +def _clear_tls_env(monkeypatch: pytest.MonkeyPatch) -> None: + for key in ( + "FEISHU_LONG_CONNECTION_SSL_CERT_FILE", + "SSL_CERT_FILE", + "REQUESTS_CA_BUNDLE", + "CURL_CA_BUNDLE", + ): + monkeypatch.delenv(key, raising=False) + + +def test_ensure_feishu_tls_trust_bundle_defaults_to_certifi(monkeypatch: pytest.MonkeyPatch) -> None: + bundle = ensure_feishu_tls_trust_bundle() + + assert bundle["bundle_path"] == certifi.where() + assert bundle["source"] == "certifi" + assert os.environ["SSL_CERT_FILE"] == certifi.where() + assert os.environ["REQUESTS_CA_BUNDLE"] == certifi.where() + assert os.environ["CURL_CA_BUNDLE"] == certifi.where() + + +def test_ensure_feishu_tls_trust_bundle_honors_explicit_override(monkeypatch: pytest.MonkeyPatch) -> None: + override_path = "/tmp/custom-feishu-ca.pem" + monkeypatch.setenv("FEISHU_LONG_CONNECTION_SSL_CERT_FILE", override_path) + + bundle = ensure_feishu_tls_trust_bundle() + + assert bundle["bundle_path"] == override_path + assert bundle["source"] == "FEISHU_LONG_CONNECTION_SSL_CERT_FILE" + assert os.environ["SSL_CERT_FILE"] == override_path + assert os.environ["REQUESTS_CA_BUNDLE"] == override_path + assert os.environ["CURL_CA_BUNDLE"] == override_path + + +class _DummyLongConnectionManager(LongConnectionLifecycleConnectionMixin): + def __init__(self) -> None: + self._boot_lock = asyncio.Lock() + self._queue = asyncio.Queue() + self._running = True + self._thread = None + self._client = None + self._sdk_available = True + self._configured = False + self._event_mode = "long_connection" + self._tenant_id = 1 + self._last_error = None + self._last_error_code = None + self._last_error_retryable = False + self._last_received_at = None + self._last_received_message_id = None + self._last_event_at = None + self._last_auth_at = None + self._last_connect_at = None + self._last_disconnect_at = None + self._restart_attempts = 0 + self._next_retry_at = 0.0 + self._stopping = False + self._max_retry_attempts = 3 + self._retry_base_seconds = 1.0 + + async def _load_active_config(self) -> dict[str, object] | None: + return { + "tenant_id": 1, + "app_id": "app-id", + "app_secret": "app-secret", + "encrypt_key": "encrypt-key", + "verification_token": "verification-token", + } + + async def _preflight_auth(self, *, tenant_id: int) -> None: # noqa: ARG002 + return None + + def _start_ws_thread( + self, + app_id: str, + app_secret: str, + *, + encrypt_key: str = "", + verification_token: str = "", + ) -> None: + self.started_with = (app_id, app_secret, encrypt_key, verification_token) + + +class _FakeResult: + def __init__(self, value) -> None: + self._value = value + + def scalar_one_or_none(self): + return self._value + + +class _FakeDB: + def __init__(self, integration) -> None: + self._integration = integration + self.committed = False + + async def execute(self, query): # noqa: ARG002 + return _FakeResult(self._integration) + + async def commit(self) -> None: + self.committed = True + + +@pytest.mark.asyncio +async def test_feishu_config_load_preserves_event_credentials() -> None: + integration = type( + "IntegrationRow", + (), + { + "config": { + "app_id": "app-id", + "app_secret": "app-secret", + "event_mode": "long_connection", + "encrypt_key": "encrypt-key", + "verification_token": "verification-token", + }, + "tenant_id": 1, + }, + )() + + service = FeishuConfigMixin(_FakeDB(integration)) + + await service.load_config(tenant_id=1) + + assert service.app_id == "app-id" + assert service.app_secret == "app-secret" + assert service.encrypt_key == "encrypt-key" + assert service.verification_token == "verification-token" + assert service.event_mode == "long_connection" + + +@pytest.mark.asyncio +async def test_feishu_config_save_preserves_existing_event_credentials() -> None: + integration = type( + "IntegrationRow", + (), + { + "config": { + "app_id": "old-app", + "app_secret": "old-secret", + "event_mode": "long_connection", + "encrypt_key": "existing-encrypt-key", + "verification_token": "existing-verification-token", + }, + "tenant_id": 1, + "is_active": True, + }, + )() + + service = FeishuConfigMixin(_FakeDB(integration)) + + await service.save_config( + app_id="new-app", + app_secret="new-secret", + encrypt_key="", + verification_token="", + event_mode="long_connection", + tenant_id=1, + ) + + assert integration.config["app_id"] == "new-app" + assert integration.config["app_secret"] == "new-secret" + assert integration.config["encrypt_key"] == "existing-encrypt-key" + assert integration.config["verification_token"] == "existing-verification-token" + assert service.encrypt_key == "existing-encrypt-key" + assert service.verification_token == "existing-verification-token" + assert service.app_id == "new-app" + assert service.app_secret == "new-secret" + assert service.event_mode == "long_connection" + assert service.db.committed is True + + +@pytest.mark.asyncio +async def test_ensure_connection_bootstraps_tls_bundle_before_ws_start() -> None: + manager = _DummyLongConnectionManager() + + started = await manager._ensure_connection(reason="startup") + + assert started is True + assert manager.started_with == ( + "app-id", + "app-secret", + "encrypt-key", + "verification-token", + ) + assert os.environ["SSL_CERT_FILE"] == certifi.where() + assert os.environ["REQUESTS_CA_BUNDLE"] == certifi.where() + assert os.environ["CURL_CA_BUNDLE"] == certifi.where() + + +@pytest.mark.asyncio +async def test_long_connection_status_tracks_received_and_processed_times() -> None: + class _RuntimeDummy(LongConnectionRuntimeMixin): + def __init__(self) -> None: + self._running = True + self._loop = asyncio.get_running_loop() + self._queue = asyncio.Queue() + self._tenant_id = 1 + self._last_received_at = None + self._last_received_message_id = None + self._last_event_at = None + + manager = _RuntimeDummy() + manager._loop = asyncio.get_running_loop() + + manager._on_message_event( + { + "header": {"event_type": "im.message.receive_v1"}, + "event": {"message": {"message_id": "msg-123"}}, + } + ) + await asyncio.sleep(0) + + assert manager._last_received_message_id == "msg-123" + assert manager._last_received_at is not None + assert manager._queue.qsize() == 1 + + lifecycle_manager = type("StatusOnly", (), {})() + lifecycle_manager._running = True + lifecycle_manager._tenant_id = 1 + lifecycle_manager._event_mode = "long_connection" + lifecycle_manager._sdk_available = True + lifecycle_manager._configured = True + lifecycle_manager._worker_task = None + lifecycle_manager._supervisor_task = None + lifecycle_manager._thread = None + lifecycle_manager._last_error = None + lifecycle_manager._last_error_code = None + lifecycle_manager._last_error_retryable = False + lifecycle_manager._last_received_at = manager._last_received_at + lifecycle_manager._last_received_message_id = manager._last_received_message_id + lifecycle_manager._last_event_at = None + lifecycle_manager._last_auth_at = None + lifecycle_manager._last_connect_at = None + lifecycle_manager._last_disconnect_at = None + lifecycle_manager._restart_attempts = 0 + lifecycle_manager._next_retry_at = 0.0 + lifecycle_manager._max_retry_attempts = 3 + lifecycle_manager._retry_base_seconds = 1.0 + status = LongConnectionLifecycleControlMixin.status(lifecycle_manager) + assert status["last_received_message_id"] == "msg-123" + assert status["last_received_at"] == manager._last_received_at + assert status["last_event_at"] is None diff --git a/backend/tests/test_feishu_pipeline_queue_contract.py b/backend/tests/test_feishu_pipeline_queue_contract.py new file mode 100644 index 00000000..829d2fc6 --- /dev/null +++ b/backend/tests/test_feishu_pipeline_queue_contract.py @@ -0,0 +1,736 @@ +from types import SimpleNamespace + +import pytest + +from app.services.integrations.feishu_signal_pipeline.mixin_gate_project import ( + FeishuPipelineGateProjectMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_command_commit_project import ( + FeishuPipelineCommandCommitProjectMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_command_commit_task import ( + FeishuPipelineCommandCommitTaskMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_context_prefill_b import ( + FeishuPipelineContextPrefillBMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_extraction import ( + FeishuPipelineExtractionMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_item_processing import ( + FeishuPipelineItemProcessingMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_followup_a import ( + FeishuPipelineFollowupAMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_followup_b import FeishuPipelineFollowupBMixin +from app.services.integrations.feishu_signal_pipeline.mixin_gate_dispatch import ( + FeishuPipelineGateDispatchMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_item_finalize_routing import ( + FeishuPipelineItemFinalizeRoutingMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_message_utils_b import ( + FeishuPipelineMessageUtilsBMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_process_message_helpers_b import ( + FeishuPipelineProcessMessageHelpersBMixin, +) +from app.services.integrations.feishu_signal_pipeline.mixin_queue_confirm_undo import ( + FeishuPipelineQueueConfirmUndoMixin, +) +from app.services.signal.signal_capture_service.mixin_task_orchestration_main import ( + SignalCaptureTaskOrchestrationMainMixin, +) +from app.services.signal.signal_capture_service.mixin_task_orchestration_skill import ( + SignalCaptureTaskOrchestrationSkillMixin, +) + + +class _Item: + def __init__(self, *, item_id: int, level: str, operation_code: str, raw_message: str = "") -> None: + self.id = item_id + self.level = level + self.card_type = operation_code.replace(".", "_") + self.raw_message = raw_message + self.result_payload = {"operation_code": operation_code} + self.feishu_message_id = f"msg-{item_id}" + + +def test_followup_status_target_requires_unique_match() -> None: + items = [ + _Item(item_id=1, level="command", operation_code="project.create"), + _Item(item_id=2, level="command", operation_code="project.create"), + ] + + target = FeishuPipelineFollowupBMixin._select_followup_status_target( + message="刚才创建项目现在怎么样了", + recent_items=items, + ) + + assert target is None + + +def test_followup_status_target_rejects_single_item_without_anchor() -> None: + items = [ + _Item(item_id=1, level="project", operation_code="project.create"), + ] + + target = FeishuPipelineFollowupBMixin._select_followup_status_target( + message="它现在怎么样?", + recent_items=items, + ) + + assert target is None + + +def test_followup_status_target_prefers_thread_anchor() -> None: + items = [ + _Item(item_id=1, level="project", operation_code="project.create"), + _Item(item_id=2, level="project", operation_code="project.create"), + ] + items[0].feishu_message_id = "root-1" + items[1].feishu_message_id = "root-2" + + target = FeishuPipelineFollowupBMixin._select_followup_status_target( + message="他是什么时候创建的?", + recent_items=items, + thread_context={"root_message_id": "root-1"}, + ) + + assert target is items[0] + + +def test_followup_status_target_rejects_unmatched_thread_anchor() -> None: + items = [ + _Item(item_id=1, level="project", operation_code="project.create"), + _Item(item_id=2, level="project", operation_code="project.create"), + ] + items[0].feishu_message_id = "root-1" + items[1].feishu_message_id = "root-2" + + target = FeishuPipelineFollowupBMixin._select_followup_status_target( + message="他是什么时候创建的?", + recent_items=items, + thread_context={"reply_to_message_id": "reply-miss"}, + ) + + assert target is None + + +@pytest.mark.asyncio +async def test_followup_status_reply_reports_project_created_at(monkeypatch) -> None: + from datetime import datetime + + class _FakeDBResult: + def __init__(self, value) -> None: + self._value = value + + def scalar_one_or_none(self): + return self._value + + class _FakeDB: + async def execute(self, query): + _ = query + return _FakeDBResult(type("ProjectRow", (), {"id": 7, "tenant_id": 1, "title": "自动开票系统", "name": "自动开票系统", "created_at": datetime(2026, 3, 1, 9, 30)})()) + + class _FakeFollowup(FeishuPipelineFollowupBMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + + async def _get_project_suggestion(self, item_id: int): + _ = item_id + return type("Suggestion", (), {"created_project_id": 7})() + + @staticmethod + def _normalize_route_status(value): + return str(value) + + item = _Item(item_id=1, level="project", operation_code="project.create") + item.route_status = "auto_done" + item.matched_project_id = 7 + + reply = await _FakeFollowup()._build_followup_status_reply(item, message="他是什么时候创建的?") + + assert "创建于 2026-03-01 09:30" in reply + + +@pytest.mark.asyncio +async def test_followup_queue_handler_ignores_unmatched_thread_anchor() -> None: + class _FakeItem: + def __init__(self) -> None: + self.id = 1 + self.route_status = "queued" + self.level = "project" + self.feishu_message_id = "queue-root-1" + self.blocking_reason = "请确认" + + class _FakeFollowup(FeishuPipelineFollowupAMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + @staticmethod + def _looks_like_continue_followup(message: str) -> bool: + _ = message + return False + + async def _load_recent_chat_items(self, *, source_chat_id, source_sender_open_id, sender, limit=5): + _ = (source_chat_id, source_sender_open_id, sender, limit) + return [_FakeItem()] + + result = await _FakeFollowup()._maybe_handle_queue_followup( + message="他是什么时候创建的?", + sender="测试用户", + source_chat_id="chat-1", + source_sender_open_id="ou-1", + feishu_message_id="msg-1", + process_started_at=0.0, + thread_context={"reply_to_message_id": "reply-miss"}, + ) + + assert result is None + + +def test_decorate_capture_result_adds_queue_action_card_metadata() -> None: + result = FeishuPipelineProcessMessageHelpersBMixin._decorate_capture_result( + { + "status": "captured", + "results": [{"item_id": 301, "route_status": "queued_for_decision"}], + "direct_reply_text": "已转待确认。", + }, + chat_type="group", + normalized_text="请创建项目", + route="group.intent_gate.project_create", + db_mutations=[], + ) + + assert result["reply_template_key"] == "queue_pending_confirm" + assert result["queue_item_id"] == 301 + + +@pytest.mark.asyncio +async def test_intent_gate_query_does_not_inherit_project_context(monkeypatch) -> None: + captured: dict[str, object] = {} + + class _FakeAdaptiveRAGService: + def __init__(self, db, tenant_id: int): + captured["tenant_id"] = tenant_id + _ = db + + async def classify_intent(self, text: str): + captured["classified_text"] = text + return {"intent": "question", "question_part": text} + + async def answer_question(self, **kwargs): + captured["answer_kwargs"] = kwargs + return {"answer": "AI 行业动态正常", "sources": []} + + class _FakeGate(FeishuPipelineGateDispatchMixin, FeishuPipelineProcessMessageHelpersBMixin): + def __init__(self) -> None: + class _FakeDB: + async def commit(self) -> None: + return None + + self.db = _FakeDB() + self.tenant_id = 1 + + async def _write_processing_log(self, **kwargs): + captured["write_log"] = kwargs + + def _build_rag_conversation_key(self, **kwargs): + _ = kwargs + return "feishu:chat-1" + + def _build_rag_reply_text(self, *, answer: str, sources: list[dict[str, object]]) -> str: + _ = sources + return answer + + monkeypatch.setattr( + "app.services.integrations.feishu_signal_pipeline.mixin_gate_dispatch.AdaptiveRAGService", + _FakeAdaptiveRAGService, + ) + + result = await _FakeGate()._maybe_handle_intent_gate_capture( + intent_gate={"intent": "query"}, + message="最近AI行业有什么动态", + sender="测试用户", + source_chat_id="chat-1", + source_sender_open_id="ou-1", + feishu_message_id="msg-1", + captured_at=None, + inferred_project_id=6, + projects=[], + process_started_at=0.0, + normalized_chat_type="group", + thread_context={"reply_to_message_id": "msg-root-1"}, + ) + + assert isinstance(result, dict) + assert result["linked_project_ids"] == [] + assert captured["answer_kwargs"]["project_id"] is None + + +@pytest.mark.asyncio +async def test_project_context_prefill_skips_chatwide_reply_fallback(monkeypatch) -> None: + called = {"fallback": False} + + class _FakePrefill(FeishuPipelineContextPrefillBMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + @staticmethod + def _looks_like_contextual_project_request(message: str) -> bool: + return True + + async def _load_recent_chat_turns(self, **kwargs): + _ = kwargs + return [] + + async def _load_recent_chat_reply_fallback_text(self, *, source_chat_id: str, limit: int = 5) -> str: + _ = (source_chat_id, limit) + called["fallback"] = True + raise AssertionError("reply fallback should not be used when no recent turns are available") + + fields = { + "title": "", + "description": "", + "project_scope": "", + "missing_fields": ["title", "description", "project_scope"], + "ready": False, + } + result = await _FakePrefill()._maybe_prefill_project_fields_from_context( + message="根据这个创建一个项目", + source_chat_id="chat-1", + source_sender_open_id="ou-1", + sender="测试用户", + fields=fields, + source_message_id="msg-1", + thread_context={"reply_to_message_id": "msg-root-1"}, + ) + + assert result == fields + assert called["fallback"] is False + + +@pytest.mark.asyncio +async def test_project_prefill_fallback_leaves_owner_and_timeline_empty() -> None: + class _FakeLLM: + async def generate(self, *args, **kwargs): + _ = args, kwargs + raise RuntimeError("boom") + + class _FakeExtraction(FeishuPipelineExtractionMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + self.llm = _FakeLLM() + + result = await _FakeExtraction()._extract_project_prefill( + "我们想做一个内部知识库升级项目,先把项目名称和范围整理出来。" + ) + + assert result["suggested_owner"] is None + assert result["suggested_timeline"] is None + assert float(result["confidence"]) <= 0.35 + assert len(result["reasoning"]) >= 1 + + +@pytest.mark.asyncio +async def test_create_project_suggestion_preserves_uncertain_owner_and_timeline() -> None: + class _FakeDB: + def __init__(self) -> None: + self.added = [] + + def add(self, obj): + self.added.append(obj) + + async def flush(self): + return None + + class _FakeProcessing(FeishuPipelineItemProcessingMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + + async def _extract_project_prefill(self, message: str): + _ = message + return { + "suggested_name": "内部知识库升级", + "suggested_description": "围绕内部知识库问答能力升级", + "suggested_owner": None, + "suggested_timeline": None, + "confidence": 0.31, + "reasoning": ["需求里没有明确负责人", "需求里没有明确时间线"], + } + + project_info, suggestion = await _FakeProcessing()._create_project_suggestion( + item_id=12, + content="我们想做一个内部知识库升级项目,先把项目名称和范围整理出来。", + sender="测试用户", + fallback_confidence=0.31, + ) + + assert project_info["suggested_owner"] is None + assert project_info["suggested_timeline"] is None + assert suggestion.suggested_owner is None + assert suggestion.suggested_timeline is None + + +def test_project_create_idempotency_key_prefers_message_key() -> None: + key_one = FeishuPipelineCommandCommitProjectMixin._build_project_create_idempotency_key( + idempotency_key="feishu:sm-123", + trace_id="trace-1", + ) + key_two = FeishuPipelineCommandCommitProjectMixin._build_project_create_idempotency_key( + idempotency_key="feishu:sm-123", + trace_id="trace-2", + ) + fallback_key = FeishuPipelineCommandCommitProjectMixin._build_project_create_idempotency_key( + idempotency_key=None, + trace_id="trace-3", + ) + + assert key_one == "feishu:sm-123" + assert key_two == "feishu:sm-123" + assert fallback_key == "trace-3" + + +def test_task_orchestration_charter_context_flattens_modules() -> None: + charter = SimpleNamespace( + id=55, + title="内部知识库升级宪章", + version=2, + status="aligning", + content={ + "modules": { + "module_1": { + "problem_statement": "减少重复检索", + "goal_statement": "提升命中率 / 降低重复提问", + "kpis": "首响 < 3s", + }, + "module_2": { + "in_scope": "问答检索,知识补充", + "out_of_scope": "权限模型重构", + }, + "module_4": { + "milestones": "需求冻结\n开发完成", + }, + "module_5": { + "risk_register": "需求膨胀; 依赖不清", + }, + "module_6": { + "governance_rule": "附件仅作证据引用", + }, + } + }, + ) + + context = SignalCaptureTaskOrchestrationMainMixin._build_task_orchestration_charter_context(charter) + + assert context["title"] == "内部知识库升级宪章" + assert context["summary"] == "减少重复检索" + assert context["objectives"] == ["提升命中率", "降低重复提问"] + assert context["in_scope"] == ["问答检索", "知识补充"] + assert context["out_of_scope"] == ["权限模型重构"] + assert context["milestones"] == ["需求冻结", "开发完成"] + assert "附件仅作证据引用" in context["constraints"][0] + + +def test_task_orchestration_prompt_embeds_charter_context() -> None: + prompt = SignalCaptureTaskOrchestrationMainMixin._build_task_orchestration_prompt( + project_payload={ + "id": 6, + "title": "内部知识库升级", + "description": "围绕内部知识库问答能力升级", + "assigned_role": "backend", + "status": "active", + }, + charter_payload={ + "id": 55, + "title": "内部知识库升级宪章", + "version": 2, + "status": "aligning", + "summary": "减少重复检索", + "objectives": ["提升命中率", "降低重复提问"], + "scope": "知识库检索与问答", + "in_scope": ["问答检索", "知识补充"], + "out_of_scope": ["权限模型重构"], + "success_criteria": ["首响 < 3s"], + "constraints": ["附件仅作证据引用"], + "milestones": ["需求冻结", "开发完成"], + "risks": ["需求膨胀"], + }, + signal_payload={ + "signal_id": 8, + "message": "支持导出知识库内容", + "classification": {"classification": "new_project"}, + "proposer": "测试用户", + "captured_at": "2026-03-25T10:00:00+08:00", + }, + team_members=[ + {"user_id": 1, "name": "Alice", "workload_ratio": 42.0, "skill_tag_ids": [11, 12]}, + ], + ) + + assert "charter_json" in prompt + assert "内部知识库升级宪章" in prompt + assert "success_criteria" in prompt + assert "附件仅作证据引用" in prompt + + +def test_task_orchestration_fallback_uses_charter_context() -> None: + class _FakeSkill(SignalCaptureTaskOrchestrationSkillMixin): + @staticmethod + def _normalize_single_line_text(text: str, max_len: int = 64) -> str: + return " ".join(str(text or "").split())[:max_len] + + tasks = _FakeSkill()._build_task_orchestration_fallback( + cleaned_message="新增内部知识库导出需求", + classification={"summary": "补充内部知识库导出能力"}, + charter_context={ + "title": "内部知识库升级宪章", + "summary": "围绕内部知识库问答体验升级", + }, + ) + + assert len(tasks) == 2 + assert all("内部知识库升级宪章" in task["description"] for task in tasks) + + +def test_queue_confirm_undo_prefers_feishu_message_id_for_idempotency() -> None: + item = _Item(item_id=42, level="command", operation_code="project.create") + key = FeishuPipelineQueueConfirmUndoMixin._build_operation_idempotency_key(item=item) + assert key == "feishu:msg-42" + + +def test_task_create_idempotency_key_prefers_message_key() -> None: + key_one = FeishuPipelineCommandCommitTaskMixin._build_task_create_idempotency_key( + idempotency_key="feishu:sm-456", + trace_id="trace-1", + ) + key_two = FeishuPipelineCommandCommitTaskMixin._build_task_create_idempotency_key( + idempotency_key="feishu:sm-456", + trace_id="trace-2", + ) + fallback_key = FeishuPipelineCommandCommitTaskMixin._build_task_create_idempotency_key( + idempotency_key=None, + trace_id="trace-3", + ) + + assert key_one == "feishu:sm-456" + assert key_two == "feishu:sm-456" + assert fallback_key == "trace-3" + + +@pytest.mark.asyncio +async def test_task_create_replays_existing_ledger_without_persisting_task(monkeypatch) -> None: + from app.services.integrations.feishu_signal_pipeline import mixin_command_commit_task as task_commit_module + + class _FakeResult: + def __init__(self, value) -> None: + self._value = value + + def scalar_one_or_none(self): + return self._value + + def scalars(self): + return self + + def first(self): + return self._value + + class _FakeDB: + def __init__(self) -> None: + self.calls = 0 + self.committed = False + + async def execute(self, query): + self.calls += 1 + if self.calls == 1: + return _FakeResult(type("ProjectRow", (), {"id": 11, "tenant_id": 1})()) + if self.calls == 2: + return _FakeResult(type("TruthNodeRow", (), {"id": 22, "tenant_id": 1})()) + raise AssertionError("unexpected execute") + + async def commit(self): + self.committed = True + + class _FakeLedger: + def __init__(self) -> None: + self.status = "applied" + self.result_payload = { + "status": "applied", + "message": "task created #777", + "entity_type": "task", + "entity_id": "777", + "result": { + "task_id": 777, + "title": "补充埋点", + "project_id": 11, + }, + } + + class _FakeCommit(FeishuPipelineCommandCommitTaskMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + self.sync_called = False + + async def _load_task_create_ledger(self, *, idempotency_key): + self.loaded_key = idempotency_key + return _FakeLedger() + + async def _claim_task_create_ledger(self, *, idempotency_key, trace_id, payload): + raise AssertionError("ledger claim should not be reached when replaying an applied result") + + def _sync_task_rag(self, *, task): + self.sync_called = True + + async def _fail_persist_task(*args, **kwargs): + raise AssertionError("persist_task should not be called when replaying an existing ledger") + + monkeypatch.setattr(task_commit_module, "persist_task", _fail_persist_task) + + kernel = _FakeCommit() + result = await kernel._commit_task_create( + operator_user=type("User", (), {"id": 9})(), + payload={"project_id": 11, "title": "补充埋点", "description": "补充行为埋点"}, + trace_id="trace-task-1", + idempotency_key="feishu:msg-777", + ) + + assert result["status"] == "applied" + assert result["result"]["task_id"] == 777 + assert kernel.loaded_key == "feishu:msg-777" + assert kernel.db.committed is False + assert kernel.sync_called is False + + +@pytest.mark.asyncio +async def test_project_gate_always_queues_ready_create(monkeypatch) -> None: + called: dict[str, object] = {} + + class _FakeGate(FeishuPipelineGateProjectMixin): + def __init__(self) -> None: + self.db = object() + self.tenant_id = 1 + + @staticmethod + def _build_project_clarification_message(missing_fields): + return "请补充项目信息。" + + @staticmethod + def _stable_positive_int(*parts): + return 42 + + async def _resolve_command_operator_user(self, *, sender): + called["operator_sender"] = sender + return object() + + def _can_execute_high_value_action(self, operator_user): + called["can_execute"] = operator_user + return True + + async def _queue_project_intent_item(self, **kwargs): + called["queue_kwargs"] = kwargs + return { + "item_id": 42, + "level": "project", + "project_id": None, + "signal_id": None, + "route_status": "queued", + "clarification_message": "建项目已进入待确认队列,请点击继续执行。", + } + + async def _write_processing_log(self, **kwargs): + called["write_processing_log"] = kwargs + + async def _execute_controlled_operation(self, **kwargs): + raise AssertionError("project.create should not execute before confirmation") + + kernel = _FakeGate() + result = await kernel._handle_create_project_gate( + message="请创建一个新项目《AI PMO闭环重构实战-0314》:目标是完成工作流路由重构并上线验收。", + sender="林宇航", + source_chat_id="chat-1", + source_sender_open_id="ou-1", + feishu_message_id="msg-1", + captured_at=None, + intent_gate={ + "intent": "project.create", + "fields": { + "ready": True, + "title": "AI PMO闭环重构实战-0314", + "description": "目标是完成工作流路由重构并上线验收。", + "project_scope": "workflow routing", + }, + }, + process_started_at=0.0, + ) + + assert result["direct_reply_required"] is True + assert result["created_project_id"] is None + assert result["results"][0]["item_id"] == 42 + assert result["reply_template_key"] == "queue_pending_confirm" + assert result["queue_item_id"] == 42 + assert called["queue_kwargs"]["message"].startswith("请创建一个新项目") + + +@pytest.mark.asyncio +async def test_signal_route_item_ignores_low_confidence_threshold_when_project_is_matched() -> None: + class _FakeDBResult: + def scalar_one_or_none(self): + return None + + class _FakeDB: + def __init__(self) -> None: + self.added = [] + + async def execute(self, query): + _ = query + return _FakeDBResult() + + def add(self, obj): + self.added.append(obj) + + async def flush(self): + return None + + class _FakeFinalize(FeishuPipelineItemFinalizeRoutingMixin, FeishuPipelineMessageUtilsBMixin): + def __init__(self) -> None: + self.db = _FakeDB() + self.tenant_id = 1 + self.log_kwargs: dict[str, object] = {} + self.notified: tuple[int, int] | None = None + + async def _ensure_signal_link(self, *, signal_id: int, project_id: int) -> None: + _ = (signal_id, project_id) + + async def _write_processing_log(self, **kwargs): + self.log_kwargs = kwargs + + async def _notify_signal_auto_entered(self, *, project_id: int, signal) -> None: + self.notified = (project_id, int(signal.id)) + + item = SimpleNamespace(id=1) + signal = SimpleNamespace(id=88, title="直播礼物量分析") + + kernel = _FakeFinalize() + result = await kernel._route_signal_item( + item=item, + signal=signal, + matched_project_id=62, + confidence=0.03, + cold_start_enabled=False, + signal_auto_enter_threshold=0.80, + ) + + assert result == 62 + assert item.safe_to_auto_execute is True + assert item.auto_execute_state == "done" + assert item.blocking_reason is None + assert item.route_status == "auto_done" + assert kernel.notified == (62, 88) diff --git a/backend/tests/test_feishu_receive_orchestration_contract.py b/backend/tests/test_feishu_receive_orchestration_contract.py index 6c474555..1a4b1374 100644 --- a/backend/tests/test_feishu_receive_orchestration_contract.py +++ b/backend/tests/test_feishu_receive_orchestration_contract.py @@ -1,5 +1,8 @@ import pytest +from app.services.integrations.feishu_integration_service.mixin_receive_prepare import ( + FeishuReceivePrepareMixin, +) from app.services.integrations.feishu_integration_service.service import FeishuIntegrationService @@ -116,3 +119,53 @@ async def test_handle_message_receive_orchestrates_capture_and_finalize(monkeypa assert called["capture"]["chat_id"] == "chat-1" assert called["capture"]["sender_name"] == "解析后用户名" assert called["finalize"]["chat_type"] == "group" + + +def test_extract_thread_context_reads_top_level_reply_fields() -> None: + event = { + "thread_id": "th-1", + "root_id": "root-1", + "parent_id": "reply-1", + "message": { + "message_id": "msg-1", + "content": "{\"text\":\"hello\"}", + }, + } + context = FeishuReceivePrepareMixin._extract_thread_context( + event=event, + message=event["message"], + message_payload={"text": "hello", "attachments": [], "message_type": "text"}, + ) + + assert context == { + "thread_id": "th-1", + "root_message_id": "root-1", + "reply_to_message_id": "reply-1", + } + + +def test_extract_thread_context_reads_nested_reply_fields() -> None: + event = { + "message": { + "message_id": "msg-1", + "meta": { + "conversation": { + "thread_id": "th-nested", + "root_message_id": "root-nested", + "reply_to_message_id": "reply-nested", + } + }, + "content": "{\"text\":\"hello\"}", + } + } + context = FeishuReceivePrepareMixin._extract_thread_context( + event=event, + message=event["message"], + message_payload={"text": "hello", "attachments": [], "message_type": "text"}, + ) + + assert context == { + "thread_id": "th-nested", + "root_message_id": "root-nested", + "reply_to_message_id": "reply-nested", + } diff --git a/backend/tests/test_signal_planner_contract.py b/backend/tests/test_signal_planner_contract.py new file mode 100644 index 00000000..9a3a1868 --- /dev/null +++ b/backend/tests/test_signal_planner_contract.py @@ -0,0 +1,206 @@ +from types import SimpleNamespace + +import pytest + +from app.services.signal.signal_planner.mixin_loaders import SignalPlannerLoadersMixin +from app.services.signal.signal_planner.service import SignalPlannerService + + +class _FakeScalarResult: + def __init__(self, row): + self._row = row + + def scalar_one_or_none(self): + return self._row + + +class _FakeDB: + def __init__(self, rows): + self._rows = list(rows) + self.statements = [] + + async def execute(self, statement): + self.statements.append(statement) + if self._rows: + return _FakeScalarResult(self._rows.pop(0)) + return _FakeScalarResult(None) + + +@pytest.mark.asyncio +async def test_signal_planner_latest_charter_payload_flattens_modules_and_orders_latest() -> None: + charter = SimpleNamespace( + id=77, + title="支付优化宪章", + version=3, + status="aligned", + content={ + "modules": { + "module_1": { + "problem_statement": "减少支付失败", + "goal_statement": "提升支付成功率", + "kpis": "成功率 >= 99%", + }, + "module_2": { + "in_scope": "支付链路优化", + "out_of_scope": "结算清分重构", + }, + "module_4": { + "milestones": "需求冻结\n开发上线", + }, + "module_5": { + "risk_register": "第三方通道不稳定", + }, + "module_6": { + "governance_rule": "对外变更需复核", + }, + } + }, + ) + db = _FakeDB([charter]) + + payload = await SignalPlannerLoadersMixin._load_latest_charter_payload( + db, + tenant_id=1, + project_id=42, + ) + + assert payload["title"] == "支付优化宪章" + assert payload["summary"] == "减少支付失败" + assert payload["objectives"] == ["提升支付成功率"] + assert payload["in_scope"] == ["支付链路优化"] + assert payload["out_of_scope"] == ["结算清分重构"] + assert payload["milestones"] == ["需求冻结", "开发上线"] + assert "对外变更需复核" in payload["constraints"][0] + + sql_text = str(db.statements[0]).lower() + assert "order by" in sql_text + assert "updated_at" in sql_text + assert "id desc" in sql_text + + +def test_signal_planner_prompt_embeds_charter_context() -> None: + signal = SimpleNamespace( + id=12, + title="继续优化支付失败重试", + summary="提升支付成功率", + content="需要优化支付失败重试策略", + matched_project_id=42, + strategic_analysis={ + "ai_analysis": { + "summary": "支付链路存在失败重试问题", + "requirement_project_scope": "支付优化", + } + }, + ) + prompt = SignalPlannerService._build_prompt( + signal=signal, + project_title="支付优化", + charter_json={ + "id": 77, + "title": "支付优化宪章", + "objectives": ["提升支付成功率"], + "scope": "支付链路优化", + "success_criteria": ["成功率 >= 99%"], + "constraints": ["对外变更需复核"], + "milestones": ["需求冻结", "开发上线"], + "risks": ["第三方通道不稳定"], + }, + recent_signals=[{"id": 1, "title": "支付失败告警"}], + members=[{"user_id": 9, "name": "Alice", "workload_ratio": 42.5}], + ) + + assert "charter_json" in prompt + assert "支付优化宪章" in prompt + assert "success_criteria" in prompt + assert "对外变更需复核" in prompt + + +@pytest.mark.asyncio +async def test_signal_planner_analyze_degrades_without_charter(monkeypatch) -> None: + signal = SimpleNamespace( + id=12, + title="继续优化支付失败重试", + summary="提升支付成功率", + content="需要优化支付失败重试策略", + matched_project_id=42, + strategic_analysis={ + "ai_analysis": { + "summary": "支付链路存在失败重试问题", + "requirement_project_scope": "支付优化", + } + }, + ) + captured: dict[str, object] = {} + + async def _fake_load_signal(db, *, tenant_id: int, signal_id: int): + _ = (db, tenant_id, signal_id) + return signal + + async def _fake_load_project_title(db, *, tenant_id: int, project_id): + _ = (db, tenant_id, project_id) + return "支付优化" + + async def _fake_load_charter_payload(db, *, tenant_id: int, project_id): + _ = (db, tenant_id, project_id) + return {} + + async def _fake_load_recent_signals(db, *, tenant_id: int, project_id, exclude_signal_id: int): + _ = (db, tenant_id, project_id, exclude_signal_id) + return [] + + async def _fake_load_members(db, *, tenant_id: int, project_id): + _ = (db, tenant_id, project_id) + return [{"user_id": 9, "name": "Alice", "workload_ratio": 42.5}] + + async def _fake_generate_structured_object(prompt: str, **kwargs): + captured["prompt"] = prompt + captured["kwargs"] = kwargs + return SimpleNamespace( + payload={ + "summary": "聚焦支付失败重试", + "priority": "high", + "assignee_user_id": 9, + "assignee_reason": "技能匹配且负载可接受", + "execution_plan": ["梳理失败场景", "拆解重试策略"], + "risks": ["第三方通道波动"], + "confidence": 0.91, + } + ) + + async def _fake_get_effective_routing(db, *, tenant_id: int, function_name: str): + _ = (db, tenant_id, function_name) + return {"source": "override", "provider": "glm", "model": "qwen-plus"} + + async def _fake_merge_and_save(db, *, signal_id: int, planner_result: dict, tenant_id: int): + _ = (db, signal_id, tenant_id) + captured["planner_result"] = planner_result + return signal + + class _FakeDB: + async def commit(self): + return None + + async def rollback(self): + return None + + monkeypatch.setattr(SignalPlannerService, "_load_signal", _fake_load_signal) + monkeypatch.setattr(SignalPlannerService, "_load_project_title", _fake_load_project_title) + monkeypatch.setattr(SignalPlannerService, "_load_latest_charter_payload", _fake_load_charter_payload) + monkeypatch.setattr(SignalPlannerService, "_load_recent_signal_summaries", _fake_load_recent_signals) + monkeypatch.setattr(SignalPlannerService, "_load_member_profiles", _fake_load_members) + monkeypatch.setattr("app.services.signal.signal_planner.mixin_analyze.llm_service.generate_structured_object", _fake_generate_structured_object) + monkeypatch.setattr("app.services.signal.signal_planner.mixin_analyze.ModelRoutingService.get_effective_routing", _fake_get_effective_routing) + monkeypatch.setattr("app.services.signal.signal_planner.mixin_analyze.SignalStrategicAnalysisService.merge_and_save", _fake_merge_and_save) + + await SignalPlannerService._analyze_with_session( + db=_FakeDB(), + signal_id=12, + tenant_id=1, + source_message_id="msg-1", + source_chat_id="chat-1", + dedupe_seed="seed-1", + ) + + assert "charter_json" in str(captured["prompt"]) + assert "{}" in str(captured["prompt"]) + assert captured["planner_result"]["summary"] == "聚焦支付失败重试" diff --git a/backend/tests/test_sstc_tasks_ai_breakdown_contract.py b/backend/tests/test_sstc_tasks_ai_breakdown_contract.py new file mode 100644 index 00000000..a584c93a --- /dev/null +++ b/backend/tests/test_sstc_tasks_ai_breakdown_contract.py @@ -0,0 +1,197 @@ +import json +from types import SimpleNamespace + +import pytest + +from app.api import tasks as tasks_api +from app.services.sstc.charter_data import ( + _build_task_breakdown_context, + _build_task_breakdown_prompt, +) +from app.schemas import TaskBreakdownRequest + + +def test_task_breakdown_context_flattens_charter_modules() -> None: + project = SimpleNamespace( + id=6, + title="内部知识库升级", + name="内部知识库升级", + description="围绕内部知识库问答能力升级", + status="active", + assigned_role="backend", + ) + charter = SimpleNamespace( + id=55, + title="内部知识库升级宪章", + version=2, + status="aligning", + content={ + "modules": { + "module_1": { + "problem_statement": "减少重复检索", + "goal_statement": "提升命中率 / 降低重复提问", + "kpis": "首响 < 3s", + }, + "module_2": { + "in_scope": "问答检索,知识补充", + "out_of_scope": "权限模型重构", + }, + "module_4": { + "milestones": "需求冻结\n开发完成", + }, + "module_5": { + "risk_register": "需求膨胀; 依赖不清", + }, + "module_6": { + "governance_rule": "附件仅作证据引用", + }, + } + }, + ) + + context = _build_task_breakdown_context( + description="支持导出知识库内容", + project=project, + charter=charter, + ) + + assert context["project"]["title"] == "内部知识库升级" + assert context["project"]["status"] == "active" + assert context["charter"]["summary"] == "减少重复检索" + assert context["charter"]["objectives"] == ["提升命中率", "降低重复提问"] + assert context["charter"]["in_scope"] == ["问答检索", "知识补充"] + assert context["charter"]["out_of_scope"] == ["权限模型重构"] + assert context["charter"]["milestones"] == ["需求冻结", "开发完成"] + assert "附件仅作证据引用" in context["charter"]["constraints"][0] + + +def test_task_breakdown_prompt_embeds_charter_context() -> None: + prompt = _build_task_breakdown_prompt( + project_id=6, + context={ + "project": { + "id": 6, + "title": "内部知识库升级", + "description": "围绕内部知识库问答能力升级", + "status": "active", + "assigned_role": "backend", + }, + "charter": { + "id": 55, + "title": "内部知识库升级宪章", + "version": 2, + "status": "aligning", + "summary": "减少重复检索", + "objectives": ["提升命中率", "降低重复提问"], + "scope": "知识库检索与问答", + "in_scope": ["问答检索", "知识补充"], + "out_of_scope": ["权限模型重构"], + "success_criteria": ["首响 < 3s"], + "constraints": ["附件仅作证据引用"], + "milestones": ["需求冻结", "开发完成"], + "risks": ["需求膨胀"], + "signal_ids": [12], + }, + }, + available_skills="FastAPI, React, LangChain", + ) + + assert "需求宪章与项目上下文" in prompt + assert "内部知识库升级宪章" in prompt + assert "FastAPI, React, LangChain" in prompt + assert "objectives" in prompt + assert "constraints" in prompt + + +@pytest.mark.asyncio +async def test_general_task_breakdown_route_uses_project_and_charter_context(monkeypatch) -> None: + captured: dict[str, object] = {} + + async def _fake_load_context(db, *, tenant_id: int, description: str, project_id=None, charter_id=None): + _ = (db, tenant_id, project_id, charter_id) + captured["description"] = description + return { + "request": {"description": description}, + "project": { + "id": 6, + "title": "内部知识库升级", + "description": "围绕内部知识库问答能力升级", + "status": "active", + "assigned_role": "backend", + }, + "charter": { + "id": 55, + "title": "内部知识库升级宪章", + "version": 2, + "status": "aligning", + "summary": "减少重复检索", + "objectives": ["提升命中率", "降低重复提问"], + "scope": "知识库检索与问答", + "in_scope": ["问答检索", "知识补充"], + "out_of_scope": ["权限模型重构"], + "success_criteria": ["首响 < 3s"], + "constraints": ["附件仅作证据引用"], + "milestones": ["需求冻结", "开发完成"], + "risks": ["需求膨胀"], + "signal_ids": [12], + }, + } + + async def _fake_load_skills(db, *, tenant_id: int): + _ = (db, tenant_id) + return "FastAPI, React, LangChain" + + class _FakeLLM: + async def generate(self, prompt: str, **kwargs): + captured["prompt"] = prompt + captured["llm_kwargs"] = kwargs + return json.dumps( + [ + { + "task_name": "需求澄清与范围确认", + "type": "ai", + "estimated_hours": 4, + "priority": "high", + "required_skill_names": ["FastAPI"], + }, + { + "task_name": "核心方案落地", + "type": "backend", + "estimated_hours": 12, + "priority": "medium", + "required_skill_names": ["React", "LangChain"], + }, + ], + ensure_ascii=False, + ) + + @staticmethod + def _parse_json_array(text: str): + parsed = json.loads(text) + return [item for item in parsed if isinstance(item, dict)] + + class _FakePlane: + async def sync_tasks(self, tasks, is_urgent: bool = False): + _ = is_urgent + return {"synced_count": len(tasks)} + + monkeypatch.setattr(tasks_api, "_load_task_breakdown_context", _fake_load_context) + monkeypatch.setattr(tasks_api, "_load_available_skill_names", _fake_load_skills) + monkeypatch.setattr(tasks_api, "llm_service", _FakeLLM()) + monkeypatch.setattr(tasks_api, "plane_service", _FakePlane()) + + result = await tasks_api.breakdown_tasks( + TaskBreakdownRequest( + description="支持导出知识库内容", + project_id=6, + charter_id=55, + sync_to_plane=True, + ), + db=SimpleNamespace(), + current_user=SimpleNamespace(tenant_id=1, id=1), + ) + + assert "内部知识库升级宪章" in str(captured["prompt"]) + assert "FastAPI, React, LangChain" in str(captured["prompt"]) + assert result["sync_result"]["synced_count"] == 2 + assert result["tasks"][0]["task_name"] == "需求澄清与范围确认" diff --git a/frontend/src/pages/ai-steward/constants.ts b/frontend/src/pages/ai-steward/constants.ts index 4d9f54a8..299c6d09 100644 --- a/frontend/src/pages/ai-steward/constants.ts +++ b/frontend/src/pages/ai-steward/constants.ts @@ -3,7 +3,7 @@ import type { EngineProviderPresets } from "./types"; export const PROVIDER_ZH: Record = { deepseek: "DeepSeek", - glm: "GLM", + glm: "Qwen(DashScope)", openai: "OpenAI", ollama: "Ollama(本地)", }; @@ -19,8 +19,8 @@ export const ENGINE_PROVIDER_FALLBACK_PRESETS: EngineProviderPresets = { base_url: "https://api.deepseek.com/v1", }, glm: { - model: "GLM-4.7", - base_url: "https://api.edgefn.net/v1", + model: "qwen-plus", + base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1", }, openai: { model: "MiniMax-M2.5", diff --git a/frontend/src/pages/ai-steward/sections/EngineTab.tsx b/frontend/src/pages/ai-steward/sections/EngineTab.tsx index 594a8092..d14ed3e9 100644 --- a/frontend/src/pages/ai-steward/sections/EngineTab.tsx +++ b/frontend/src/pages/ai-steward/sections/EngineTab.tsx @@ -229,7 +229,7 @@ export default function EngineTab({ setEngineForm((prev) => ({ ...prev, model: e.target.value }))} - placeholder="如:deepseek-chat / GLM-4.7 / MiniMax-M2.5 / llama3:latest" + placeholder="如:deepseek-chat / qwen-plus / MiniMax-M2.5 / llama3:latest" />
@@ -237,9 +237,9 @@ export default function EngineTab({ setEngineForm((prev) => ({ ...prev, base_url: e.target.value }))} - placeholder="如:https://api.edgefn.net/v1" + placeholder="如:https://dashscope.aliyuncs.com/compatible-mode/v1" /> -
当前推荐:GLM 与 MiniMax 都走 `https://api.edgefn.net/v1`
+
当前推荐:Qwen 走 `https://dashscope.aliyuncs.com/compatible-mode/v1`,MiniMax 继续走 OpenAI 兼容网关
diff --git a/package-lock.json b/package-lock.json index 417cd38a..a6ecbbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,171 @@ "name": "AIProjectManger", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "devDependencies": { + "pg": "^8.20.0" + } + }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } } diff --git a/package.json b/package.json new file mode 100644 index 00000000..9dd9a84c --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "pg": "^8.20.0" + } +}