From a3cecd4b5064975d7ac55f2611f8e929d0ec8ad3 Mon Sep 17 00:00:00 2001 From: chiguyong Date: Tue, 30 Jun 2026 14:27:46 +0800 Subject: [PATCH] fix(review): apply P0/P2 findings from dual-agent review - Dockerfile: split ENTRYPOINT/CMD to align with docker-compose serve - test_termbase: guard jieba import with pytest.importorskip - orchestrator: mark silent review-degradation with [DEGRADED] prefix - chat.py: accurate ExecutionMode log message - agentkit.yaml: document OTel exporter config - skill_routing: replace 12 Any with object/typed (AGENTS.md compliance) - AssistantText.vue: add aria-live/role for a11y --- Dockerfile | 5 ++++- agentkit.yaml | 6 ++++++ src/agentkit/chat/skill_routing.py | 21 +++++++++---------- src/agentkit/experts/orchestrator.py | 8 ++++--- .../chat/messages/AssistantText.vue | 13 ++++++++++-- src/agentkit/server/routes/chat.py | 4 ++-- tests/unit/rag_platform/test_termbase.py | 7 ++++++- 7 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 02a1e10..aa3e614 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,4 +31,7 @@ EXPOSE 8001 HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8001/api/v1/health')" -CMD ["uvicorn", "configs.geo_server:create_geo_app", "--factory", "--host", "0.0.0.0", "--port", "8001"] +# ponytail: 与 docker-compose.yaml command 对齐,纯 `docker run` 启动完整 AgentKit +# 而非 GEO 子系统。GEO 子系统应通过独立 image 或 ENTRYPOINT 参数切换。 +ENTRYPOINT ["agentkit"] +CMD ["serve", "--host", "0.0.0.0", "--port", "8001"] diff --git a/agentkit.yaml b/agentkit.yaml index a0b2f43..553bab6 100644 --- a/agentkit.yaml +++ b/agentkit.yaml @@ -72,3 +72,9 @@ experts: {paths: ["./configs/experts"]} board: {max_rounds: 5, default_template: private_board, parallel_speech: true, history_compression_threshold: 20} logging: {level: INFO, format: text} router: {classifier: heuristic, auction_enabled: false} +# OTel 可观测性 — 默认注释(OTel 为可选依赖,未安装时 telemetry/metrics.py 返回 NoOp)。 +# 启用:pip install opentelemetry-sdk opentelemetry-exporter-otlp,取消注释并指向 collector。 +# 未配置时所有指标(请求量/延迟/token 消耗)静默丢弃,形成监控盲区。 +# telemetry: +# otlp_endpoint: http://localhost:4317 # OTLP gRPC 端点 +# service_name: fischer-agentkit diff --git a/src/agentkit/chat/skill_routing.py b/src/agentkit/chat/skill_routing.py index 9972173..8ae04cd 100644 --- a/src/agentkit/chat/skill_routing.py +++ b/src/agentkit/chat/skill_routing.py @@ -10,7 +10,6 @@ import enum import logging import re from dataclasses import dataclass, field -from typing import Any logger = logging.getLogger(__name__) @@ -48,7 +47,7 @@ _SKILL_EXECUTION_MODE_MAP: dict[str, ExecutionMode] = { } -def _resolve_execution_mode(skill_config: Any) -> ExecutionMode: +def _resolve_execution_mode(skill_config: object) -> ExecutionMode: """Resolve ExecutionMode from skill config's execution_mode field.""" mode_str = getattr(skill_config, "execution_mode", "react") or "react" return _SKILL_EXECUTION_MODE_MAP.get(mode_str, ExecutionMode.SKILL_REACT) @@ -67,11 +66,11 @@ class SkillRoutingResult: """Result of skill routing for a user message.""" skill_name: str | None = None - skill_config: Any = None - skill_tools: list = field(default_factory=list) + skill_config: object | None = None + skill_tools: list[object] = field(default_factory=list) clean_content: str = "" system_prompt: str | None = None - tools: list = field(default_factory=list) + tools: list[object] = field(default_factory=list) model: str = "default" agent_name: str | None = None matched: bool = False @@ -112,9 +111,9 @@ def format_preconditions_block(preconditions: list[str], header_level: int = 2) return "\n".join(lines) -def collect_prompt_parts(config: Any, with_headers: bool = False) -> list[str]: +def collect_prompt_parts(config: object, with_headers: bool = False) -> list[str]: """从 skill config 的 prompt 字典中收集各部分文本。""" - prompt = config.prompt or {} + prompt = getattr(config, "prompt", None) or {} parts: list[str] = [] for key in _PROMPT_KEYS: val = prompt.get(key) @@ -167,12 +166,12 @@ def build_skill_system_prompt(skill_config) -> str | None: async def resolve_skill_routing( content: str, - skill_registry: Any, - default_tools: list, + skill_registry: object, + default_tools: list[object], default_system_prompt: str | None, default_model: str = "default", default_agent_name: str = "default", - agent_tool_registry: Any = None, + agent_tool_registry: object | None = None, session_id: str = "", ) -> SkillRoutingResult: """Resolve skill routing for a user message. @@ -267,7 +266,7 @@ async def resolve_skill_routing( return result -def _build_tools_description(tools: list) -> str: +def _build_tools_description(tools: list[object]) -> str: """Build a text description of tools for the system prompt.""" lines = [] for tool in tools: diff --git a/src/agentkit/experts/orchestrator.py b/src/agentkit/experts/orchestrator.py index 4ed80ed..faf1e81 100644 --- a/src/agentkit/experts/orchestrator.py +++ b/src/agentkit/experts/orchestrator.py @@ -994,7 +994,9 @@ class TeamOrchestrator: gateway = self._get_llm_gateway(lead) if not gateway: logger.warning("No LLM gateway available, skipping review") - return True, "LLM 验收不可用,自动通过" + # 优雅降级:不阻塞流程,但 [DEGRADED] 前缀让 review_result 事件 + # 和日志聚合可识别降级路径,便于运维监控验收失效频率。 + return True, "[DEGRADED] LLM 验收不可用,自动通过" content = result.get("content", str(result)) # P1: prompt injection 防护 — 用 XML 标签包裹专家输出,指示 LLM 忽略其中指令 @@ -1039,8 +1041,8 @@ class TeamOrchestrator: except Exception as e: logger.warning(f"Review LLM call failed: {e}") - # 降级:验收通过(标注降级原因,便于追踪) - return True, "LLM 验收降级,自动通过" + # 降级:不阻塞流程,但 [DEGRADED] 前缀让 review_result 事件可识别降级路径 + return True, "[DEGRADED] LLM 验收降级,自动通过" @staticmethod def _parse_risk_flags(content: str) -> list[str]: diff --git a/src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue b/src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue index 1b3c041..20c6391 100644 --- a/src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue +++ b/src/agentkit/server/frontend/src/components/chat/messages/AssistantText.vue @@ -14,9 +14,18 @@ /> -
+
-
+
diff --git a/src/agentkit/server/routes/chat.py b/src/agentkit/server/routes/chat.py index 7b2bba5..f47b5a7 100644 --- a/src/agentkit/server/routes/chat.py +++ b/src/agentkit/server/routes/chat.py @@ -1234,8 +1234,8 @@ async def _handle_chat_message( ExecutionMode.PLAN_EXEC, ): logger.warning( - f"Execution mode {routing.execution_mode.value} not yet supported " - f"in chat WebSocket, falling back to REACT" + f"Execution mode {routing.execution_mode.value} not implemented " + f"in chat WebSocket path, falling back to REACT" ) # Execute Agent with streaming diff --git a/tests/unit/rag_platform/test_termbase.py b/tests/unit/rag_platform/test_termbase.py index aedf65d..0c13b5c 100644 --- a/tests/unit/rag_platform/test_termbase.py +++ b/tests/unit/rag_platform/test_termbase.py @@ -15,7 +15,12 @@ from __future__ import annotations from pathlib import Path -import jieba +import pytest + +# jieba 是可选依赖(pyproject.toml 主依赖),但测试环境可能未安装。 +# importorskip 确保收集阶段不中断,符合 project_rules.md 的 pre-commit 门禁。 +pytest.importorskip("jieba") +import jieba # noqa: E402 — 必须在 importorskip 之后 from agentkit.rag_platform.termbase import TermEntry, Termbase