From a778f816c55545c6c21d688c43a56004de70503a Mon Sep 17 00:00:00 2001 From: Fischer Date: Wed, 1 Jul 2026 03:54:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20tech=20debt=20Wave=201+2=20(except?= =?UTF-8?q?=20Exception=20=E6=94=B6=E5=B0=BE=20+=20core/experts=20Any=20?= =?UTF-8?q?=E6=B2=BB=E7=90=86)=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agentkit/bus/memory_bus.py | 4 +- src/agentkit/bus/redis_bus.py | 26 +++-- src/agentkit/chat/request_preprocessor.py | 2 +- src/agentkit/chat/skill_routing.py | 2 +- .../chat/sqlite_conversation_store.py | 6 +- src/agentkit/cli/chat.py | 30 +++-- src/agentkit/core/base.py | 14 +-- src/agentkit/core/config_driven.py | 28 ++--- src/agentkit/core/fallback.py | 3 +- src/agentkit/core/middleware.py | 20 ++-- src/agentkit/core/plan_executor.py | 18 +-- src/agentkit/core/react.py | 70 ++++++------ src/agentkit/core/rewoo.py | 60 +++++----- src/agentkit/evolution/ab_tester.py | 4 +- src/agentkit/evolution/experience_store.py | 14 ++- src/agentkit/evolution/genetic.py | 3 +- src/agentkit/evolution/lifecycle.py | 6 +- src/agentkit/evolution/llm_reflector.py | 3 +- src/agentkit/evolution/pg_store.py | 15 +-- src/agentkit/evolution/pitfall_detector.py | 2 +- src/agentkit/evolution/prompt_optimizer.py | 3 +- src/agentkit/evolution/risk_guard_learner.py | 9 +- src/agentkit/experts/_debate_runner.py | 18 +-- src/agentkit/experts/_divergence_detector.py | 4 +- src/agentkit/experts/_phase_executor.py | 22 ++-- src/agentkit/experts/_review_gate.py | 5 +- src/agentkit/experts/_rollback_handler.py | 4 +- src/agentkit/experts/_synthesizer.py | 10 +- src/agentkit/experts/board.py | 9 +- src/agentkit/experts/board_orchestrator.py | 11 +- src/agentkit/experts/config.py | 17 ++- src/agentkit/experts/orchestrator.py | 21 ++-- src/agentkit/experts/plan.py | 29 +++-- src/agentkit/experts/team.py | 3 +- src/agentkit/memory/adapters/confluence.py | 13 ++- src/agentkit/memory/adapters/feishu.py | 13 ++- src/agentkit/memory/adapters/generic_http.py | 25 ++-- src/agentkit/memory/contextual_retrieval.py | 3 +- src/agentkit/memory/document_loader.py | 22 +++- src/agentkit/memory/embedder.py | 8 +- src/agentkit/memory/episodic.py | 9 +- src/agentkit/memory/http_rag.py | 8 +- src/agentkit/memory/local_rag.py | 16 +-- src/agentkit/memory/profile.py | 2 +- src/agentkit/memory/retriever.py | 2 +- src/agentkit/quality/cascade_state_store.py | 16 ++- src/agentkit/server/app.py | 108 ++++++++++++------ src/agentkit/session/store.py | 12 +- src/agentkit/skills/loader.py | 10 +- 49 files changed, 430 insertions(+), 332 deletions(-) diff --git a/src/agentkit/bus/memory_bus.py b/src/agentkit/bus/memory_bus.py index ddf7a3b..d720c52 100644 --- a/src/agentkit/bus/memory_bus.py +++ b/src/agentkit/bus/memory_bus.py @@ -70,7 +70,9 @@ class InMemoryMessageBus: for handler in self._subscribers[recipient]: try: await handler(message) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — user-defined async handlers can throw arbitrary exceptions logger.warning(f"Handler error for {recipient}: {e}") # Check if this is a response to a pending request diff --git a/src/agentkit/bus/redis_bus.py b/src/agentkit/bus/redis_bus.py index 3f41376..c846e0d 100644 --- a/src/agentkit/bus/redis_bus.py +++ b/src/agentkit/bus/redis_bus.py @@ -62,7 +62,7 @@ class RedisMessageBus: try: await redis.xadd(stream_key, {"data": json.dumps(data)}) - except Exception as e: + except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError) as e: logger.error(f"Failed to publish message to {stream_key}: {e}") raise @@ -103,7 +103,9 @@ class RedisMessageBus: await redis.xgroup_create( stream_key, self._consumer_group, id="0", mkstream=True, ) - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — xgroup_create raises ResponseError on BUSYGROUP; redis is optional dep pass # Group already exists while True: @@ -126,12 +128,16 @@ class RedisMessageBus: for handler in self._subscribers.get(agent_name, []): try: await handler(message) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — user-defined async handlers can throw arbitrary exceptions logger.warning(f"Handler error for {agent_name}: {e}") # Acknowledge message await redis.xack(stream_key, self._consumer_group, msg_id) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — wraps json parse + handler + xack; multi-op block with diverse failure modes logger.warning(f"Failed to process message {msg_id}: {e}") # Move to dead letter after max retries await self._handle_failed_message( @@ -139,7 +145,7 @@ class RedisMessageBus: ) except asyncio.CancelledError: break - except Exception as e: + except Exception as e: # noqa: BLE001 — consumer loop top-level fallback; must keep running on transient errors logger.error(f"Consumer error for {agent_name}: {e}") await asyncio.sleep(1) @@ -157,7 +163,7 @@ class RedisMessageBus: await redis.xadd(dead_key, fields) await redis.xack(stream_key, self._consumer_group, msg_id) logger.warning(f"Message {msg_id} moved to dead letter queue") - except Exception as e: + except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError) as e: logger.error(f"Failed to move message to dead letter: {e}") async def unsubscribe(self, agent_name: str) -> None: @@ -205,7 +211,7 @@ class RedisMessageBus: stream_key = self._stream_key(agent_name) try: await redis.xadd(stream_key, {"data": json.dumps(data)}) - except Exception as e: + except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError) as e: logger.error(f"Failed to broadcast to {agent_name}: {e}") # Check pending requests (only for replies) @@ -222,7 +228,7 @@ class RedisMessageBus: try: redis = await self._get_redis() return await redis.ping() - except Exception: + except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError): return False @property @@ -257,7 +263,9 @@ def create_message_bus( ) logger.info(f"MessageBus backend: redis_streams ({redis_url})") return bus - except Exception as exc: + except asyncio.CancelledError: + raise + except Exception as exc: # noqa: BLE001 — factory fallback to InMemoryMessageBus; must catch import/init errors broadly logger.warning( f"Failed to initialise RedisMessageBus ({exc}), " f"falling back to InMemoryMessageBus" diff --git a/src/agentkit/chat/request_preprocessor.py b/src/agentkit/chat/request_preprocessor.py index 24704fd..f4c74a2 100644 --- a/src/agentkit/chat/request_preprocessor.py +++ b/src/agentkit/chat/request_preprocessor.py @@ -194,7 +194,7 @@ class RequestPreprocessor: """Resolve an explicitly specified skill via @skill:xxx prefix.""" try: skill = registry.get(skill_name) - except Exception: + except (ValueError, KeyError, TypeError, RuntimeError): logger.warning(f"Skill '{skill_name}' not found, falling back to REACT") return SkillRoutingResult( clean_content=clean_content, diff --git a/src/agentkit/chat/skill_routing.py b/src/agentkit/chat/skill_routing.py index 8ae04cd..0f6cff5 100644 --- a/src/agentkit/chat/skill_routing.py +++ b/src/agentkit/chat/skill_routing.py @@ -200,7 +200,7 @@ async def resolve_skill_routing( result.match_method = "explicit" result.match_confidence = 1.0 logger.info(f"Session {session_id}: using explicit skill '{explicit_skill}'") - except Exception as e: + except (ValueError, KeyError, TypeError, RuntimeError) as e: logger.warning( f"Session {session_id}: explicit skill '{explicit_skill}' not found: {e}" ) diff --git a/src/agentkit/chat/sqlite_conversation_store.py b/src/agentkit/chat/sqlite_conversation_store.py index a0a3b39..749be29 100644 --- a/src/agentkit/chat/sqlite_conversation_store.py +++ b/src/agentkit/chat/sqlite_conversation_store.py @@ -291,7 +291,7 @@ class SqliteConversationStore: title_row = await title_cursor.fetchone() if title_row: conv.messages.append(ChatMessage(role="user", content=title_row["content"])) - except Exception: + except (aiosqlite.Error, ValueError, KeyError, TypeError): pass result.append(conv) return result @@ -321,7 +321,7 @@ class SqliteConversationStore: ) await db.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,)) await db.commit() - except Exception: + except (aiosqlite.Error, ValueError, KeyError, RuntimeError): await db.rollback() logger.exception("Failed to delete conversation %s; rolled back", conversation_id) raise @@ -365,6 +365,6 @@ class SqliteConversationStore: timestamp=self._str_to_dt(row["timestamp"]), metadata=meta, ) - except Exception as e: + except (aiosqlite.Error, ValueError, KeyError, TypeError, RuntimeError) as e: logger.warning(f"get_first_user_message failed for {conversation_id}: {e}") return None diff --git a/src/agentkit/cli/chat.py b/src/agentkit/cli/chat.py index f1ced52..0c5b1be 100644 --- a/src/agentkit/cli/chat.py +++ b/src/agentkit/cli/chat.py @@ -134,7 +134,7 @@ async def _chat_async( data=data or {}, ) ) - except Exception: + except (asyncio.QueueFull, RuntimeError, ConnectionError): pass # EQ is best-effort; never break CLI flow # Emit session.started event @@ -177,7 +177,7 @@ async def _chat_async( elif p.is_file() and p.suffix in (".yaml", ".yml"): try: loader.load_from_file(str(p)) - except Exception: + except (ValueError, TypeError, KeyError, OSError, RuntimeError): pass # Build system prompt — inject memory into system prompt @@ -428,7 +428,9 @@ async def _chat_async( data={"output": full_content}, ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — CLI main loop top-level; must not crash on user-facing errors rprint(f"\n[red]Error: {e}[/red]") # Emit task.failed to EQ (if enabled) if _emit is not None: @@ -461,7 +463,7 @@ async def _chat_async( # Archive old daily logs memory_store.archive_old_dailies(keep_days=2) - except Exception: + except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError): pass # Daily log generation is best-effort # Close EventQueue if it was enabled (emit session.ended and close) @@ -473,7 +475,7 @@ async def _chat_async( session_id=session.session_id, data={}, ) - except Exception: + except (asyncio.QueueFull, RuntimeError, ConnectionError): pass eq.close() @@ -502,7 +504,7 @@ def _build_gateway(server_config: "ServerConfig") -> "LLMGateway": try: provider = _create_provider(name, pconf) gateway.register_provider(name, provider) - except Exception as e: + except (ValueError, TypeError, KeyError, RuntimeError, ConnectionError, OSError) as e: import logging logging.getLogger(__name__).warning(f"Failed to register LLM provider '{name}': {e}") @@ -603,7 +605,9 @@ def _render_pm_collaboration_event(message: dict) -> bool: ) ) return True - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — best-effort rendering; must not break orchestration # ponytail: best-effort 渲染不中断编排,但记录日志便于调试 import logging @@ -782,7 +786,9 @@ async def _execute_team_cli( elif etype == "user_intervention": pass # User typed it themselves # Other events (expert_step, expert_result, expert_joined, etc.) are not rendered - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — best-effort rendering; must not break orchestration pass # Rendering is best-effort; never break orchestration team.handoff_transport.register_handler(team.team_channel, _event_handler) @@ -816,7 +822,7 @@ async def _execute_team_cli( if readable: try: line = sys.stdin.readline() - except Exception: + except (OSError, ValueError, RuntimeError): line = "" if not line: break # EOF @@ -846,12 +852,14 @@ async def _execute_team_cli( ) ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — team execution top-level; must report errors to user without crash rprint(f"[red]团队执行错误: {e}[/red]") finally: try: await team.dissolve() - except Exception: + except (RuntimeError, asyncio.TimeoutError, ConnectionError): pass return True diff --git a/src/agentkit/core/base.py b/src/agentkit/core/base.py index 9d20558..fe715cf 100644 --- a/src/agentkit/core/base.py +++ b/src/agentkit/core/base.py @@ -13,7 +13,7 @@ import logging import time from abc import ABC, abstractmethod from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import redis.asyncio as aioredis @@ -71,7 +71,7 @@ class BaseAgent(ABC): # 可插拔能力(由子类或配置注入) self._tools: list["Tool"] = [] self._memory: "Memory | None" = None - self._memory_retriever: Any | None = None + self._memory_retriever: object | None = None # 外部依赖注入(由 start() 时设置) self._registry = None @@ -192,7 +192,7 @@ class BaseAgent(ABC): lines.append(f" - {msg}") return "\n".join(lines) - def _build_skill_context(self) -> dict[str, Any] | None: + def _build_skill_context(self) -> dict[str, object] | None: """从当前技能配置构建 skill_context,用于 QualityGate skill_match 校验""" if not self._skill: return None @@ -216,17 +216,17 @@ class BaseAgent(ABC): self._memory = memory return self - def use_memory_retriever(self, retriever: Any) -> "BaseAgent": + def use_memory_retriever(self, retriever: object) -> "BaseAgent": """设置记忆检索器,用于上下文注入""" self._memory_retriever = retriever return self - def set_registry(self, registry: Any) -> "BaseAgent": + def set_registry(self, registry: object) -> "BaseAgent": """注入注册中心""" self._registry = registry return self - def set_dispatcher(self, dispatcher: Any) -> "BaseAgent": + def set_dispatcher(self, dispatcher: object) -> "BaseAgent": """注入任务分发器""" self._dispatcher = dispatcher return self @@ -489,7 +489,7 @@ class BaseAgent(ABC): target_agent: str, task: TaskMessage, reason: str, - context: dict[str, Any] | None = None, + context: dict[str, object] | None = None, ): """将当前任务转交给另一个 Agent""" if self._redis is None: diff --git a/src/agentkit/core/config_driven.py b/src/agentkit/core/config_driven.py index dc9a546..92f2c94 100644 --- a/src/agentkit/core/config_driven.py +++ b/src/agentkit/core/config_driven.py @@ -10,7 +10,7 @@ import json import logging import os -from typing import Any, Callable, Coroutine +from typing import Callable, Coroutine import yaml @@ -39,12 +39,12 @@ class AgentConfig: task_mode: str = "llm_generate", supported_tasks: list[str] | None = None, max_concurrency: int = 1, - input_schema: dict[str, Any] | None = None, - output_schema: dict[str, Any] | None = None, + input_schema: dict[str, object] | None = None, + output_schema: dict[str, object] | None = None, prompt: dict[str, str] | None = None, - llm: dict[str, Any] | None = None, + llm: dict[str, object] | None = None, tools: list[str] | None = None, - memory: dict[str, Any] | None = None, + memory: dict[str, object] | None = None, custom_handler: str | None = None, ): self.name = name @@ -96,7 +96,7 @@ class AgentConfig: ) @classmethod - def from_dict(cls, data: dict[str, Any]) -> "AgentConfig": + def from_dict(cls, data: dict[str, object]) -> "AgentConfig": """从字典创建配置""" return cls( name=data["name"], @@ -128,7 +128,7 @@ class AgentConfig: ) return cls.from_dict(data) - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" d = { "name": self.name, @@ -197,11 +197,11 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin): self, config: AgentConfig, tool_registry: ToolRegistry | None = None, - llm_client: Any = None, + llm_client: object | None = None, custom_handlers: dict[str, Callable[..., Coroutine]] | None = None, - llm_gateway: Any = None, # NEW v2 param: LLMGateway + llm_gateway: object | None = None, # NEW v2 param: LLMGateway mcp_servers: dict[str, str] | None = None, # NEW v2 param: MCP server URLs - compressor: Any = None, # CompressionStrategy | None + compressor: object | None = None, # CompressionStrategy | None ): # v2: If SkillConfig, extract skill info from agentkit.skills.base import SkillConfig, Skill @@ -310,12 +310,12 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin): logger.info(f"Merged skill tool '{tool.name}' into agent '{self.name}'") # v2: Register MCP tools if mcp_servers provided - self._mcp_clients: list[Any] = [] + self._mcp_clients: list[object] = [] self._mcp_servers: dict[str, str] = mcp_servers or {} self._mcp_tools_registered = False # Memory integration: 从 config.memory 自动实例化 MemoryRetriever - self._memory_retriever: Any | None = None + self._memory_retriever: object | None = None if config.memory: try: from agentkit.memory.retriever import MemoryRetriever @@ -903,7 +903,7 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin): ) return await self.handle_task(enhanced_task) - def _wrap_llm_client(self, llm_client: Any): + def _wrap_llm_client(self, llm_client: object): """Wrap legacy llm_client into LLMGateway""" from agentkit.llm.gateway import LLMGateway from agentkit.llm.protocol import LLMProvider, LLMRequest, LLMResponse, TokenUsage @@ -911,7 +911,7 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin): class ClientProvider(LLMProvider): """Adapter: wraps legacy llm_client as an LLMProvider""" - def __init__(self, raw_client: Any): + def __init__(self, raw_client: object): self._raw_client = raw_client async def chat(self, request: LLMRequest) -> LLMResponse: diff --git a/src/agentkit/core/fallback.py b/src/agentkit/core/fallback.py index 1c28549..4857468 100644 --- a/src/agentkit/core/fallback.py +++ b/src/agentkit/core/fallback.py @@ -9,7 +9,6 @@ no LLM). See ``docs/plans/2026-06-29-003-feat-agent-wave2-medium-coupling-plan.m """ from dataclasses import dataclass -from typing import Any from agentkit.core.exceptions import ( LLMProviderError, @@ -54,7 +53,7 @@ class EmergencyError: retryable: bool # whether a user retry might succeed original_error: str # str(exc) for traceability - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: return { "error_code": self.error_code, "message": self.message, diff --git a/src/agentkit/core/middleware.py b/src/agentkit/core/middleware.py index a66f0d8..cad3d17 100644 --- a/src/agentkit/core/middleware.py +++ b/src/agentkit/core/middleware.py @@ -24,7 +24,7 @@ from __future__ import annotations import json import logging from dataclasses import dataclass, field -from typing import Any, Awaitable, Callable, Protocol, runtime_checkable +from typing import Awaitable, Callable, Protocol, runtime_checkable logger = logging.getLogger(__name__) @@ -37,14 +37,14 @@ class RequestContext: """ messages: list[dict[str, str]] - tools: list[Any] = field(default_factory=list) + tools: list[object] = field(default_factory=list) system_prompt: str | None = None model: str = "default" agent_name: str = "" task_type: str = "" task_id: str | None = None # 中间件间共享状态(压缩结果、token 用量、循环检测状态等) - metadata: dict[str, Any] = field(default_factory=dict) + metadata: dict[str, object] = field(default_factory=dict) @runtime_checkable @@ -57,7 +57,7 @@ class Middleware(Protocol): async def before(self, ctx: RequestContext) -> RequestContext: ... - async def after(self, ctx: RequestContext, result: Any) -> Any: ... + async def after(self, ctx: RequestContext, result: object) -> object: ... class MiddlewareChain: @@ -81,8 +81,8 @@ class MiddlewareChain: async def execute( self, ctx: RequestContext, - handler: Callable[[RequestContext], Awaitable[Any]], - ) -> Any: + handler: Callable[[RequestContext], Awaitable[object]], + ) -> object: """执行中间件链 + handler。 洋葱模型:before 顺序执行 → handler → after 逆序执行。 @@ -125,7 +125,7 @@ class SummarizationMiddleware: after: 无操作(压缩在 before 完成) """ - def __init__(self, compressor: Any = None) -> None: + def __init__(self, compressor: object | None = None) -> None: self._compressor = compressor async def before(self, ctx: RequestContext) -> RequestContext: @@ -143,7 +143,7 @@ class SummarizationMiddleware: logger.warning(f"SummarizationMiddleware: compression failed: {e}") return ctx - async def after(self, ctx: RequestContext, result: Any) -> Any: + async def after(self, ctx: RequestContext, result: object) -> object: return result @@ -157,7 +157,7 @@ class TokenUsageMiddleware: async def before(self, ctx: RequestContext) -> RequestContext: return ctx - async def after(self, ctx: RequestContext, result: Any) -> Any: + async def after(self, ctx: RequestContext, result: object) -> object: # 从 ReActResult 或类似结构提取 token 用量 # ReActResult 有 total_tokens 属性(非 token_usage) usage = getattr(result, "total_tokens", None) @@ -184,7 +184,7 @@ class LoopDetectionMiddleware: ctx.metadata["loop_detection_window"] = [] return ctx - async def after(self, ctx: RequestContext, result: Any) -> Any: + async def after(self, ctx: RequestContext, result: object) -> object: trajectory = getattr(result, "trajectory", None) or [] if len(trajectory) < self._threshold: return result diff --git a/src/agentkit/core/plan_executor.py b/src/agentkit/core/plan_executor.py index 4f736e1..99c400b 100644 --- a/src/agentkit/core/plan_executor.py +++ b/src/agentkit/core/plan_executor.py @@ -16,9 +16,9 @@ import asyncio import logging import random import time -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum -from typing import Any, Callable, Awaitable +from typing import Callable, Awaitable from agentkit.core.plan_schema import ExecutionPlan, PlanStep, PlanStepStatus from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus @@ -42,7 +42,7 @@ class StepExecutionResult: step_id: str status: PlanStepStatus - result: dict[str, Any] | None = None + result: dict[str, object] | None = None error: str | None = None retry_count: int = 0 duration_ms: float = 0.0 @@ -91,7 +91,7 @@ class PlanExecutor: def __init__( self, - agent_pool: Any, + agent_pool: object, max_retries: int = 2, step_timeout: float = 300.0, max_parallel: int = 5, @@ -207,7 +207,7 @@ class PlanExecutor: async def _execute_step_with_retry( self, step: PlanStep, - input_data: dict[str, Any], + input_data: dict[str, object], original_task: TaskMessage, ) -> StepExecutionResult: """执行单个步骤,支持重试 @@ -281,9 +281,9 @@ class PlanExecutor: async def _execute_step_once( self, step: PlanStep, - input_data: dict[str, Any], + input_data: dict[str, object], original_task: TaskMessage, - ) -> dict[str, Any]: + ) -> dict[str, object]: """执行单个步骤一次 通过 AgentPool 创建 Agent 执行步骤。 @@ -463,7 +463,7 @@ class PlanExecutor: self, step: PlanStep, step_results: dict[str, StepExecutionResult], - ) -> dict[str, Any]: + ) -> dict[str, object]: """将依赖步骤的结果注入到当前步骤的输入中 兼容 Orchestrator 的 subtask_results 累积模式。 @@ -471,7 +471,7 @@ class PlanExecutor: enriched = dict(step.input_data) if step.dependencies: - dep_results: dict[str, dict[str, Any]] = {} + dep_results: dict[str, dict[str, object]] = {} for dep_id in step.dependencies: if dep_id in step_results: dep_result = step_results[dep_id] diff --git a/src/agentkit/core/react.py b/src/agentkit/core/react.py index 9a0431c..48fc1f4 100644 --- a/src/agentkit/core/react.py +++ b/src/agentkit/core/react.py @@ -13,7 +13,7 @@ from collections import Counter, deque from collections.abc import AsyncGenerator from dataclasses import dataclass, field from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Awaitable, Callable from agentkit.core.exceptions import LLMProviderError, LoopDetectedError, TaskCancelledError, TaskTimeoutError from agentkit.core.protocol import CancellationToken @@ -43,13 +43,13 @@ class ReActStep: step: int action: str # "tool_call" or "final_answer" tool_name: str | None = None - arguments: dict[str, Any] | None = None - result: Any = None + arguments: dict[str, object] | None = None + result: object = None content: str | None = None tokens: int = 0 -async def _ensure_async_iterable(obj: Any, label: str = ""): +async def _ensure_async_iterable(obj: object, label: str = ""): """Defensive helper: ensure the given object is an async iterable. Guards against the recurring ``'async for' requires an object with @@ -133,7 +133,7 @@ class ReActEvent: event_type: str # "thinking","token","tool_call","tool_result","confirmation_request","confirmation_result","phase_violation","step","final_answer","final_result","error" step: int - data: dict[str, Any] = field(default_factory=dict) + data: dict[str, object] = field(default_factory=dict) timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat()) @@ -230,7 +230,7 @@ class ReActEngine: # step and yields phase_violation ReActEvents. Non-streaming execute() # simply ignores the accumulator (the error dict returned to the LLM is # the only signal there). - self._phase_violations: list[dict[str, Any]] = [] + self._phase_violations: list[dict[str, object]] = [] def reset(self) -> None: """Reset internal state for reuse across conversations. @@ -299,8 +299,8 @@ class ReActEngine: return False def _check_phase_permission( - self, tool_name: str, arguments: dict[str, Any] - ) -> dict[str, Any] | None: + self, tool_name: str, arguments: dict[str, object] + ) -> dict[str, object] | None: """Return None if tool is allowed; return a structured error dict if blocked. The error dict replaces what `_execute_tool` would have returned — @@ -351,7 +351,7 @@ class ReActEngine: return violation return None - def _check_tool_loop(self, tool_calls: list[Any]) -> str | None: + def _check_tool_loop(self, tool_calls: list[object]) -> str | None: """检测重复工具调用模式。 将当前步的工具调用 hash 加入滑动窗口,若同一 hash 在窗口内出现 @@ -406,10 +406,10 @@ class ReActEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, timeout_seconds: float | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> ReActResult: """执行 ReAct 循环 @@ -536,7 +536,7 @@ class ReActEngine: async def _run_loop_and_extract( self, - **kwargs: Any, + **kwargs: object, ) -> ReActResult: """Collect all events from _execute_loop and extract the final ReActResult. @@ -564,9 +564,9 @@ class ReActEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, stream: bool = False, effective_timeout: float = 0.0, ) -> AsyncGenerator[ReActEvent, None]: @@ -666,7 +666,7 @@ class ReActEngine: ) # 构建初始消息 - conversation: list[dict[str, Any]] = [] + conversation: list[dict[str, object]] = [] system_content = self._build_system_message( stable=system_prompt or "", volatile=memory_context, @@ -726,7 +726,7 @@ class ReActEngine: # 流式模式:用 chat_stream,yield token events stream_content_chunks: list[str] = [] stream_usage = None - stream_tool_calls: list[Any] = [] + stream_tool_calls: list[object] = [] stream_model = model # U3/G8: delta_flush 节流 buffer _flush_buffer: list[str] = [] @@ -840,7 +840,7 @@ class ReActEngine: ) # Act: 记录 assistant 消息(含 tool_calls)到对话历史 - assistant_msg: dict[str, Any] = { + assistant_msg: dict[str, object] = { "role": "assistant", "content": response.content or "", "tool_calls": [ @@ -874,7 +874,7 @@ class ReActEngine: if i in parallelizable_set ] - all_results: list[Any] = [None] * len(response.tool_calls) + all_results: list[object] = [None] * len(response.tool_calls) # Execute serial tools first (handles confirmation flow) for i, tc in serial_calls: @@ -1452,10 +1452,10 @@ class ReActEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, timeout_seconds: float | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> AsyncGenerator[ReActEvent, None]: """Execute ReAct loop, yielding ReActEvent objects. @@ -1514,7 +1514,7 @@ class ReActEngine: volatile: str, *, model: str, - ) -> str | list[dict[str, Any]] | None: + ) -> str | list[dict[str, object]] | None: """构建双块结构 system message(stable + volatile)。 - prompt_cache_enable=False 或无 stable+volatile → 返回 str(或 None) @@ -1535,7 +1535,7 @@ class ReActEngine: provider_name = self._get_provider_name(model) if provider_name == "anthropic": - blocks: list[dict[str, Any]] = [] + blocks: list[dict[str, object]] = [] if stable: blocks.append( { @@ -1675,8 +1675,8 @@ class ReActEngine: @staticmethod def _build_response_from_stream( content: str, - tool_calls: list[Any], - usage: Any, + tool_calls: list[object], + usage: object, model: str, ) -> LLMResponse: """Build an LLMResponse from accumulated stream chunks.""" @@ -1723,7 +1723,7 @@ class ReActEngine: async def _build_tool_result_message( self, tool_call_id: str, - result: Any, + result: object, compressor: "CompressionStrategy | None" = None, tool_name: str | None = None, ) -> dict: @@ -1742,7 +1742,7 @@ class ReActEngine: } async def _execute_tool( - self, tool_name: str, arguments: dict[str, Any], tools: list[Tool] + self, tool_name: str, arguments: dict[str, object], tools: list[Tool] ) -> dict: """执行工具调用,处理成功和失败情况""" # U3/G6: phase enforcement — check before dispatch. If the tool is @@ -1788,11 +1788,11 @@ class ReActEngine: async def _execute_tool_with_confirmation( self, - tc: Any, + tc: object, tools: list[Tool], step: int, - confirmation_handler: Any, - ) -> tuple[Any, list[ReActEvent]]: + confirmation_handler: Callable[..., Awaitable[object]] | None, + ) -> tuple[object, list[ReActEvent]]: """Execute a tool call with confirmation flow support. Used in the parallel execution path for serial (non-parallelizable) tools @@ -1886,7 +1886,7 @@ class ReActEngine: return tool_result, events - def _should_execute_parallel(self, tool_calls: list[Any]) -> bool: + def _should_execute_parallel(self, tool_calls: list[object]) -> bool: """Determine if tool calls should be executed in parallel. - parallel_tools=True: always parallel (if >1 tool) @@ -1905,7 +1905,7 @@ class ReActEngine: return len(parallelizable_indices) > 1 return False - def _get_parallelizable_indices(self, tool_calls: list[Any]) -> list[int]: + def _get_parallelizable_indices(self, tool_calls: list[object]) -> list[int]: """Get indices of tool_calls that have _parallelizable=true in arguments. LLM marks parallelizable tools by including _parallelizable: true @@ -1918,7 +1918,7 @@ class ReActEngine: indices.append(i) return indices - def _parse_text_tool_calls(self, content: str) -> list[dict[str, Any]]: + def _parse_text_tool_calls(self, content: str) -> list[dict[str, object]]: """从文本中解析工具调用模式 支持格式: @@ -1926,7 +1926,7 @@ class ReActEngine: 2. ```tool\n{"name": "...", "arguments": {...}}\n``` 3. \n{"name": "...", "arguments": {...}}\n """ - calls: list[dict[str, Any]] = [] + calls: list[dict[str, object]] = [] # 格式 1: Action: tool_name(args) action_pattern = re.compile(r"Action:\s*(\w+)\((.+?)\)", re.DOTALL) @@ -1999,7 +1999,7 @@ class ReActEngine: return calls @staticmethod - def _extract_tool_call_from_malformed(text: str) -> dict[str, Any] | None: + def _extract_tool_call_from_malformed(text: str) -> dict[str, object] | None: """从畸形文本中尝试提取工具调用。 处理场景: @@ -2066,7 +2066,7 @@ class ReActEngine: return None name = name_match.group(1) - arguments: dict[str, Any] = {} + arguments: dict[str, object] = {} # 提取 "key": "value" 模式 for kv_match in re.finditer(r'"(\w+)"\s*:\s*"([^"]*)"', text): key = kv_match.group(1) diff --git a/src/agentkit/core/rewoo.py b/src/agentkit/core/rewoo.py index 1d19d37..32426e6 100644 --- a/src/agentkit/core/rewoo.py +++ b/src/agentkit/core/rewoo.py @@ -11,7 +11,7 @@ import logging import re import time from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Awaitable, Callable from agentkit.core.exceptions import LLMProviderError, TaskCancelledError, TaskTimeoutError from agentkit.core.protocol import CancellationToken @@ -52,7 +52,7 @@ class ReWOOPlanStep: step_id: int tool_name: str - arguments: dict[str, Any] + arguments: dict[str, object] reasoning: str = "" @@ -164,10 +164,10 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, timeout_seconds: float | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> ReActResult: """执行 ReWOO 三阶段流程 @@ -241,9 +241,9 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> ReActResult: tools = tools or [] tool_schemas = self._build_tool_schemas(tools) if tools else None @@ -350,7 +350,7 @@ class ReWOOEngine: # 如果计划为空(无需工具),直接让 LLM 回答 if not plan.steps: - llm_messages: list[dict[str, Any]] = [] + llm_messages: list[dict[str, object]] = [] if effective_system_prompt: llm_messages.append({"role": "system", "content": effective_system_prompt}) llm_messages.extend(messages) @@ -399,7 +399,7 @@ class ReWOOEngine: ) # ── Phase 2: Execution ── - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: # 协作式取消检查 if cancellation_token is not None: @@ -524,10 +524,10 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, timeout_seconds: float | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ): """Execute ReWOO flow, yielding ReActEvent objects. @@ -637,7 +637,7 @@ class ReWOOEngine: # Empty plan: direct answer if not plan.steps: - llm_messages: list[dict[str, Any]] = [] + llm_messages: list[dict[str, object]] = [] if effective_system_prompt: llm_messages.append({"role": "system", "content": effective_system_prompt}) llm_messages.extend(messages) @@ -676,7 +676,7 @@ class ReWOOEngine: return # ── Phase 2: Execution ── - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: if cancellation_token is not None: cancellation_token.check() @@ -806,10 +806,10 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, total_tokens: int = 0, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ): """Stream version: try fallback strategies in configured order, yielding events from the first successful one. @@ -902,7 +902,7 @@ class ReWOOEngine: "reasoning": plan.reasoning, "steps": [{"step_id": s.step_id, "tool_name": s.tool_name, "arguments": s.arguments, "reasoning": s.reasoning} for s in plan.steps], }) - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: if cancellation_token is not None: cancellation_token.check() @@ -934,9 +934,9 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ): """Stream: ReAct fallback""" logger.warning("ReWOO planning failed in stream mode, falling back to ReActEngine") @@ -969,7 +969,7 @@ class ReWOOEngine: """Stream: Direct LLM fallback""" logger.warning("Falling back to direct LLM call in stream mode") try: - direct_messages: list[dict[str, Any]] = [] + direct_messages: list[dict[str, object]] = [] if effective_system_prompt: direct_messages.append({"role": "system", "content": effective_system_prompt}) direct_messages.extend(messages) @@ -1012,7 +1012,7 @@ class ReWOOEngine: "reasoning": plan.reasoning, "steps": [{"step_id": s.step_id, "tool_name": s.tool_name, "arguments": s.arguments, "reasoning": s.reasoning} for s in plan.steps], }) - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: if cancellation_token is not None: cancellation_token.check() @@ -1043,11 +1043,11 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, trajectory: list[ReActStep] | None = None, total_tokens: int = 0, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> ReActResult | None: """按配置的 fallback 策略顺序尝试回退,返回第一个成功的结果 @@ -1136,7 +1136,7 @@ class ReWOOEngine: # Execute the simplified plan trajectory: list[ReActStep] = [] total_tokens = simplified_tokens - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: if cancellation_token is not None: cancellation_token.check() @@ -1195,9 +1195,9 @@ class ReWOOEngine: memory_retriever: "MemoryRetriever | None" = None, task_id: str | None = None, compressor: "CompressionStrategy | None" = None, - retrieval_config: dict[str, Any] | None = None, + retrieval_config: dict[str, object] | None = None, cancellation_token: CancellationToken | None = None, - confirmation_handler: Any | None = None, + confirmation_handler: Callable[..., Awaitable[object]] | None = None, ) -> ReActResult | None: """ReAct fallback: delegate to ReActEngine""" logger.warning("ReWOO planning failed, falling back to ReActEngine") @@ -1240,7 +1240,7 @@ class ReWOOEngine: """Direct fallback: simple LLM call without tools""" logger.warning("Falling back to direct LLM call") try: - direct_messages: list[dict[str, Any]] = [] + direct_messages: list[dict[str, object]] = [] if effective_system_prompt: direct_messages.append({"role": "system", "content": effective_system_prompt}) direct_messages.extend(messages) @@ -1319,7 +1319,7 @@ class ReWOOEngine: if plan is not None and plan.steps: trajectory: list[ReActStep] = [] total_tokens = plan_tokens - tool_results: list[dict[str, Any]] = [] + tool_results: list[dict[str, object]] = [] for plan_step in plan.steps: if cancellation_token is not None: cancellation_token.check() @@ -1396,7 +1396,7 @@ class ReWOOEngine: tool_descriptions = self._build_tool_descriptions(tools) # 构建规划消息 - planning_messages: list[dict[str, Any]] = [ + planning_messages: list[dict[str, object]] = [ {"role": "system", "content": _PLANNING_SYSTEM_PROMPT}, ] @@ -1451,7 +1451,7 @@ class ReWOOEngine: async def _synthesis_phase( self, messages: list[dict[str, str]], - tool_results: list[dict[str, Any]], + tool_results: list[dict[str, object]], model: str, agent_name: str, task_type: str, @@ -1468,7 +1468,7 @@ class ReWOOEngine: cancellation_token.check() # 构建综合消息 - synthesis_messages: list[dict[str, Any]] = [ + synthesis_messages: list[dict[str, object]] = [ {"role": "system", "content": _SYNTHESIS_SYSTEM_PROMPT}, ] @@ -1600,7 +1600,7 @@ class ReWOOEngine: return None async def _execute_tool( - self, tool_name: str, arguments: dict[str, Any], tools: list[Tool] + self, tool_name: str, arguments: dict[str, object], tools: list[Tool] ) -> dict: """执行工具调用,处理成功和失败情况""" tool = self._find_tool(tool_name, tools) diff --git a/src/agentkit/evolution/ab_tester.py b/src/agentkit/evolution/ab_tester.py index b3a3b2d..9a01572 100644 --- a/src/agentkit/evolution/ab_tester.py +++ b/src/agentkit/evolution/ab_tester.py @@ -8,6 +8,8 @@ import math from dataclasses import dataclass from typing import TYPE_CHECKING +from sqlalchemy.exc import DBAPIError + if TYPE_CHECKING: from agentkit.evolution.evolution_store import InMemoryEvolutionStore @@ -129,7 +131,7 @@ class ABTester: sample_count=len(experiment_metrics), ) logger.info(f"A/B test results persisted for test '{test_id}'") - except Exception as e: + except (DBAPIError, RuntimeError, ValueError, KeyError) as e: logger.error(f"Failed to persist A/B test results: {e}") async def evaluate(self, test_id: str) -> ABTestResult | None: diff --git a/src/agentkit/evolution/experience_store.py b/src/agentkit/evolution/experience_store.py index 5e47d13..642e02b 100644 --- a/src/agentkit/evolution/experience_store.py +++ b/src/agentkit/evolution/experience_store.py @@ -10,6 +10,7 @@ from __future__ import annotations +import asyncio import logging import math import re @@ -18,6 +19,7 @@ from datetime import datetime, timedelta, timezone from typing import Any from sqlalchemy import text +from sqlalchemy.exc import DBAPIError _SAFE_TABLE_NAME_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') @@ -96,7 +98,7 @@ class ExperienceStore: text = experience.text_for_embedding() try: experience.embedding = await self._embedder.embed(text) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"Failed to generate embedding for experience {experience.experience_id}: {e}") async with self._session_factory() as db: @@ -122,7 +124,7 @@ class ExperienceStore: f"task_type={experience.task_type} outcome={experience.outcome}" ) return experience.experience_id - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to record experience: {e}") raise @@ -149,7 +151,7 @@ class ExperienceStore: if self._pgvector_enabled and self._embedder: return await self._search_pgvector(db, query, top_k, task_type, search_multiplier) return await self._search_client_side(db, query, top_k, task_type, search_multiplier) - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to search experiences: {e}") return [] @@ -343,7 +345,7 @@ class ExperienceStore: ) ) return metrics_list - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to get metrics: {e}") return [] @@ -375,7 +377,7 @@ class InMemoryExperienceStore: text = experience.text_for_embedding() try: experience.embedding = await self._embedder.embed(text) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"Failed to generate embedding for experience {experience.experience_id}: {e}") # 存储副本,避免外部修改影响内部状态 @@ -411,7 +413,7 @@ class InMemoryExperienceStore: if self._embedder: try: query_embedding = await self._embedder.embed(query) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"Failed to generate query embedding: {e}") # 筛选候选 diff --git a/src/agentkit/evolution/genetic.py b/src/agentkit/evolution/genetic.py index 38d9e4e..57beee1 100644 --- a/src/agentkit/evolution/genetic.py +++ b/src/agentkit/evolution/genetic.py @@ -13,6 +13,7 @@ from __future__ import annotations +import asyncio import copy import logging import random @@ -292,7 +293,7 @@ class MutationOperator: model="default", ) return response.content.strip() or instructions - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"LLM instruction mutation failed: {e}") # Fallback: simple text mutation (shuffle paragraphs) diff --git a/src/agentkit/evolution/lifecycle.py b/src/agentkit/evolution/lifecycle.py index 1cce14f..d0ef216 100644 --- a/src/agentkit/evolution/lifecycle.py +++ b/src/agentkit/evolution/lifecycle.py @@ -8,6 +8,8 @@ from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Any +from sqlalchemy.exc import DBAPIError + from agentkit.core.protocol import EvolutionEvent, TaskMessage, TaskResult from agentkit.evolution.ab_tester import ABTestConfig, ABTestResult, ABTester from agentkit.evolution.evolution_store import EvolutionStore @@ -371,7 +373,7 @@ class EvolutionMixin: entry.event_id = event_id break return True - except Exception as e: + except (DBAPIError, RuntimeError, ValueError, KeyError) as e: logger.error(f"Failed to apply evolution change: {e}") return False @@ -382,7 +384,7 @@ class EvolutionMixin: try: return await self._evolution_store.rollback(log_entry.event_id) - except Exception as e: + except (DBAPIError, RuntimeError, ValueError, KeyError) as e: logger.error(f"Failed to rollback evolution change: {e}") return False diff --git a/src/agentkit/evolution/llm_reflector.py b/src/agentkit/evolution/llm_reflector.py index 91a334a..aaf7fc4 100644 --- a/src/agentkit/evolution/llm_reflector.py +++ b/src/agentkit/evolution/llm_reflector.py @@ -3,6 +3,7 @@ 通过 LLM 分析执行轨迹生成结构化反思,比 RuleBasedReflector 提供更深入的洞察。 """ +import asyncio import json import logging import re @@ -62,7 +63,7 @@ class LLMReflector: task_type="reflection", ) return self._parse_reflection_response(response.content, task, result) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError, KeyError) as e: logger.warning(f"LLM reflection failed, returning default: {e}") return Reflection( task_id=getattr(task, "task_id", "unknown"), diff --git a/src/agentkit/evolution/pg_store.py b/src/agentkit/evolution/pg_store.py index aa0571a..ca2b17b 100644 --- a/src/agentkit/evolution/pg_store.py +++ b/src/agentkit/evolution/pg_store.py @@ -13,6 +13,7 @@ from typing import Any from sqlalchemy import select from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy import Column, DateTime, Float, Integer, String, Text, UniqueConstraint +from sqlalchemy.exc import DBAPIError from sqlalchemy.orm import declarative_base from agentkit.core.protocol import EvolutionEvent @@ -154,7 +155,7 @@ class PostgreSQLEvolutionStore: event.event_id = event_id logger.info(f"Evolution event recorded: {event_id} for agent '{event.agent_name}'") return event_id - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to record evolution event: {e}") raise @@ -178,7 +179,7 @@ class PostgreSQLEvolutionStore: await db.commit() logger.info(f"Evolution event {event_id} rolled back") return True - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to rollback evolution event {event_id}: {e}") return False @@ -218,7 +219,7 @@ class PostgreSQLEvolutionStore: } for e in entries ] - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to list evolution events: {e}") return [] @@ -242,7 +243,7 @@ class PostgreSQLEvolutionStore: db.add(entry) await db.commit() return vid - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to record skill version: {e}") raise @@ -271,7 +272,7 @@ class PostgreSQLEvolutionStore: } for e in entries ] - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to list skill versions: {e}") return [] @@ -295,7 +296,7 @@ class PostgreSQLEvolutionStore: db.add(entry) await db.commit() return rid - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to record A/B test result: {e}") raise @@ -324,6 +325,6 @@ class PostgreSQLEvolutionStore: } for e in entries ] - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to get A/B test results: {e}") return [] diff --git a/src/agentkit/evolution/pitfall_detector.py b/src/agentkit/evolution/pitfall_detector.py index 31a1abd..c50205e 100644 --- a/src/agentkit/evolution/pitfall_detector.py +++ b/src/agentkit/evolution/pitfall_detector.py @@ -151,7 +151,7 @@ class PitfallDetector: task_type=task_type, ) return results - except Exception as e: + except (RuntimeError, ValueError, KeyError) as e: logger.error(f"Failed to search experiences for pitfall detection: {e}") return [] diff --git a/src/agentkit/evolution/prompt_optimizer.py b/src/agentkit/evolution/prompt_optimizer.py index 2bf9c99..a5dc655 100644 --- a/src/agentkit/evolution/prompt_optimizer.py +++ b/src/agentkit/evolution/prompt_optimizer.py @@ -10,6 +10,7 @@ - LLMPromptOptimizer: 基于 LLM 分析反思结果生成改进指令 """ +import asyncio import logging from dataclasses import dataclass, field from typing import Any @@ -202,7 +203,7 @@ class LLMPromptOptimizer: """ try: optimized_instruction = await self._llm_optimize_instruction(module, trace, reflection) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"LLM prompt optimization failed, falling back to bootstrap: {e}") return await self._bootstrap.optimize(module) diff --git a/src/agentkit/evolution/risk_guard_learner.py b/src/agentkit/evolution/risk_guard_learner.py index 06a658f..5a62341 100644 --- a/src/agentkit/evolution/risk_guard_learner.py +++ b/src/agentkit/evolution/risk_guard_learner.py @@ -8,6 +8,7 @@ from __future__ import annotations +import asyncio import json import logging import re @@ -79,7 +80,7 @@ class RiskGuardLearner: top_k=top_k, task_type=skill_name, ) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError, KeyError) as e: logger.warning(f"RiskGuardLearner: failed to search experiences: {e}") return [] @@ -95,7 +96,7 @@ class RiskGuardLearner: # 2. 构建 LLM prompt try: prompt = self._build_prompt(failures) - except Exception as e: + except (ValueError, KeyError, RuntimeError) as e: logger.warning(f"RiskGuardLearner: failed to build prompt: {e}") return [] @@ -117,14 +118,14 @@ class RiskGuardLearner: agent_name="risk_guard_learner", task_type="risk_guard_learning", ) - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"RiskGuardLearner: LLM call failed: {e}") return [] # 4. 解析响应 try: return self._parse_response(response.content, source_ids) - except Exception as e: + except (ValueError, KeyError, TypeError) as e: logger.warning(f"RiskGuardLearner: failed to parse response: {e}") return [] diff --git a/src/agentkit/experts/_debate_runner.py b/src/agentkit/experts/_debate_runner.py index c56cba2..be9a829 100644 --- a/src/agentkit/experts/_debate_runner.py +++ b/src/agentkit/experts/_debate_runner.py @@ -9,7 +9,7 @@ import asyncio import json import logging import re -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from .expert import Expert from .plan import PhaseStatus, PlanPhase, TeamPlan @@ -28,7 +28,7 @@ class DebateRunnerMixin: _phase_semaphore: asyncio.Semaphore MAX_DEBATE_ROUNDS: int - async def _execute_debate_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, Any]: + async def _execute_debate_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, object]: """Execute a DEBATE phase: Lead-facilitated structured debate (5 stages). Parse config → Lead opens → experts argue in parallel rounds → Lead summarizes → Lead adjudicates → write conclusion to workspace.""" @@ -86,7 +86,7 @@ class DebateRunnerMixin: ) # Debate history for context (Lead opening + expert arguments + Lead summaries) - history: list[dict[str, Any]] = [ + history: list[dict[str, object]] = [ {"expert": lead.config.name, "content": opening, "round": 0, "role": "moderator"} ] @@ -103,7 +103,7 @@ class DebateRunnerMixin: break # Experts argue in parallel (with concurrency limit) - async def _bounded_debate(e: Any) -> str: + async def _bounded_debate(e: object) -> str: async with self._phase_semaphore: return await self._generate_debate_argument(e, topic, history, round_num) @@ -234,7 +234,7 @@ class DebateRunnerMixin: return f"辩论主题:{topic}。请各位专家发表看法。" async def _generate_debate_argument( - self, expert: Expert, topic: str, history: list[dict[str, Any]], round_num: int + self, expert: Expert, topic: str, history: list[dict[str, object]], round_num: int ) -> str: """Generate an expert's debate argument for the current round.""" gateway = self._get_llm_gateway(expert) @@ -269,7 +269,7 @@ class DebateRunnerMixin: return response.content.strip() async def _generate_debate_summary( - self, lead: Expert, topic: str, history: list[dict[str, Any]], round_num: int + self, lead: Expert, topic: str, history: list[dict[str, object]], round_num: int ) -> str: """Generate Lead's summary of the current debate round.""" gateway = self._get_llm_gateway(lead) @@ -307,8 +307,8 @@ class DebateRunnerMixin: return f"[第 {round_num} 轮辩论完成,小结生成失败]" async def _generate_debate_verdict( - self, lead: Expert, topic: str, history: list[dict[str, Any]] - ) -> dict[str, Any]: + self, lead: Expert, topic: str, history: list[dict[str, object]] + ) -> dict[str, object]: """Generate Lead's final verdict for the debate.""" gateway = self._get_llm_gateway(lead) if not gateway: @@ -371,7 +371,7 @@ class DebateRunnerMixin: "conclusion": f"辩论主题:{topic}。裁决生成失败,建议参考辩论历史自行判断。", } - def _format_debate_history(self, history: list[dict[str, Any]]) -> str: + def _format_debate_history(self, history: list[dict[str, object]]) -> str: """Format debate history as readable text for LLM prompts.""" if not history: return "" diff --git a/src/agentkit/experts/_divergence_detector.py b/src/agentkit/experts/_divergence_detector.py index e8ad0f0..9845d17 100644 --- a/src/agentkit/experts/_divergence_detector.py +++ b/src/agentkit/experts/_divergence_detector.py @@ -6,7 +6,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from .expert import Expert from .plan import PhaseStatus, PhaseType, PlanPhase, TeamPlan @@ -23,7 +23,7 @@ class DivergenceDetectorMixin: # Shared state provided by TeamOrchestrator (annotations only) _team: ExpertTeam _debate_count: int - _checkpoint: Any + _checkpoint: object MAX_DEBATES: int async def _maybe_add_plan_review_debate(self, lead: Expert, plan: TeamPlan, task: str) -> None: diff --git a/src/agentkit/experts/_phase_executor.py b/src/agentkit/experts/_phase_executor.py index 3d94322..dbdc1bd 100644 --- a/src/agentkit/experts/_phase_executor.py +++ b/src/agentkit/experts/_phase_executor.py @@ -9,7 +9,7 @@ import asyncio import copy import logging from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from agentkit.core.config_driven import ConfigDrivenAgent from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus @@ -37,7 +37,7 @@ class PhaseExecutorMixin: # U4: State offloading helpers — keep memory lean for long-horizon runs. _OFFLOAD_SUMMARY_LIMIT = 500 - def _offload_result(self, content: str, ref_key: str) -> dict[str, Any]: + def _offload_result(self, content: str, ref_key: str) -> dict[str, object]: """Create an offloaded result: summary in memory, full content in workspace.""" if not isinstance(content, str): content = str(content) if content is not None else "" @@ -64,17 +64,17 @@ class PhaseExecutorMixin: logger.warning(f"Failed to read offloaded output '{ref_key}': {e}") return content - async def _execute_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, Any]: + async def _execute_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, object]: """Execute a single phase, dispatching by phase_type.""" if phase.phase_type == PhaseType.DEBATE: return await self._execute_debate_phase(phase, plan) return await self._execute_execution_phase(phase, plan) - async def _execute_execution_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, Any]: + async def _execute_execution_phase(self, phase: PlanPhase, plan: TeamPlan) -> dict[str, object]: """Execute a standard EXECUTION phase. Split into 3 sub-methods (U2, KTD3 isolation).""" expert, agent, lead = await self._prepare_phase_context(phase, plan) last_error: str | None = None - result: dict[str, Any] | None = None + result: dict[str, object] | None = None try: # U3: 返工循环 — 最多 MAX_REWORKS + 1 次(1 次初始 + MAX_REWORKS 次返工) @@ -135,11 +135,11 @@ class PhaseExecutorMixin: self, expert: Expert, phase: PlanPhase, - dependency_outputs: dict[str, Any], + dependency_outputs: dict[str, object], collaboration_outputs: dict[str, str], ) -> TaskMessage: """Build TaskMessage for execution with context isolation.""" - input_data: dict[str, Any] = { + input_data: dict[str, object] = { "task": phase.task_description, "team_id": self._team.team_id, "phase_id": phase.id, @@ -180,12 +180,12 @@ class PhaseExecutorMixin: lead: Expert, phase: PlanPhase, plan: TeamPlan, - ) -> tuple[dict[str, Any], str | None, bool, str, bool]: + ) -> tuple[dict[str, object], str | None, bool, str, bool]: """Run one rework iteration: read deps, build input, execute, review. Returns (result, last_error, passed, feedback, degraded). Raises RuntimeError on retry exhaustion.""" # 每次迭代重新读取依赖输出(前置阶段可能在返工期间完成) - dependency_outputs: dict[str, Any] = {} + dependency_outputs: dict[str, object] = {} for dep_id in phase.depends_on: dep_phase = plan.get_phase(dep_id) if dep_phase and dep_phase.status == PhaseStatus.COMPLETED and dep_phase.result: @@ -216,7 +216,7 @@ class PhaseExecutorMixin: # 执行专家任务(带重试,MAX_RETRIES 处理瞬时失败) last_error: str | None = None - result: dict[str, Any] | None = None + result: dict[str, object] | None = None for attempt in range(self.MAX_RETRIES + 1): try: task_result: TaskResult = await agent.execute(task_msg) @@ -265,7 +265,7 @@ class PhaseExecutorMixin: lead: Expert, phase: PlanPhase, plan: TeamPlan, - result: dict[str, Any], + result: dict[str, object], passed: bool, feedback: str, degraded: bool = False, diff --git a/src/agentkit/experts/_review_gate.py b/src/agentkit/experts/_review_gate.py index 36523ba..dd642c4 100644 --- a/src/agentkit/experts/_review_gate.py +++ b/src/agentkit/experts/_review_gate.py @@ -10,7 +10,6 @@ import json import logging import re from dataclasses import dataclass -from typing import Any from agentkit.core.exceptions import LLMProviderError @@ -45,7 +44,7 @@ class ReviewGateMixin: """Mixin: Lead 验收阶段输出质量 + 解析风险标记。由 TeamOrchestrator 组合。""" async def _review_phase_output( - self, lead: Expert, phase: PlanPhase, result: dict[str, Any] + self, lead: Expert, phase: PlanPhase, result: dict[str, object] ) -> ReviewResult: """Lead 验收阶段输出质量。 @@ -93,7 +92,7 @@ class ReviewGateMixin: ) # P2: 优先尝试直接解析整个响应为 JSON,避免贪婪正则匹配过多 - review: dict[str, Any] | None = None + review: dict[str, object] | None = None try: review = json.loads(response.content) except (json.JSONDecodeError, TypeError): diff --git a/src/agentkit/experts/_rollback_handler.py b/src/agentkit/experts/_rollback_handler.py index e16890f..af2bb27 100644 --- a/src/agentkit/experts/_rollback_handler.py +++ b/src/agentkit/experts/_rollback_handler.py @@ -6,7 +6,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from agentkit.orchestrator.rollback import RollbackExecutor @@ -27,7 +27,7 @@ class RollbackHandlerMixin: _rollback_timeout: float async def _mark_dependents_failed( - self, failed_phase_id: str, plan: TeamPlan, phase_results: dict[str, dict[str, Any]] + self, failed_phase_id: str, plan: TeamPlan, phase_results: dict[str, dict[str, object]] ) -> None: """Mark all phases that depend on the failed phase as FAILED.""" for ph in plan.phases: diff --git a/src/agentkit/experts/_synthesizer.py b/src/agentkit/experts/_synthesizer.py index a472715..0a6a2c9 100644 --- a/src/agentkit/experts/_synthesizer.py +++ b/src/agentkit/experts/_synthesizer.py @@ -7,7 +7,7 @@ from __future__ import annotations import logging from datetime import datetime, timezone -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from agentkit.core.protocol import TaskMessage, TaskResult @@ -29,7 +29,7 @@ class SynthesizerMixin: async def _synthesize_results( self, lead: Expert, task: str, completed_phases: list[PlanPhase] - ) -> dict[str, Any]: + ) -> dict[str, object]: """Lead Expert synthesizes results using BEST strategy. The Lead Expert evaluates all completed phase results and produces @@ -114,8 +114,8 @@ class SynthesizerMixin: self, task: str, plan: TeamPlan, - phase_results: dict[str, dict[str, Any]], - ) -> dict[str, Any]: + phase_results: dict[str, dict[str, object]], + ) -> dict[str, object]: """Fallback to single agent mode when pipeline execution fails. Uses the lead expert (or first active expert) to complete the original task. @@ -128,7 +128,7 @@ class SynthesizerMixin: active = self._team.active_experts expert = active[0] if active else None - fallback_result: dict[str, Any] | None = None + fallback_result: dict[str, object] | None = None if expert: try: task_msg = TaskMessage( diff --git a/src/agentkit/experts/board.py b/src/agentkit/experts/board.py index d82e28f..ec3221d 100644 --- a/src/agentkit/experts/board.py +++ b/src/agentkit/experts/board.py @@ -16,7 +16,6 @@ import enum import logging import time import uuid -from typing import Any from .config import ExpertConfig from .expert import Expert @@ -72,7 +71,7 @@ class BoardTeam: # Discussion state self._topic: str = "" - self._history: list[dict[str, Any]] = [] + self._history: list[dict[str, object]] = [] self._current_round: int = 0 self._max_rounds: int = max_rounds self._user_interventions: list[str] = [] # Pending user messages @@ -125,7 +124,7 @@ class BoardTeam: return self._max_rounds @property - def history(self) -> list[dict[str, Any]]: + def history(self) -> list[dict[str, object]]: return self._history.copy() def get_expert(self, name: str) -> Expert | None: @@ -254,7 +253,7 @@ class BoardTeam: return "\n\n---\n\n".join(lines) - async def compress_history(self, moderator: Expert, llm_gateway: Any) -> None: + async def compress_history(self, moderator: Expert, llm_gateway: object) -> None: """Compress discussion history when it exceeds token threshold. The moderator summarizes each round's key points, replacing @@ -291,7 +290,7 @@ class BoardTeam: # Parse compressed history back to entries # This is a best-effort compression; if parsing fails, keep original - new_history: list[dict[str, Any]] = [] + new_history: list[dict[str, object]] = [] current_round = 0 for line in compressed.split("\n"): line = line.strip() diff --git a/src/agentkit/experts/board_orchestrator.py b/src/agentkit/experts/board_orchestrator.py index a6b2276..d9d89e1 100644 --- a/src/agentkit/experts/board_orchestrator.py +++ b/src/agentkit/experts/board_orchestrator.py @@ -19,7 +19,6 @@ from __future__ import annotations import asyncio import logging -from typing import Any from .expert import Expert from .board import BoardTeam, BoardStatus @@ -43,7 +42,7 @@ class BoardOrchestrator: def __init__(self, team: BoardTeam) -> None: self._team = team - async def execute(self, topic: str) -> dict[str, Any]: + async def execute(self, topic: str) -> dict[str, object]: """Execute a board meeting discussion. Flow: @@ -351,7 +350,7 @@ class BoardOrchestrator: logger.warning(f"Moderator summary generation failed: {e}") return f"[第 {round} 轮讨论完成,主持人小结生成失败]" - async def _generate_final_conclusion(self, moderator: Expert, topic: str) -> dict[str, Any]: + async def _generate_final_conclusion(self, moderator: Expert, topic: str) -> dict[str, object]: """Generate moderator's final conclusion. The moderator gives: @@ -428,7 +427,7 @@ class BoardOrchestrator: "dissent_points": [], } - async def _generate_fallback_conclusion(self, moderator: Expert, topic: str) -> dict[str, Any]: + async def _generate_fallback_conclusion(self, moderator: Expert, topic: str) -> dict[str, object]: """Generate a fallback conclusion when execution fails. Uses existing discussion history to provide a basic summary. @@ -491,7 +490,7 @@ class BoardOrchestrator: return True return False - def _get_llm_gateway(self, expert: Expert | None = None) -> Any: + def _get_llm_gateway(self, expert: Expert | None = None) -> object: """Get LLM gateway from the given expert or the moderator's agent. Falls back to other active experts if the primary target has no gateway. @@ -509,7 +508,7 @@ class BoardOrchestrator: return gateway return None - async def _broadcast_event(self, event_type: str, data: dict[str, Any]) -> None: + async def _broadcast_event(self, event_type: str, data: dict[str, object]) -> None: """Broadcast a board event to the team channel. Events are emitted via handoff_transport for WebSocket relay. diff --git a/src/agentkit/experts/config.py b/src/agentkit/experts/config.py index b6b9a42..d60c6cb 100644 --- a/src/agentkit/experts/config.py +++ b/src/agentkit/experts/config.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Any from agentkit.core.config_driven import AgentConfig @@ -24,12 +23,12 @@ class ExpertConfig(AgentConfig): task_mode: str = "llm_generate", supported_tasks: list[str] | None = None, max_concurrency: int = 1, - input_schema: dict[str, Any] | None = None, - output_schema: dict[str, Any] | None = None, + input_schema: dict[str, object] | None = None, + output_schema: dict[str, object] | None = None, prompt: dict[str, str] | None = None, - llm: dict[str, Any] | None = None, + llm: dict[str, object] | None = None, tools: list[str] | None = None, - memory: dict[str, Any] | None = None, + memory: dict[str, object] | None = None, custom_handler: str | None = None, # Expert 专属字段 persona: str = "", @@ -70,7 +69,7 @@ class ExpertConfig(AgentConfig): self.decision_framework = decision_framework @classmethod - def from_dict(cls, data: dict[str, Any]) -> ExpertConfig: + def from_dict(cls, data: dict[str, object]) -> ExpertConfig: """从字典创建配置""" return cls( name=data["name"], @@ -98,7 +97,7 @@ class ExpertConfig(AgentConfig): decision_framework=data.get("decision_framework", ""), ) - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典,包含 Expert 专属字段""" d = super().to_dict() d["persona"] = self.persona @@ -125,7 +124,7 @@ class ExpertTemplate: is_builtin: bool = False description: str = "" - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" return { "name": self.name, @@ -135,7 +134,7 @@ class ExpertTemplate: } @classmethod - def from_dict(cls, data: dict[str, Any]) -> ExpertTemplate: + def from_dict(cls, data: dict[str, object]) -> ExpertTemplate: """从字典创建模板""" config_data = data["config"] config = ExpertConfig.from_dict(config_data) diff --git a/src/agentkit/experts/orchestrator.py b/src/agentkit/experts/orchestrator.py index bd1d10f..7202594 100644 --- a/src/agentkit/experts/orchestrator.py +++ b/src/agentkit/experts/orchestrator.py @@ -14,7 +14,6 @@ import asyncio import json import logging import re -from typing import Any from agentkit.core.exceptions import LLMProviderError from agentkit.llm.gateway import LLMGateway @@ -72,7 +71,7 @@ class TeamOrchestrator( self, team: ExpertTeam, max_concurrent_phases: int | None = None, - checkpoint: Any = None, + checkpoint: object | None = None, workspace_root: str | None = None, rollback_timeout: float | None = None, ) -> None: @@ -95,7 +94,7 @@ class TeamOrchestrator( self._workspace_root = workspace_root self._rollback_timeout = rollback_timeout or self.DEFAULT_ROLLBACK_TIMEOUT - async def execute(self, task: str) -> dict[str, Any]: + async def execute(self, task: str) -> dict[str, object]: """Execute a task in pipeline mode. Lead decomposes → topological sort → execute layers (parallel within layer) → synthesize. Returns dict with status/result/phase_results/plan.""" @@ -175,7 +174,7 @@ class TeamOrchestrator( # 4. Set EXECUTING status, execute phases self._team.set_status(TeamStatus.EXECUTING) - phase_results: dict[str, dict[str, Any]] = {} + phase_results: dict[str, dict[str, object]] = {} return await self._run_pipeline(lead, plan, phase_results, task) @@ -183,9 +182,9 @@ class TeamOrchestrator( self, lead: Expert, plan: TeamPlan, - phase_results: dict[str, dict[str, Any]], + phase_results: dict[str, dict[str, object]], task: str, - ) -> dict[str, Any]: + ) -> dict[str, object]: """Execute the pipeline loop: run pending phases, synthesize, return result. Shared by execute() and resume(). phase_results may be pre-populated @@ -220,7 +219,7 @@ class TeamOrchestrator( break # Execute all phases in this layer in parallel (with concurrency limit) - async def _bounded_phase(ph: PlanPhase) -> dict[str, Any]: + async def _bounded_phase(ph: PlanPhase) -> dict[str, object]: async with self._phase_semaphore: return await self._execute_phase(ph, plan) @@ -335,7 +334,7 @@ class TeamOrchestrator( await self._broadcast_event("team_dissolved", {"team_id": self._team.team_id}) return await self._fallback_to_single_agent(task, plan, phase_results) - async def resume(self, plan_id: str) -> dict[str, Any]: + async def resume(self, plan_id: str) -> dict[str, object]: """Resume from last checkpoint: load plan, restore completed/failed phases, continue via _run_pipeline. Returns same dict shape as execute().""" if self._checkpoint is None: @@ -362,7 +361,7 @@ class TeamOrchestrator( # 3. Load checkpoints, mark completed phases checkpoints = await self._checkpoint.list_checkpoints(plan_id) - phase_results: dict[str, dict[str, Any]] = {} + phase_results: dict[str, dict[str, object]] = {} completed_phase_ids: set[str] = set() failed_phase_ids: set[str] = set() @@ -492,7 +491,7 @@ class TeamOrchestrator( # First pass: create phases with IDs, build name->id mapping name_to_id: dict[str, str] = {} - raw_phases: list[dict[str, Any]] = [] + raw_phases: list[dict[str, object]] = [] for item in items: if not isinstance(item, dict): @@ -584,7 +583,7 @@ class TeamOrchestrator( return gateway return None - async def _broadcast_event(self, event_type: str, data: dict[str, Any]) -> None: + async def _broadcast_event(self, event_type: str, data: dict[str, object]) -> None: """Broadcast an orchestration event to the team channel via handoff_transport.""" if self._team.handoff_transport: try: diff --git a/src/agentkit/experts/plan.py b/src/agentkit/experts/plan.py index 2a95cf0..06f7b81 100644 --- a/src/agentkit/experts/plan.py +++ b/src/agentkit/experts/plan.py @@ -15,7 +15,6 @@ from __future__ import annotations import enum import uuid from dataclasses import dataclass, field -from typing import Any class MergeStrategy(str, enum.Enum): @@ -82,9 +81,9 @@ class SubTask: description: str = "" assigned_expert: str = "" status: SubTaskStatus = SubTaskStatus.PENDING - result: dict[str, Any] | None = None + result: dict[str, object] | None = None - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" return { "id": self.id, @@ -95,7 +94,7 @@ class SubTask: } @classmethod - def from_dict(cls, data: dict[str, Any]) -> SubTask: + def from_dict(cls, data: dict[str, object]) -> SubTask: """从字典创建 SubTask""" return cls( id=data.get("id", str(uuid.uuid4())), @@ -124,7 +123,7 @@ class CollaborationContract: content_description: str = "" status: str = "pending" - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" return { "from_expert": self.from_expert, @@ -134,7 +133,7 @@ class CollaborationContract: } @classmethod - def from_dict(cls, data: dict[str, Any]) -> CollaborationContract: + def from_dict(cls, data: dict[str, object]) -> CollaborationContract: """从字典创建 CollaborationContract""" return cls( from_expert=data.get("from_expert", ""), @@ -176,9 +175,9 @@ class PlanPhase: task_description: str = "" depends_on: list[str] = field(default_factory=list) status: PhaseStatus = PhaseStatus.PENDING - result: dict[str, Any] | None = None + result: dict[str, object] | None = None phase_type: PhaseType = PhaseType.EXECUTION - debate_config: dict[str, Any] | None = None + debate_config: dict[str, object] | None = None collaboration_contracts: list[CollaborationContract] = field(default_factory=list) rework_count: int = 0 review_feedback: str | None = None @@ -188,7 +187,7 @@ class PlanPhase: validation_command: str | None = None rollback_command: str | None = None - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" # Serialize result to string to match frontend ITeamPlanPhase.result type result_str: str | None = None @@ -197,7 +196,7 @@ class PlanPhase: result_str = self.result.get("content", str(self.result)) else: result_str = str(self.result) - out: dict[str, Any] = { + out: dict[str, object] = { "id": self.id, "name": self.name, "assigned_expert": self.assigned_expert, @@ -219,7 +218,7 @@ class PlanPhase: return out @classmethod - def from_dict(cls, data: dict[str, Any]) -> PlanPhase: + def from_dict(cls, data: dict[str, object]) -> PlanPhase: """从字典创建 PlanPhase""" contracts_data = data.get("collaboration_contracts", []) if not isinstance(contracts_data, list): @@ -272,7 +271,7 @@ class TeamPlan: status: PlanStatus = PlanStatus.DRAFT lead_expert: str = "" - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> dict[str, object]: """序列化为字典""" return { "id": self.id, @@ -284,7 +283,7 @@ class TeamPlan: } @classmethod - def from_dict(cls, data: dict[str, Any]) -> TeamPlan: + def from_dict(cls, data: dict[str, object]) -> TeamPlan: """从字典创建 TeamPlan""" subtasks = [SubTask.from_dict(st) for st in data.get("subtasks", [])] phases = [PlanPhase.from_dict(ph) for ph in data.get("phases", [])] @@ -307,7 +306,7 @@ class TeamPlan: return None def update_subtask_status( - self, subtask_id: str, status: SubTaskStatus, result: dict[str, Any] | None = None + self, subtask_id: str, status: SubTaskStatus, result: dict[str, object] | None = None ) -> None: """更新子任务状态和可选的结果""" st = self.get_subtask(subtask_id) @@ -343,7 +342,7 @@ class TeamPlan: return None def update_phase_status( - self, phase_id: str, status: PhaseStatus, result: dict[str, Any] | None = None + self, phase_id: str, status: PhaseStatus, result: dict[str, object] | None = None ) -> None: """更新阶段状态和可选的结果""" ph = self.get_phase(phase_id) diff --git a/src/agentkit/experts/team.py b/src/agentkit/experts/team.py index 70f6e53..54fb056 100644 --- a/src/agentkit/experts/team.py +++ b/src/agentkit/experts/team.py @@ -17,7 +17,6 @@ import enum import logging import time import uuid -from typing import Any from .config import ExpertConfig from .expert import Expert @@ -63,7 +62,7 @@ class ExpertTeam: workspace: SharedWorkspace | None = None, pool: AgentPool | None = None, template_registry: ExpertTemplateRegistry | None = None, - redis_client: Any = None, + redis_client: object | None = None, ): self.team_id = team_id or str(uuid.uuid4()) # U4: Accept redis_client for SharedWorkspace state offloading. diff --git a/src/agentkit/memory/adapters/confluence.py b/src/agentkit/memory/adapters/confluence.py index 1aeb763..3f69a23 100644 --- a/src/agentkit/memory/adapters/confluence.py +++ b/src/agentkit/memory/adapters/confluence.py @@ -6,6 +6,7 @@ from __future__ import annotations +import asyncio import logging import httpx @@ -83,7 +84,7 @@ class ConfluenceAdapter(KBAdapter): resp = await client.get("/rest/api/user/current") self._authenticated = resp.status_code == 200 return self._authenticated - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Confluence auth error: {e}") self._authenticated = False return False @@ -139,7 +140,7 @@ class ConfluenceAdapter(KBAdapter): f"Confluence search HTTP error: {e.response.status_code} — {e.response.text[:200]}" ) return [] - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Confluence search error: {e}") return [] @@ -170,7 +171,7 @@ class ConfluenceAdapter(KBAdapter): "version": page.get("version", {}).get("number", 0), }, ) - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Confluence get_document error: {e}") return None @@ -202,7 +203,7 @@ class ConfluenceAdapter(KBAdapter): ) ] ) - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Confluence list_sources error: {e}") return [ SourceInfo( @@ -218,5 +219,7 @@ class ConfluenceAdapter(KBAdapter): try: resp = await client.get("/rest/api/space", params={"limit": 1}) return resp.status_code == 200 - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False return False diff --git a/src/agentkit/memory/adapters/feishu.py b/src/agentkit/memory/adapters/feishu.py index 69ef235..b84d323 100644 --- a/src/agentkit/memory/adapters/feishu.py +++ b/src/agentkit/memory/adapters/feishu.py @@ -6,6 +6,7 @@ from __future__ import annotations +import asyncio import logging import time from typing import TypeAlias @@ -101,7 +102,7 @@ class FeishuKBAdapter(KBAdapter): else: logger.error(f"Feishu auth failed: code={data.get('code')}, msg={data.get('msg')}") return None - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Feishu auth error: {e}") return None @@ -166,7 +167,7 @@ class FeishuKBAdapter(KBAdapter): f"Feishu search HTTP error: {e.response.status_code} — {e.response.text[:200]}" ) return [] - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Feishu search error: {e}") return [] @@ -199,7 +200,7 @@ class FeishuKBAdapter(KBAdapter): "obj_type": node.get("obj_type", ""), }, ) - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Feishu get_document error: {e}") return None @@ -241,7 +242,7 @@ class FeishuKBAdapter(KBAdapter): ) ] ) - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"Feishu list_sources error: {e}") return [ SourceInfo( @@ -256,5 +257,7 @@ class FeishuKBAdapter(KBAdapter): try: token = await self._get_access_token() return token is not None - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False return False diff --git a/src/agentkit/memory/adapters/generic_http.py b/src/agentkit/memory/adapters/generic_http.py index 41dd26b..8a9b5c3 100644 --- a/src/agentkit/memory/adapters/generic_http.py +++ b/src/agentkit/memory/adapters/generic_http.py @@ -6,6 +6,7 @@ from __future__ import annotations +import asyncio import logging import httpx @@ -138,7 +139,7 @@ class GenericHTTPAdapter(KBAdapter): f"GenericHTTP search HTTP error: {e.response.status_code} — {e.response.text[:200]}" ) return [] - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"GenericHTTP search error: {e}") return [] @@ -179,7 +180,7 @@ class GenericHTTPAdapter(KBAdapter): f"GenericHTTP ingest HTTP error: {e.response.status_code} — {e.response.text[:200]}" ) return [] - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"GenericHTTP ingest error: {e}") return [] @@ -194,7 +195,7 @@ class GenericHTTPAdapter(KBAdapter): if resp.status_code in (200, 204): return True return False - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"GenericHTTP delete_by_id error: {e}") return False @@ -216,7 +217,7 @@ class GenericHTTPAdapter(KBAdapter): source_id=data.get("source_id", self._source_id), metadata=data.get("metadata", {}), ) - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"GenericHTTP get_document error: {e}") return None @@ -254,7 +255,9 @@ class GenericHTTPAdapter(KBAdapter): ) ] ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — list_sources 设计意图:任何异常回退到默认信息源 logger.debug(f"GenericHTTP list_sources error (endpoint may not exist): {e}") return [ @@ -272,14 +275,18 @@ class GenericHTTPAdapter(KBAdapter): resp = await client.get("/health") if resp.status_code == 200: return True - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — health_check 设计意图:任何异常继续尝试 fallback pass # Fallback: try the base endpoint try: resp = await client.get("/") return resp.status_code in (200, 401, 403) - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False return False async def authenticate(self) -> bool: @@ -289,6 +296,8 @@ class GenericHTTPAdapter(KBAdapter): """ try: self._authenticated = await self.health_check() - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — authenticate 设计意图:任何异常都返回 False self._authenticated = False return self._authenticated diff --git a/src/agentkit/memory/contextual_retrieval.py b/src/agentkit/memory/contextual_retrieval.py index aad0388..c1ae556 100644 --- a/src/agentkit/memory/contextual_retrieval.py +++ b/src/agentkit/memory/contextual_retrieval.py @@ -6,6 +6,7 @@ from __future__ import annotations +import asyncio import hashlib import logging from dataclasses import dataclass @@ -199,7 +200,7 @@ class ContextualChunker: ) context = response.content.strip() return context - except Exception as e: + except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e: logger.warning(f"Context generation failed for chunk: {e}") return "" diff --git a/src/agentkit/memory/document_loader.py b/src/agentkit/memory/document_loader.py index d991221..26a5c9a 100644 --- a/src/agentkit/memory/document_loader.py +++ b/src/agentkit/memory/document_loader.py @@ -9,6 +9,8 @@ from __future__ import annotations import io import logging import uuid +import zipfile +import asyncio from dataclasses import dataclass, field from datetime import datetime, timezone from pathlib import Path @@ -194,7 +196,9 @@ class DocumentLoader: return text, meta except ImportError: pass - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本 logger.warning(f"PyMuPDF parsing failed for {filename}: {e}") # 尝试 pdfplumber @@ -217,7 +221,9 @@ class DocumentLoader: return text, meta except ImportError: pass - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本 logger.warning(f"pdfplumber parsing failed for {filename}: {e}") # 回退到纯文本 @@ -264,7 +270,9 @@ class DocumentLoader: except ImportError: logger.warning(f"python-docx not available for {filename}, falling back to text") return self._parse_text(content, filename) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本 logger.warning(f"python-docx parsing failed for {filename}: {e}") return self._parse_text(content, filename) @@ -333,7 +341,9 @@ class DocumentLoader: except ImportError: logger.warning(f"openpyxl not available for {filename}, falling back to text") return self._parse_text(content, filename) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本 logger.warning(f"openpyxl parsing failed for {filename}: {e}") return self._parse_text(content, filename) @@ -407,7 +417,9 @@ class DocumentLoader: except ImportError: logger.warning(f"BeautifulSoup not available for {filename}, falling back to text") return self._parse_text(content, filename) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本 logger.warning(f"BeautifulSoup parsing failed for {filename}: {e}") return self._parse_text(content, filename) diff --git a/src/agentkit/memory/embedder.py b/src/agentkit/memory/embedder.py index 6c47c39..c12e04f 100644 --- a/src/agentkit/memory/embedder.py +++ b/src/agentkit/memory/embedder.py @@ -8,10 +8,8 @@ import os import time from abc import ABC, abstractmethod from collections import OrderedDict -from typing import TYPE_CHECKING -if TYPE_CHECKING: - import httpx +import httpx logger = logging.getLogger(__name__) @@ -108,8 +106,6 @@ class OpenAIEmbedder(Embedder): def _get_client(self) -> httpx.AsyncClient: """Lazily create and reuse a single httpx.AsyncClient.""" if self._client is None: - import httpx - self._client = httpx.AsyncClient(timeout=30.0) return self._client @@ -153,7 +149,7 @@ class OpenAIEmbedder(Embedder): self._cache.put(text, embedding) return embedding - except Exception as e: + except (httpx.HTTPError, ValueError, KeyError, TypeError) as e: logger.error(f"OpenAI embedding failed: {e}") raise diff --git a/src/agentkit/memory/episodic.py b/src/agentkit/memory/episodic.py index ba8a4c5..6110311 100644 --- a/src/agentkit/memory/episodic.py +++ b/src/agentkit/memory/episodic.py @@ -9,6 +9,7 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING from sqlalchemy import text +from sqlalchemy.exc import DBAPIError from agentkit.memory.base import Memory, MemoryItem, MetadataDict from agentkit.memory.embedder import Embedder @@ -94,7 +95,7 @@ class EpisodicMemory(Memory): ) db.add(entry) await db.commit() - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to store episodic memory: {e}") raise @@ -111,7 +112,7 @@ class EpisodicMemory(Memory): if self._pgvector_enabled: return await self._retrieve_pgvector(db, query_embedding) return await self._retrieve_client_side(db, query_embedding) - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to retrieve episodic memory: {e}") return None @@ -223,7 +224,7 @@ class EpisodicMemory(Memory): if self._pgvector_enabled and self._embedder: return await self._search_pgvector(db, query, top_k, filters, search_multiplier) return await self._search_client_side(db, query, top_k, filters, search_multiplier) - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to search episodic memory: {e}") return [] @@ -405,7 +406,7 @@ class EpisodicMemory(Memory): await db.execute(stmt) await db.commit() return True - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to delete episodic memory: {e}") return False diff --git a/src/agentkit/memory/http_rag.py b/src/agentkit/memory/http_rag.py index 9f170c8..af09bbf 100644 --- a/src/agentkit/memory/http_rag.py +++ b/src/agentkit/memory/http_rag.py @@ -147,7 +147,7 @@ class HttpRAGService: except httpx.RequestError as e: logger.error(f"RAG search request error: {e}") return [] - except Exception as e: + except (ValueError, KeyError, TypeError, RuntimeError) as e: logger.error(f"RAG search unexpected error: {e}") return [] @@ -237,7 +237,7 @@ class HttpRAGService: except httpx.RequestError as e: logger.error(f"RAG enhanced_search request error for KB {kb_id}: {e}") raise - except Exception as e: + except (ValueError, KeyError, TypeError, RuntimeError) as e: logger.error(f"RAG enhanced_search unexpected error for KB {kb_id}: {e}") raise @@ -302,7 +302,7 @@ class HttpRAGService: except httpx.HTTPStatusError as e: logger.error(f"RAG ingest HTTP error: {e.response.status_code}") return None - except Exception as e: + except (ValueError, KeyError, TypeError, RuntimeError) as e: logger.error(f"RAG ingest error: {e}") return None @@ -312,7 +312,7 @@ class HttpRAGService: try: resp = await client.get("/bases") return resp.status_code in (200, 401) # 401 = 服务在但需认证 - except Exception: + except (httpx.HTTPError, ValueError, KeyError, TypeError): return False async def close(self) -> None: diff --git a/src/agentkit/memory/local_rag.py b/src/agentkit/memory/local_rag.py index 8820131..817ea8c 100644 --- a/src/agentkit/memory/local_rag.py +++ b/src/agentkit/memory/local_rag.py @@ -14,6 +14,8 @@ import re from datetime import datetime, timezone from typing import TYPE_CHECKING, TypeAlias +from sqlalchemy.exc import DBAPIError + from agentkit.memory.chunking import Chunk, StructuralChunker, TextChunker from agentkit.memory.document_loader import Document as LoaderDocument from agentkit.memory.embedder import Embedder @@ -108,7 +110,7 @@ class LocalRAGService: await self._store_chunks(doc, chunks) ingested_ids.append(doc.doc_id) logger.info(f"Ingested document '{doc.title}' with {len(chunks)} chunks") - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to ingest document '{doc.title}': {e}") return ingested_ids @@ -130,7 +132,7 @@ class LocalRAGService: if self._pgvector_enabled: return await self._query_pgvector(db, query_embedding, top_k) return await self._query_client_side(db, query_embedding, top_k) - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to query knowledge base: {e}") return [] @@ -151,7 +153,7 @@ class LocalRAGService: await db.execute(sql, {"doc_id": id}) await db.commit() return True - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to delete document {id}: {e}") return False @@ -190,7 +192,7 @@ class LocalRAGService: ) ) return sources - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to list sources: {e}") return [] @@ -202,7 +204,7 @@ class LocalRAGService: await db.execute(sql_text(f"SELECT 1 FROM {self._table_name} LIMIT 1")) return True - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Health check failed: {e}") return False @@ -268,7 +270,7 @@ class LocalRAGService: await db.execute(sql, params_list) await db.commit() - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: await db.rollback() logger.error(f"Failed to store chunks for document '{doc.title}': {e}") raise @@ -455,7 +457,7 @@ class InMemoryLocalRAGService: } ingested_ids.append(doc.doc_id) logger.info(f"Ingested document '{doc.title}' with {len(chunks)} chunks") - except Exception as e: + except (DBAPIError, ValueError, KeyError, RuntimeError) as e: logger.error(f"Failed to ingest document '{doc.title}': {e}") return ingested_ids diff --git a/src/agentkit/memory/profile.py b/src/agentkit/memory/profile.py index b7050d5..963048a 100644 --- a/src/agentkit/memory/profile.py +++ b/src/agentkit/memory/profile.py @@ -378,7 +378,7 @@ class MemoryStore: try: new_prompt = self.refresh_system_prompt() self._on_change(new_prompt) - except Exception: + except (OSError, ValueError, RuntimeError, TypeError): import logging logging.getLogger(__name__).warning("Memory notify_change failed", exc_info=True) diff --git a/src/agentkit/memory/retriever.py b/src/agentkit/memory/retriever.py index 01d9110..0fc3f20 100644 --- a/src/agentkit/memory/retriever.py +++ b/src/agentkit/memory/retriever.py @@ -392,5 +392,5 @@ class RetrieveKnowledgeTool(Tool): } ) return {"query": query, "results": results, "call_count": self._call_count} - except Exception as e: + except (ConnectionError, RuntimeError, ValueError, KeyError, TypeError) as e: return {"error": str(e), "results": []} diff --git a/src/agentkit/quality/cascade_state_store.py b/src/agentkit/quality/cascade_state_store.py index 9adde99..b45fc43 100644 --- a/src/agentkit/quality/cascade_state_store.py +++ b/src/agentkit/quality/cascade_state_store.py @@ -13,6 +13,12 @@ import logging import time from typing import Any, Protocol, runtime_checkable +# redis 可选依赖;未安装时回退为 Exception 以保留原 catch-all 语义(降级到 fallback) +try: + from redis.exceptions import RedisError as _RedisError +except ImportError: + _RedisError = Exception + logger = logging.getLogger(__name__) @@ -157,7 +163,7 @@ class RedisCascadeStateStore: pipe.expire(key, self._session_ttl) results = pipe.execute() return results[0] - except Exception as e: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e: logger.warning(f"Redis cascade increment failed: {e}") self._degrade_to_fallback() if self._fallback is not None: @@ -171,7 +177,7 @@ class RedisCascadeStateStore: r = self._get_sync_redis() val = r.get(f"{self.INTER_PREFIX}{session_id}") return int(val) if val is not None else 0 - except Exception as e: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e: logger.warning(f"Redis cascade get failed: {e}") if self._fallback is not None: return self._fallback.get_interaction(session_id) @@ -188,7 +194,7 @@ class RedisCascadeStateStore: pipe.set(key, depth) pipe.expire(key, self._session_ttl) pipe.execute() - except Exception as e: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e: logger.warning(f"Redis cascade set_depth failed: {e}") self._degrade_to_fallback() if self._fallback is not None: @@ -201,7 +207,7 @@ class RedisCascadeStateStore: r = self._get_sync_redis() val = r.get(f"{self.DEPTH_PREFIX}{session_id}") return int(val) if val is not None else 0 - except Exception as e: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e: logger.warning(f"Redis cascade get_depth failed: {e}") if self._fallback is not None: return self._fallback.get_depth(session_id) @@ -217,7 +223,7 @@ class RedisCascadeStateStore: pipe.delete(f"{self.INTER_PREFIX}{session_id}") pipe.delete(f"{self.DEPTH_PREFIX}{session_id}") pipe.execute() - except Exception as e: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e: logger.warning(f"Redis cascade reset failed: {e}") self._degrade_to_fallback() if self._fallback is not None: diff --git a/src/agentkit/server/app.py b/src/agentkit/server/app.py index ce2d3b9..60373c8 100644 --- a/src/agentkit/server/app.py +++ b/src/agentkit/server/app.py @@ -81,7 +81,7 @@ def _build_llm_gateway(config: ServerConfig) -> LLMGateway: backend=config.usage_store.get("backend", "memory"), redis_url=config.usage_store.get("redis_url", "redis://localhost:6379"), ) - except Exception as e: + except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError) as e: logger.warning(f"Failed to initialize usage store: {e}, using in-memory") gateway = LLMGateway(config=config.llm_config, usage_store=usage_store) @@ -93,7 +93,7 @@ def _build_llm_gateway(config: ServerConfig) -> LLMGateway: try: provider = _create_provider(name, pconf) gateway.register_provider(name, provider) - except Exception as e: + except (ValueError, TypeError, KeyError, RuntimeError, ConnectionError, OSError) as e: logger.warning(f"Failed to register LLM provider '{name}': {e}") return gateway @@ -222,7 +222,7 @@ async def lifespan(app: FastAPI): if agent is not None: agent._system_prompt = new_prompt updated += 1 - except Exception: + except (AttributeError, TypeError, ValueError): logger.warning( f"Failed to update system prompt for agent '{agent_name}'", exc_info=True ) @@ -283,7 +283,9 @@ async def lifespan(app: FastAPI): agent._tool_registry.register(DocumentTool(service=doc_service)) app.state.document_service = doc_service logger.info("DocumentTool registered with word/excel/pdf renderers") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — DocumentTool init + registry; diverse failure modes logger.exception("Failed to register DocumentTool") # Override system prompt with memory-injected version + available tools @@ -296,7 +298,9 @@ async def lifespan(app: FastAPI): ) logger.info("GUI mode: created default chat agent with memory + tools") - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — GUI mode agent creation; multi-step init with diverse failures logger.warning(f"GUI mode: failed to create default agent: {e}") # Load skills from config and register into SkillRegistry @@ -310,7 +314,7 @@ async def lifespan(app: FastAPI): for tool in agent._tool_registry.list_tools(): try: tool_registry.register(tool) - except Exception: + except (ValueError, KeyError, RuntimeError): pass # Already registered # Load skills from configured paths @@ -331,11 +335,13 @@ async def lifespan(app: FastAPI): try: loader.load_from_file(str(p)) logger.info(f"GUI mode: loaded skill from {p}") - except Exception as se: + except (ValueError, TypeError, KeyError, OSError, RuntimeError) as se: logger.warning(f"GUI mode: failed to load skill from {p}: {se}") logger.info(f"GUI mode: {len(skill_registry.list_skills())} skills registered") - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — GUI mode skills loading top-level; multi-step init logger.warning(f"GUI mode: failed to load skills: {e}") elif os.environ.get("AGENTKIT_GUI_MODE"): # Agent already exists (e.g. from config), still ensure memory store is available @@ -369,7 +375,7 @@ async def lifespan(app: FastAPI): if agent is not None: agent._system_prompt = new_prompt updated += 1 - except Exception: + except (AttributeError, TypeError, ValueError): logger.warning( f"Failed to update system prompt for agent '{agent_name}'", exc_info=True, @@ -416,7 +422,9 @@ async def lifespan(app: FastAPI): app.state.expert_template_registry = expert_registry if total_loaded: logger.info(f"Total {total_loaded} ExpertTemplates registered for @board mode") - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — ExpertTemplate registry loading; multi-step init logger.warning(f"Failed to load ExpertTemplates: {e}") # Ensure app.state.expert_template_registry always exists (empty registry) from agentkit.experts.registry import ExpertTemplateRegistry @@ -468,7 +476,7 @@ async def lifespan(app: FastAPI): _row = await _cur.fetchone() if _row is not None: default_cal_user_id = str(_row["id"]) - except Exception: + except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError): logger.debug("Could not resolve default user_id for CalendarTool", exc_info=True) calendar_tool = CalendarTool( @@ -492,7 +500,7 @@ async def lifespan(app: FastAPI): if default_agent is not None: try: default_agent._tool_registry.register(calendar_tool) - except Exception: + except (ValueError, KeyError, RuntimeError, AttributeError): # ponytail: log at debug — CalendarTool double-registration # is expected on reload, but silent pass hides real errors. logger.debug("CalendarTool already registered or registration failed", exc_info=True) @@ -512,12 +520,18 @@ async def lifespan(app: FastAPI): "CalendarTool attached to default agent (tools=%d)", len(default_agent._tool_registry.list_tools()), ) - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — CalendarTool attach; multi-step with attribute access logger.exception("Failed to attach CalendarTool to default agent") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — CalendarTool registration; multi-step init logger.exception("Failed to register CalendarTool") logger.info("Calendar subsystem initialized (service + reminder scheduler)") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — calendar subsystem top-level init; must not block app startup logger.exception("Failed to initialize calendar subsystem — calendar API unavailable") # Bitable subsystem: init DB, service, internal token (KTD11). @@ -529,7 +543,9 @@ async def lifespan(app: FastAPI): app.state.bitable_service = BitableService(db=bitable_db) app.state.bitable_internal_token = os.environ.get("AGENTKIT_BITABLE_INTERNAL_TOKEN") logger.info("Bitable subsystem initialized") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — bitable subsystem top-level init; includes import + DB setup logger.exception("Failed to initialize bitable subsystem") # RAG platform subsystem (P1): wire KBStore, RetrievalEngine, HitProcessor, @@ -552,7 +568,7 @@ async def lifespan(app: FastAPI): from agentkit.rag_platform.store import ensure_tables await ensure_tables(rag_database_url) - except Exception: + except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError): logger.exception("Failed to ensure rag_platform tables") # KBStore — KB/Document persistence @@ -561,7 +577,9 @@ async def lifespan(app: FastAPI): app.state.kb_store = KBStore(session_factory=kb_session_factory) logger.info("KBStore initialized") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — KBStore init includes import + DB setup logger.exception("Failed to initialize KBStore") # RetrievalEngine — requires llama_index PGVectorStore @@ -576,9 +594,13 @@ async def lifespan(app: FastAPI): session_factory=kb_session_factory, ) logger.info("RetrievalEngine initialized (pgvector ready)") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — RetrievalEngine init includes import + pgvector setup logger.exception("Failed to initialize RetrievalEngine — vector search unavailable") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — rag_platform top-level init logger.exception("Failed to initialize rag_platform DB components") # HitProcessor — LLM-based answer generation from retrieval results @@ -587,7 +609,9 @@ async def lifespan(app: FastAPI): app.state.hit_processor = HitProcessor(llm_gateway=app.state.llm_gateway) logger.info("HitProcessor initialized") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — HitProcessor init includes import logger.exception("Failed to initialize HitProcessor") # TaskManager — async vectorize/batch-index tasks @@ -598,7 +622,9 @@ async def lifespan(app: FastAPI): app.state.task_manager = TaskManager() logger.info("TaskManager initialized (degraded mode: InMemoryTaskStore)") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — TaskManager init includes import logger.exception("Failed to initialize TaskManager") # KBSettingsStore — per-KB retrieval/hit-processing defaults (process singleton) @@ -607,7 +633,9 @@ async def lifespan(app: FastAPI): app.state.kb_settings_store = get_settings_store() logger.info("KBSettingsStore initialized") - except Exception: + except asyncio.CancelledError: + raise + except Exception: # noqa: BLE001 — KBSettingsStore init includes import logger.exception("Failed to initialize KBSettingsStore") yield @@ -618,7 +646,7 @@ async def lifespan(app: FastAPI): from agentkit.bitable.db import close_bitable_db await close_bitable_db() - except Exception: + except (RuntimeError, asyncio.TimeoutError, ConnectionError, OSError): pass # Stop MCP servers if mcp_manager is not None: @@ -660,7 +688,7 @@ async def lifespan(app: FastAPI): from agentkit.server.routes.channels import close_all_adapters await close_all_adapters() - except Exception: + except (RuntimeError, asyncio.TimeoutError, ConnectionError, OSError): logger.debug("close_all_adapters 异常已忽略") @@ -706,7 +734,7 @@ def _on_config_change(app: FastAPI, config: ServerConfig) -> None: if hasattr(app.state, "agent_pool") and app.state.agent_pool is not None: app.state.agent_pool._llm_gateway = new_gateway logger.info(f"LLM Gateway reloaded (config v{current_version})") - except Exception as e: + except (ValueError, TypeError, KeyError, RuntimeError, ConnectionError, OSError) as e: logger.error(f"Failed to reload LLM Gateway: {e}") # Reload skills if skill paths changed @@ -730,13 +758,15 @@ def _on_config_change(app: FastAPI, config: ServerConfig) -> None: elif p.is_file() and p.suffix in (".yaml", ".yml"): try: loader.load_from_file(str(p)) - except Exception: + except (ValueError, TypeError, KeyError, OSError, RuntimeError): pass app.state.skill_registry = new_skill_registry if hasattr(app.state, "agent_pool") and app.state.agent_pool is not None: app.state.agent_pool._skill_registry = new_skill_registry logger.info(f"Skills reloaded (config v{current_version})") - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — skills reload top-level; multi-step init logger.error(f"Failed to reload skills: {e}") # Update config version on all agents @@ -856,7 +886,7 @@ def create_app( from agentkit.server.middleware import _load_client_keys client_keys = _load_client_keys() - except Exception as e: + except (ValueError, TypeError, KeyError, OSError, RuntimeError) as e: logger.warning(f"Failed to load client keys for AuthMiddleware: {e}") app.add_middleware( @@ -983,7 +1013,7 @@ def create_app( try: ts_config = {**ts_config, **_json.loads(ts_env)} - except Exception: + except (ValueError, TypeError): pass task_store = create_task_store( backend=ts_config.get("backend", "memory"), @@ -1039,7 +1069,9 @@ def create_app( db_path=evo_conf.get("db_path", "~/.agentkit/evolution.db"), database_url=evo_conf.get("database_url"), ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — evolution store init includes import + DB setup logger.warning(f"Failed to initialize evolution store: {e}") app.state.evolution_store = None else: @@ -1056,7 +1088,9 @@ def create_app( redis_url=cs_conf.get("redis_url", "redis://localhost:6379"), session_ttl=cs_conf.get("session_ttl", 86400), ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — cascade state store init includes import + setup logger.warning(f"Failed to initialize cascade state store: {e}") app.state.cascade_state_store = None else: @@ -1138,7 +1172,7 @@ def create_app( try: epi_session_factory = create_episodic_session_factory(database_url) epi_model = EpisodeModel - except Exception as db_err: + except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError, ImportError) as db_err: import logging as _log _log.getLogger(__name__).warning( @@ -1155,7 +1189,9 @@ def create_app( pgvector_enabled=epi_conf.get("pgvector_enabled", True), table_name=epi_conf.get("table_name", "episodic_memories"), ) - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — episodic memory init; multi-step with embedder + DB import logging logging.getLogger(__name__).warning( @@ -1174,7 +1210,9 @@ def create_app( retrieve_tool = memory_retriever.create_retrieve_tool() if retrieve_tool: app.state.retrieve_knowledge_tool = retrieve_tool - except Exception as e: + except asyncio.CancelledError: + raise + except Exception as e: # noqa: BLE001 — memory components top-level init; multi-step with imports + DB import logging logging.getLogger(__name__).warning(f"Failed to initialize memory components: {e}") diff --git a/src/agentkit/session/store.py b/src/agentkit/session/store.py index 7199370..8f6c306 100644 --- a/src/agentkit/session/store.py +++ b/src/agentkit/session/store.py @@ -7,6 +7,12 @@ import logging import os from typing import Any, Protocol, runtime_checkable +# redis 可选依赖;未安装时回退为 Exception 以保留原 catch-all 语义 +try: + from redis.exceptions import RedisError as _RedisError +except ImportError: + _RedisError = Exception + from agentkit.session.models import Message, Session, SessionStatus logger = logging.getLogger(__name__) @@ -207,7 +213,7 @@ class RedisSessionStore: try: redis = await self._get_redis() return await redis.ping() - except Exception: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError): return False @@ -283,7 +289,7 @@ class FileSessionStore: session = Session.from_dict(data["session"]) if agent_name is None or session.agent_name == agent_name: sessions.append(session) - except Exception: + except (ValueError, KeyError, TypeError, OSError): continue sessions.sort(key=lambda s: s.updated_at, reverse=True) return sessions[:limit] @@ -343,7 +349,7 @@ def create_session_store( store = RedisSessionStore(redis_url=redis_url, ttl_seconds=ttl_seconds) logger.info(f"SessionStore backend: redis") return store - except Exception as exc: + except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError) as exc: logger.warning(f"Failed to initialise RedisSessionStore ({exc}), falling back to InMemorySessionStore") store = InMemorySessionStore() diff --git a/src/agentkit/skills/loader.py b/src/agentkit/skills/loader.py index 941fda2..67d5e12 100644 --- a/src/agentkit/skills/loader.py +++ b/src/agentkit/skills/loader.py @@ -56,7 +56,7 @@ class SkillLoader: try: skill = self._load_skill_from_file(yaml_path) skills.append(skill) - except Exception as e: + except (ValueError, KeyError, TypeError, OSError, RuntimeError) as e: logger.warning(f"Skipping invalid YAML file '{yaml_path}': {e}") # 加载 SKILL.md 文件 @@ -66,7 +66,7 @@ class SkillLoader: try: skill = self.load_from_skill_md(md_path) skills.append(skill) - except Exception as e: + except (ValueError, KeyError, TypeError, OSError, RuntimeError) as e: logger.warning(f"Skipping invalid SKILL.md file '{md_path}': {e}") return skills @@ -144,7 +144,7 @@ class SkillLoader: from importlib.metadata import entry_points as _entry_points eps = _entry_points().get(group_name, []) - except Exception as e: + except (ValueError, KeyError, TypeError, OSError, RuntimeError) as e: logger.warning(f"Failed to discover entry_points for group '{group_name}': {e}") return skills @@ -192,7 +192,7 @@ class SkillLoader: logger.info( f"Loaded skill '{skill.name}' v{skill.version} from entry_point '{ep.name}'" ) - except Exception as e: + except (ValueError, KeyError, TypeError, OSError, RuntimeError) as e: logger.warning(f"Failed to load skill from entry_point '{ep.name}': {e}") return skills @@ -208,6 +208,6 @@ class SkillLoader: tool = self._tool_registry.get(tool_name) tools.append(tool) logger.info(f"Bound tool '{tool_name}' to skill '{config.name}'") - except Exception as e: + except (ValueError, KeyError, TypeError, OSError, RuntimeError) as e: logger.warning(f"Failed to bind tool '{tool_name}' to skill '{config.name}': {e}") return tools