refactor: tech debt Wave 1+2 (except Exception 收尾 + core/experts Any 治理) (#10)
Deploy to Production / deploy (push) Waiting to run Details
Test / backend-test (push) Waiting to run Details
Test / frontend-unit (push) Waiting to run Details
Test / api-e2e (push) Waiting to run Details
Test / frontend-e2e (push) Waiting to run Details

This commit is contained in:
Fischer 2026-07-01 03:54:53 +08:00
parent 838a05772e
commit a778f816c5
49 changed files with 430 additions and 332 deletions

View File

@ -70,7 +70,9 @@ class InMemoryMessageBus:
for handler in self._subscribers[recipient]: for handler in self._subscribers[recipient]:
try: try:
await handler(message) 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}") logger.warning(f"Handler error for {recipient}: {e}")
# Check if this is a response to a pending request # Check if this is a response to a pending request

View File

@ -62,7 +62,7 @@ class RedisMessageBus:
try: try:
await redis.xadd(stream_key, {"data": json.dumps(data)}) 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}") logger.error(f"Failed to publish message to {stream_key}: {e}")
raise raise
@ -103,7 +103,9 @@ class RedisMessageBus:
await redis.xgroup_create( await redis.xgroup_create(
stream_key, self._consumer_group, id="0", mkstream=True, 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 pass # Group already exists
while True: while True:
@ -126,12 +128,16 @@ class RedisMessageBus:
for handler in self._subscribers.get(agent_name, []): for handler in self._subscribers.get(agent_name, []):
try: try:
await handler(message) 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}") logger.warning(f"Handler error for {agent_name}: {e}")
# Acknowledge message # Acknowledge message
await redis.xack(stream_key, self._consumer_group, msg_id) 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}") logger.warning(f"Failed to process message {msg_id}: {e}")
# Move to dead letter after max retries # Move to dead letter after max retries
await self._handle_failed_message( await self._handle_failed_message(
@ -139,7 +145,7 @@ class RedisMessageBus:
) )
except asyncio.CancelledError: except asyncio.CancelledError:
break 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}") logger.error(f"Consumer error for {agent_name}: {e}")
await asyncio.sleep(1) await asyncio.sleep(1)
@ -157,7 +163,7 @@ class RedisMessageBus:
await redis.xadd(dead_key, fields) await redis.xadd(dead_key, fields)
await redis.xack(stream_key, self._consumer_group, msg_id) await redis.xack(stream_key, self._consumer_group, msg_id)
logger.warning(f"Message {msg_id} moved to dead letter queue") 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}") logger.error(f"Failed to move message to dead letter: {e}")
async def unsubscribe(self, agent_name: str) -> None: async def unsubscribe(self, agent_name: str) -> None:
@ -205,7 +211,7 @@ class RedisMessageBus:
stream_key = self._stream_key(agent_name) stream_key = self._stream_key(agent_name)
try: try:
await redis.xadd(stream_key, {"data": json.dumps(data)}) 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}") logger.error(f"Failed to broadcast to {agent_name}: {e}")
# Check pending requests (only for replies) # Check pending requests (only for replies)
@ -222,7 +228,7 @@ class RedisMessageBus:
try: try:
redis = await self._get_redis() redis = await self._get_redis()
return await redis.ping() return await redis.ping()
except Exception: except (ConnectionError, OSError, asyncio.TimeoutError, RuntimeError):
return False return False
@property @property
@ -257,7 +263,9 @@ def create_message_bus(
) )
logger.info(f"MessageBus backend: redis_streams ({redis_url})") logger.info(f"MessageBus backend: redis_streams ({redis_url})")
return bus 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( logger.warning(
f"Failed to initialise RedisMessageBus ({exc}), " f"Failed to initialise RedisMessageBus ({exc}), "
f"falling back to InMemoryMessageBus" f"falling back to InMemoryMessageBus"

View File

@ -194,7 +194,7 @@ class RequestPreprocessor:
"""Resolve an explicitly specified skill via @skill:xxx prefix.""" """Resolve an explicitly specified skill via @skill:xxx prefix."""
try: try:
skill = registry.get(skill_name) skill = registry.get(skill_name)
except Exception: except (ValueError, KeyError, TypeError, RuntimeError):
logger.warning(f"Skill '{skill_name}' not found, falling back to REACT") logger.warning(f"Skill '{skill_name}' not found, falling back to REACT")
return SkillRoutingResult( return SkillRoutingResult(
clean_content=clean_content, clean_content=clean_content,

View File

@ -200,7 +200,7 @@ async def resolve_skill_routing(
result.match_method = "explicit" result.match_method = "explicit"
result.match_confidence = 1.0 result.match_confidence = 1.0
logger.info(f"Session {session_id}: using explicit skill '{explicit_skill}'") logger.info(f"Session {session_id}: using explicit skill '{explicit_skill}'")
except Exception as e: except (ValueError, KeyError, TypeError, RuntimeError) as e:
logger.warning( logger.warning(
f"Session {session_id}: explicit skill '{explicit_skill}' not found: {e}" f"Session {session_id}: explicit skill '{explicit_skill}' not found: {e}"
) )

View File

@ -291,7 +291,7 @@ class SqliteConversationStore:
title_row = await title_cursor.fetchone() title_row = await title_cursor.fetchone()
if title_row: if title_row:
conv.messages.append(ChatMessage(role="user", content=title_row["content"])) conv.messages.append(ChatMessage(role="user", content=title_row["content"]))
except Exception: except (aiosqlite.Error, ValueError, KeyError, TypeError):
pass pass
result.append(conv) result.append(conv)
return result return result
@ -321,7 +321,7 @@ class SqliteConversationStore:
) )
await db.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,)) await db.execute("DELETE FROM conversations WHERE id = ?", (conversation_id,))
await db.commit() await db.commit()
except Exception: except (aiosqlite.Error, ValueError, KeyError, RuntimeError):
await db.rollback() await db.rollback()
logger.exception("Failed to delete conversation %s; rolled back", conversation_id) logger.exception("Failed to delete conversation %s; rolled back", conversation_id)
raise raise
@ -365,6 +365,6 @@ class SqliteConversationStore:
timestamp=self._str_to_dt(row["timestamp"]), timestamp=self._str_to_dt(row["timestamp"]),
metadata=meta, 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}") logger.warning(f"get_first_user_message failed for {conversation_id}: {e}")
return None return None

View File

@ -134,7 +134,7 @@ async def _chat_async(
data=data or {}, data=data or {},
) )
) )
except Exception: except (asyncio.QueueFull, RuntimeError, ConnectionError):
pass # EQ is best-effort; never break CLI flow pass # EQ is best-effort; never break CLI flow
# Emit session.started event # Emit session.started event
@ -177,7 +177,7 @@ async def _chat_async(
elif p.is_file() and p.suffix in (".yaml", ".yml"): elif p.is_file() and p.suffix in (".yaml", ".yml"):
try: try:
loader.load_from_file(str(p)) loader.load_from_file(str(p))
except Exception: except (ValueError, TypeError, KeyError, OSError, RuntimeError):
pass pass
# Build system prompt — inject memory into system prompt # Build system prompt — inject memory into system prompt
@ -428,7 +428,9 @@ async def _chat_async(
data={"output": full_content}, 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]") rprint(f"\n[red]Error: {e}[/red]")
# Emit task.failed to EQ (if enabled) # Emit task.failed to EQ (if enabled)
if _emit is not None: if _emit is not None:
@ -461,7 +463,7 @@ async def _chat_async(
# Archive old daily logs # Archive old daily logs
memory_store.archive_old_dailies(keep_days=2) 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 pass # Daily log generation is best-effort
# Close EventQueue if it was enabled (emit session.ended and close) # Close EventQueue if it was enabled (emit session.ended and close)
@ -473,7 +475,7 @@ async def _chat_async(
session_id=session.session_id, session_id=session.session_id,
data={}, data={},
) )
except Exception: except (asyncio.QueueFull, RuntimeError, ConnectionError):
pass pass
eq.close() eq.close()
@ -502,7 +504,7 @@ def _build_gateway(server_config: "ServerConfig") -> "LLMGateway":
try: try:
provider = _create_provider(name, pconf) provider = _create_provider(name, pconf)
gateway.register_provider(name, provider) gateway.register_provider(name, provider)
except Exception as e: except (ValueError, TypeError, KeyError, RuntimeError, ConnectionError, OSError) as e:
import logging import logging
logging.getLogger(__name__).warning(f"Failed to register LLM provider '{name}': {e}") 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 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 渲染不中断编排,但记录日志便于调试 # ponytail: best-effort 渲染不中断编排,但记录日志便于调试
import logging import logging
@ -782,7 +786,9 @@ async def _execute_team_cli(
elif etype == "user_intervention": elif etype == "user_intervention":
pass # User typed it themselves pass # User typed it themselves
# Other events (expert_step, expert_result, expert_joined, etc.) are not rendered # 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 pass # Rendering is best-effort; never break orchestration
team.handoff_transport.register_handler(team.team_channel, _event_handler) team.handoff_transport.register_handler(team.team_channel, _event_handler)
@ -816,7 +822,7 @@ async def _execute_team_cli(
if readable: if readable:
try: try:
line = sys.stdin.readline() line = sys.stdin.readline()
except Exception: except (OSError, ValueError, RuntimeError):
line = "" line = ""
if not line: if not line:
break # EOF 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]") rprint(f"[red]团队执行错误: {e}[/red]")
finally: finally:
try: try:
await team.dissolve() await team.dissolve()
except Exception: except (RuntimeError, asyncio.TimeoutError, ConnectionError):
pass pass
return True return True

View File

@ -13,7 +13,7 @@ import logging
import time import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING
import redis.asyncio as aioredis import redis.asyncio as aioredis
@ -71,7 +71,7 @@ class BaseAgent(ABC):
# 可插拔能力(由子类或配置注入) # 可插拔能力(由子类或配置注入)
self._tools: list["Tool"] = [] self._tools: list["Tool"] = []
self._memory: "Memory | None" = None self._memory: "Memory | None" = None
self._memory_retriever: Any | None = None self._memory_retriever: object | None = None
# 外部依赖注入(由 start() 时设置) # 外部依赖注入(由 start() 时设置)
self._registry = None self._registry = None
@ -192,7 +192,7 @@ class BaseAgent(ABC):
lines.append(f" - {msg}") lines.append(f" - {msg}")
return "\n".join(lines) 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 校验""" """从当前技能配置构建 skill_context用于 QualityGate skill_match 校验"""
if not self._skill: if not self._skill:
return None return None
@ -216,17 +216,17 @@ class BaseAgent(ABC):
self._memory = memory self._memory = memory
return self return self
def use_memory_retriever(self, retriever: Any) -> "BaseAgent": def use_memory_retriever(self, retriever: object) -> "BaseAgent":
"""设置记忆检索器,用于上下文注入""" """设置记忆检索器,用于上下文注入"""
self._memory_retriever = retriever self._memory_retriever = retriever
return self return self
def set_registry(self, registry: Any) -> "BaseAgent": def set_registry(self, registry: object) -> "BaseAgent":
"""注入注册中心""" """注入注册中心"""
self._registry = registry self._registry = registry
return self return self
def set_dispatcher(self, dispatcher: Any) -> "BaseAgent": def set_dispatcher(self, dispatcher: object) -> "BaseAgent":
"""注入任务分发器""" """注入任务分发器"""
self._dispatcher = dispatcher self._dispatcher = dispatcher
return self return self
@ -489,7 +489,7 @@ class BaseAgent(ABC):
target_agent: str, target_agent: str,
task: TaskMessage, task: TaskMessage,
reason: str, reason: str,
context: dict[str, Any] | None = None, context: dict[str, object] | None = None,
): ):
"""将当前任务转交给另一个 Agent""" """将当前任务转交给另一个 Agent"""
if self._redis is None: if self._redis is None:

View File

@ -10,7 +10,7 @@
import json import json
import logging import logging
import os import os
from typing import Any, Callable, Coroutine from typing import Callable, Coroutine
import yaml import yaml
@ -39,12 +39,12 @@ class AgentConfig:
task_mode: str = "llm_generate", task_mode: str = "llm_generate",
supported_tasks: list[str] | None = None, supported_tasks: list[str] | None = None,
max_concurrency: int = 1, max_concurrency: int = 1,
input_schema: dict[str, Any] | None = None, input_schema: dict[str, object] | None = None,
output_schema: dict[str, Any] | None = None, output_schema: dict[str, object] | None = None,
prompt: dict[str, str] | 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, tools: list[str] | None = None,
memory: dict[str, Any] | None = None, memory: dict[str, object] | None = None,
custom_handler: str | None = None, custom_handler: str | None = None,
): ):
self.name = name self.name = name
@ -96,7 +96,7 @@ class AgentConfig:
) )
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> "AgentConfig": def from_dict(cls, data: dict[str, object]) -> "AgentConfig":
"""从字典创建配置""" """从字典创建配置"""
return cls( return cls(
name=data["name"], name=data["name"],
@ -128,7 +128,7 @@ class AgentConfig:
) )
return cls.from_dict(data) return cls.from_dict(data)
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
"""序列化为字典""" """序列化为字典"""
d = { d = {
"name": self.name, "name": self.name,
@ -197,11 +197,11 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin):
self, self,
config: AgentConfig, config: AgentConfig,
tool_registry: ToolRegistry | None = None, tool_registry: ToolRegistry | None = None,
llm_client: Any = None, llm_client: object | None = None,
custom_handlers: dict[str, Callable[..., Coroutine]] | 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 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 # v2: If SkillConfig, extract skill info
from agentkit.skills.base import SkillConfig, Skill 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}'") logger.info(f"Merged skill tool '{tool.name}' into agent '{self.name}'")
# v2: Register MCP tools if mcp_servers provided # 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_servers: dict[str, str] = mcp_servers or {}
self._mcp_tools_registered = False self._mcp_tools_registered = False
# Memory integration: 从 config.memory 自动实例化 MemoryRetriever # Memory integration: 从 config.memory 自动实例化 MemoryRetriever
self._memory_retriever: Any | None = None self._memory_retriever: object | None = None
if config.memory: if config.memory:
try: try:
from agentkit.memory.retriever import MemoryRetriever from agentkit.memory.retriever import MemoryRetriever
@ -903,7 +903,7 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin):
) )
return await self.handle_task(enhanced_task) 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""" """Wrap legacy llm_client into LLMGateway"""
from agentkit.llm.gateway import LLMGateway from agentkit.llm.gateway import LLMGateway
from agentkit.llm.protocol import LLMProvider, LLMRequest, LLMResponse, TokenUsage from agentkit.llm.protocol import LLMProvider, LLMRequest, LLMResponse, TokenUsage
@ -911,7 +911,7 @@ class ConfigDrivenAgent(BaseAgent, EvolutionMixin):
class ClientProvider(LLMProvider): class ClientProvider(LLMProvider):
"""Adapter: wraps legacy llm_client as an 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 self._raw_client = raw_client
async def chat(self, request: LLMRequest) -> LLMResponse: async def chat(self, request: LLMRequest) -> LLMResponse:

View File

@ -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 dataclasses import dataclass
from typing import Any
from agentkit.core.exceptions import ( from agentkit.core.exceptions import (
LLMProviderError, LLMProviderError,
@ -54,7 +53,7 @@ class EmergencyError:
retryable: bool # whether a user retry might succeed retryable: bool # whether a user retry might succeed
original_error: str # str(exc) for traceability original_error: str # str(exc) for traceability
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
return { return {
"error_code": self.error_code, "error_code": self.error_code,
"message": self.message, "message": self.message,

View File

@ -24,7 +24,7 @@ from __future__ import annotations
import json import json
import logging import logging
from dataclasses import dataclass, field 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__) logger = logging.getLogger(__name__)
@ -37,14 +37,14 @@ class RequestContext:
""" """
messages: list[dict[str, str]] messages: list[dict[str, str]]
tools: list[Any] = field(default_factory=list) tools: list[object] = field(default_factory=list)
system_prompt: str | None = None system_prompt: str | None = None
model: str = "default" model: str = "default"
agent_name: str = "" agent_name: str = ""
task_type: str = "" task_type: str = ""
task_id: str | None = None task_id: str | None = None
# 中间件间共享状态压缩结果、token 用量、循环检测状态等) # 中间件间共享状态压缩结果、token 用量、循环检测状态等)
metadata: dict[str, Any] = field(default_factory=dict) metadata: dict[str, object] = field(default_factory=dict)
@runtime_checkable @runtime_checkable
@ -57,7 +57,7 @@ class Middleware(Protocol):
async def before(self, ctx: RequestContext) -> RequestContext: ... 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: class MiddlewareChain:
@ -81,8 +81,8 @@ class MiddlewareChain:
async def execute( async def execute(
self, self,
ctx: RequestContext, ctx: RequestContext,
handler: Callable[[RequestContext], Awaitable[Any]], handler: Callable[[RequestContext], Awaitable[object]],
) -> Any: ) -> object:
"""执行中间件链 + handler。 """执行中间件链 + handler。
洋葱模型before 顺序执行 handler after 逆序执行 洋葱模型before 顺序执行 handler after 逆序执行
@ -125,7 +125,7 @@ class SummarizationMiddleware:
after: 无操作压缩在 before 完成 after: 无操作压缩在 before 完成
""" """
def __init__(self, compressor: Any = None) -> None: def __init__(self, compressor: object | None = None) -> None:
self._compressor = compressor self._compressor = compressor
async def before(self, ctx: RequestContext) -> RequestContext: async def before(self, ctx: RequestContext) -> RequestContext:
@ -143,7 +143,7 @@ class SummarizationMiddleware:
logger.warning(f"SummarizationMiddleware: compression failed: {e}") logger.warning(f"SummarizationMiddleware: compression failed: {e}")
return ctx return ctx
async def after(self, ctx: RequestContext, result: Any) -> Any: async def after(self, ctx: RequestContext, result: object) -> object:
return result return result
@ -157,7 +157,7 @@ class TokenUsageMiddleware:
async def before(self, ctx: RequestContext) -> RequestContext: async def before(self, ctx: RequestContext) -> RequestContext:
return ctx return ctx
async def after(self, ctx: RequestContext, result: Any) -> Any: async def after(self, ctx: RequestContext, result: object) -> object:
# 从 ReActResult 或类似结构提取 token 用量 # 从 ReActResult 或类似结构提取 token 用量
# ReActResult 有 total_tokens 属性(非 token_usage # ReActResult 有 total_tokens 属性(非 token_usage
usage = getattr(result, "total_tokens", None) usage = getattr(result, "total_tokens", None)
@ -184,7 +184,7 @@ class LoopDetectionMiddleware:
ctx.metadata["loop_detection_window"] = [] ctx.metadata["loop_detection_window"] = []
return ctx 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 [] trajectory = getattr(result, "trajectory", None) or []
if len(trajectory) < self._threshold: if len(trajectory) < self._threshold:
return result return result

View File

@ -16,9 +16,9 @@ import asyncio
import logging import logging
import random import random
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass
from enum import Enum 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.plan_schema import ExecutionPlan, PlanStep, PlanStepStatus
from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus
@ -42,7 +42,7 @@ class StepExecutionResult:
step_id: str step_id: str
status: PlanStepStatus status: PlanStepStatus
result: dict[str, Any] | None = None result: dict[str, object] | None = None
error: str | None = None error: str | None = None
retry_count: int = 0 retry_count: int = 0
duration_ms: float = 0.0 duration_ms: float = 0.0
@ -91,7 +91,7 @@ class PlanExecutor:
def __init__( def __init__(
self, self,
agent_pool: Any, agent_pool: object,
max_retries: int = 2, max_retries: int = 2,
step_timeout: float = 300.0, step_timeout: float = 300.0,
max_parallel: int = 5, max_parallel: int = 5,
@ -207,7 +207,7 @@ class PlanExecutor:
async def _execute_step_with_retry( async def _execute_step_with_retry(
self, self,
step: PlanStep, step: PlanStep,
input_data: dict[str, Any], input_data: dict[str, object],
original_task: TaskMessage, original_task: TaskMessage,
) -> StepExecutionResult: ) -> StepExecutionResult:
"""执行单个步骤,支持重试 """执行单个步骤,支持重试
@ -281,9 +281,9 @@ class PlanExecutor:
async def _execute_step_once( async def _execute_step_once(
self, self,
step: PlanStep, step: PlanStep,
input_data: dict[str, Any], input_data: dict[str, object],
original_task: TaskMessage, original_task: TaskMessage,
) -> dict[str, Any]: ) -> dict[str, object]:
"""执行单个步骤一次 """执行单个步骤一次
通过 AgentPool 创建 Agent 执行步骤 通过 AgentPool 创建 Agent 执行步骤
@ -463,7 +463,7 @@ class PlanExecutor:
self, self,
step: PlanStep, step: PlanStep,
step_results: dict[str, StepExecutionResult], step_results: dict[str, StepExecutionResult],
) -> dict[str, Any]: ) -> dict[str, object]:
"""将依赖步骤的结果注入到当前步骤的输入中 """将依赖步骤的结果注入到当前步骤的输入中
兼容 Orchestrator subtask_results 累积模式 兼容 Orchestrator subtask_results 累积模式
@ -471,7 +471,7 @@ class PlanExecutor:
enriched = dict(step.input_data) enriched = dict(step.input_data)
if step.dependencies: if step.dependencies:
dep_results: dict[str, dict[str, Any]] = {} dep_results: dict[str, dict[str, object]] = {}
for dep_id in step.dependencies: for dep_id in step.dependencies:
if dep_id in step_results: if dep_id in step_results:
dep_result = step_results[dep_id] dep_result = step_results[dep_id]

View File

@ -13,7 +13,7 @@ from collections import Counter, deque
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, timezone 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.exceptions import LLMProviderError, LoopDetectedError, TaskCancelledError, TaskTimeoutError
from agentkit.core.protocol import CancellationToken from agentkit.core.protocol import CancellationToken
@ -43,13 +43,13 @@ class ReActStep:
step: int step: int
action: str # "tool_call" or "final_answer" action: str # "tool_call" or "final_answer"
tool_name: str | None = None tool_name: str | None = None
arguments: dict[str, Any] | None = None arguments: dict[str, object] | None = None
result: Any = None result: object = None
content: str | None = None content: str | None = None
tokens: int = 0 tokens: int = 0
async def _ensure_async_iterable(obj: Any, label: str = "<obj>"): async def _ensure_async_iterable(obj: object, label: str = "<obj>"):
"""Defensive helper: ensure the given object is an async iterable. """Defensive helper: ensure the given object is an async iterable.
Guards against the recurring ``'async for' requires an object with 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" event_type: str # "thinking","token","tool_call","tool_result","confirmation_request","confirmation_result","phase_violation","step","final_answer","final_result","error"
step: int 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()) 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() # step and yields phase_violation ReActEvents. Non-streaming execute()
# simply ignores the accumulator (the error dict returned to the LLM is # simply ignores the accumulator (the error dict returned to the LLM is
# the only signal there). # the only signal there).
self._phase_violations: list[dict[str, Any]] = [] self._phase_violations: list[dict[str, object]] = []
def reset(self) -> None: def reset(self) -> None:
"""Reset internal state for reuse across conversations. """Reset internal state for reuse across conversations.
@ -299,8 +299,8 @@ class ReActEngine:
return False return False
def _check_phase_permission( def _check_phase_permission(
self, tool_name: str, arguments: dict[str, Any] self, tool_name: str, arguments: dict[str, object]
) -> dict[str, Any] | None: ) -> dict[str, object] | None:
"""Return None if tool is allowed; return a structured error dict if blocked. """Return None if tool is allowed; return a structured error dict if blocked.
The error dict replaces what `_execute_tool` would have returned The error dict replaces what `_execute_tool` would have returned
@ -351,7 +351,7 @@ class ReActEngine:
return violation return violation
return None 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 在窗口内出现 将当前步的工具调用 hash 加入滑动窗口若同一 hash 在窗口内出现
@ -406,10 +406,10 @@ class ReActEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
timeout_seconds: float | None = None, timeout_seconds: float | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> ReActResult: ) -> ReActResult:
"""执行 ReAct 循环 """执行 ReAct 循环
@ -536,7 +536,7 @@ class ReActEngine:
async def _run_loop_and_extract( async def _run_loop_and_extract(
self, self,
**kwargs: Any, **kwargs: object,
) -> ReActResult: ) -> ReActResult:
"""Collect all events from _execute_loop and extract the final ReActResult. """Collect all events from _execute_loop and extract the final ReActResult.
@ -564,9 +564,9 @@ class ReActEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
stream: bool = False, stream: bool = False,
effective_timeout: float = 0.0, effective_timeout: float = 0.0,
) -> AsyncGenerator[ReActEvent, None]: ) -> 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( system_content = self._build_system_message(
stable=system_prompt or "", stable=system_prompt or "",
volatile=memory_context, volatile=memory_context,
@ -726,7 +726,7 @@ class ReActEngine:
# 流式模式:用 chat_streamyield token events # 流式模式:用 chat_streamyield token events
stream_content_chunks: list[str] = [] stream_content_chunks: list[str] = []
stream_usage = None stream_usage = None
stream_tool_calls: list[Any] = [] stream_tool_calls: list[object] = []
stream_model = model stream_model = model
# U3/G8: delta_flush 节流 buffer # U3/G8: delta_flush 节流 buffer
_flush_buffer: list[str] = [] _flush_buffer: list[str] = []
@ -840,7 +840,7 @@ class ReActEngine:
) )
# Act: 记录 assistant 消息(含 tool_calls到对话历史 # Act: 记录 assistant 消息(含 tool_calls到对话历史
assistant_msg: dict[str, Any] = { assistant_msg: dict[str, object] = {
"role": "assistant", "role": "assistant",
"content": response.content or "", "content": response.content or "",
"tool_calls": [ "tool_calls": [
@ -874,7 +874,7 @@ class ReActEngine:
if i in parallelizable_set 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) # Execute serial tools first (handles confirmation flow)
for i, tc in serial_calls: for i, tc in serial_calls:
@ -1452,10 +1452,10 @@ class ReActEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
timeout_seconds: float | None = None, timeout_seconds: float | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> AsyncGenerator[ReActEvent, None]: ) -> AsyncGenerator[ReActEvent, None]:
"""Execute ReAct loop, yielding ReActEvent objects. """Execute ReAct loop, yielding ReActEvent objects.
@ -1514,7 +1514,7 @@ class ReActEngine:
volatile: str, volatile: str,
*, *,
model: str, model: str,
) -> str | list[dict[str, Any]] | None: ) -> str | list[dict[str, object]] | None:
"""构建双块结构 system message(stable + volatile)。 """构建双块结构 system message(stable + volatile)。
- prompt_cache_enable=False 或无 stable+volatile 返回 str( None) - prompt_cache_enable=False 或无 stable+volatile 返回 str( None)
@ -1535,7 +1535,7 @@ class ReActEngine:
provider_name = self._get_provider_name(model) provider_name = self._get_provider_name(model)
if provider_name == "anthropic": if provider_name == "anthropic":
blocks: list[dict[str, Any]] = [] blocks: list[dict[str, object]] = []
if stable: if stable:
blocks.append( blocks.append(
{ {
@ -1675,8 +1675,8 @@ class ReActEngine:
@staticmethod @staticmethod
def _build_response_from_stream( def _build_response_from_stream(
content: str, content: str,
tool_calls: list[Any], tool_calls: list[object],
usage: Any, usage: object,
model: str, model: str,
) -> LLMResponse: ) -> LLMResponse:
"""Build an LLMResponse from accumulated stream chunks.""" """Build an LLMResponse from accumulated stream chunks."""
@ -1723,7 +1723,7 @@ class ReActEngine:
async def _build_tool_result_message( async def _build_tool_result_message(
self, self,
tool_call_id: str, tool_call_id: str,
result: Any, result: object,
compressor: "CompressionStrategy | None" = None, compressor: "CompressionStrategy | None" = None,
tool_name: str | None = None, tool_name: str | None = None,
) -> dict: ) -> dict:
@ -1742,7 +1742,7 @@ class ReActEngine:
} }
async def _execute_tool( 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: ) -> dict:
"""执行工具调用,处理成功和失败情况""" """执行工具调用,处理成功和失败情况"""
# U3/G6: phase enforcement — check before dispatch. If the tool is # U3/G6: phase enforcement — check before dispatch. If the tool is
@ -1788,11 +1788,11 @@ class ReActEngine:
async def _execute_tool_with_confirmation( async def _execute_tool_with_confirmation(
self, self,
tc: Any, tc: object,
tools: list[Tool], tools: list[Tool],
step: int, step: int,
confirmation_handler: Any, confirmation_handler: Callable[..., Awaitable[object]] | None,
) -> tuple[Any, list[ReActEvent]]: ) -> tuple[object, list[ReActEvent]]:
"""Execute a tool call with confirmation flow support. """Execute a tool call with confirmation flow support.
Used in the parallel execution path for serial (non-parallelizable) tools Used in the parallel execution path for serial (non-parallelizable) tools
@ -1886,7 +1886,7 @@ class ReActEngine:
return tool_result, events 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. """Determine if tool calls should be executed in parallel.
- parallel_tools=True: always parallel (if >1 tool) - parallel_tools=True: always parallel (if >1 tool)
@ -1905,7 +1905,7 @@ class ReActEngine:
return len(parallelizable_indices) > 1 return len(parallelizable_indices) > 1
return False 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. """Get indices of tool_calls that have _parallelizable=true in arguments.
LLM marks parallelizable tools by including _parallelizable: true LLM marks parallelizable tools by including _parallelizable: true
@ -1918,7 +1918,7 @@ class ReActEngine:
indices.append(i) indices.append(i)
return indices 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``` 2. ```tool\n{"name": "...", "arguments": {...}}\n```
3. <tool_use>\n{"name": "...", "arguments": {...}}\n</tool_use> 3. <tool_use>\n{"name": "...", "arguments": {...}}\n</tool_use>
""" """
calls: list[dict[str, Any]] = [] calls: list[dict[str, object]] = []
# 格式 1: Action: tool_name(args) # 格式 1: Action: tool_name(args)
action_pattern = re.compile(r"Action:\s*(\w+)\((.+?)\)", re.DOTALL) action_pattern = re.compile(r"Action:\s*(\w+)\((.+?)\)", re.DOTALL)
@ -1999,7 +1999,7 @@ class ReActEngine:
return calls return calls
@staticmethod @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 return None
name = name_match.group(1) name = name_match.group(1)
arguments: dict[str, Any] = {} arguments: dict[str, object] = {}
# 提取 "key": "value" 模式 # 提取 "key": "value" 模式
for kv_match in re.finditer(r'"(\w+)"\s*:\s*"([^"]*)"', text): for kv_match in re.finditer(r'"(\w+)"\s*:\s*"([^"]*)"', text):
key = kv_match.group(1) key = kv_match.group(1)

View File

@ -11,7 +11,7 @@ import logging
import re import re
import time import time
from dataclasses import dataclass, field 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.exceptions import LLMProviderError, TaskCancelledError, TaskTimeoutError
from agentkit.core.protocol import CancellationToken from agentkit.core.protocol import CancellationToken
@ -52,7 +52,7 @@ class ReWOOPlanStep:
step_id: int step_id: int
tool_name: str tool_name: str
arguments: dict[str, Any] arguments: dict[str, object]
reasoning: str = "" reasoning: str = ""
@ -164,10 +164,10 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
timeout_seconds: float | None = None, timeout_seconds: float | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> ReActResult: ) -> ReActResult:
"""执行 ReWOO 三阶段流程 """执行 ReWOO 三阶段流程
@ -241,9 +241,9 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> ReActResult: ) -> ReActResult:
tools = tools or [] tools = tools or []
tool_schemas = self._build_tool_schemas(tools) if tools else None tool_schemas = self._build_tool_schemas(tools) if tools else None
@ -350,7 +350,7 @@ class ReWOOEngine:
# 如果计划为空(无需工具),直接让 LLM 回答 # 如果计划为空(无需工具),直接让 LLM 回答
if not plan.steps: if not plan.steps:
llm_messages: list[dict[str, Any]] = [] llm_messages: list[dict[str, object]] = []
if effective_system_prompt: if effective_system_prompt:
llm_messages.append({"role": "system", "content": effective_system_prompt}) llm_messages.append({"role": "system", "content": effective_system_prompt})
llm_messages.extend(messages) llm_messages.extend(messages)
@ -399,7 +399,7 @@ class ReWOOEngine:
) )
# ── Phase 2: Execution ── # ── Phase 2: Execution ──
tool_results: list[dict[str, Any]] = [] tool_results: list[dict[str, object]] = []
for plan_step in plan.steps: for plan_step in plan.steps:
# 协作式取消检查 # 协作式取消检查
if cancellation_token is not None: if cancellation_token is not None:
@ -524,10 +524,10 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
timeout_seconds: float | 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. """Execute ReWOO flow, yielding ReActEvent objects.
@ -637,7 +637,7 @@ class ReWOOEngine:
# Empty plan: direct answer # Empty plan: direct answer
if not plan.steps: if not plan.steps:
llm_messages: list[dict[str, Any]] = [] llm_messages: list[dict[str, object]] = []
if effective_system_prompt: if effective_system_prompt:
llm_messages.append({"role": "system", "content": effective_system_prompt}) llm_messages.append({"role": "system", "content": effective_system_prompt})
llm_messages.extend(messages) llm_messages.extend(messages)
@ -676,7 +676,7 @@ class ReWOOEngine:
return return
# ── Phase 2: Execution ── # ── Phase 2: Execution ──
tool_results: list[dict[str, Any]] = [] tool_results: list[dict[str, object]] = []
for plan_step in plan.steps: for plan_step in plan.steps:
if cancellation_token is not None: if cancellation_token is not None:
cancellation_token.check() cancellation_token.check()
@ -806,10 +806,10 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
total_tokens: int = 0, 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. """Stream version: try fallback strategies in configured order, yielding events from the first successful one.
@ -902,7 +902,7 @@ class ReWOOEngine:
"reasoning": plan.reasoning, "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], "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: for plan_step in plan.steps:
if cancellation_token is not None: if cancellation_token is not None:
cancellation_token.check() cancellation_token.check()
@ -934,9 +934,9 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
): ):
"""Stream: ReAct fallback""" """Stream: ReAct fallback"""
logger.warning("ReWOO planning failed in stream mode, falling back to ReActEngine") logger.warning("ReWOO planning failed in stream mode, falling back to ReActEngine")
@ -969,7 +969,7 @@ class ReWOOEngine:
"""Stream: Direct LLM fallback""" """Stream: Direct LLM fallback"""
logger.warning("Falling back to direct LLM call in stream mode") logger.warning("Falling back to direct LLM call in stream mode")
try: try:
direct_messages: list[dict[str, Any]] = [] direct_messages: list[dict[str, object]] = []
if effective_system_prompt: if effective_system_prompt:
direct_messages.append({"role": "system", "content": effective_system_prompt}) direct_messages.append({"role": "system", "content": effective_system_prompt})
direct_messages.extend(messages) direct_messages.extend(messages)
@ -1012,7 +1012,7 @@ class ReWOOEngine:
"reasoning": plan.reasoning, "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], "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: for plan_step in plan.steps:
if cancellation_token is not None: if cancellation_token is not None:
cancellation_token.check() cancellation_token.check()
@ -1043,11 +1043,11 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
trajectory: list[ReActStep] | None = None, trajectory: list[ReActStep] | None = None,
total_tokens: int = 0, total_tokens: int = 0,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> ReActResult | None: ) -> ReActResult | None:
"""按配置的 fallback 策略顺序尝试回退,返回第一个成功的结果 """按配置的 fallback 策略顺序尝试回退,返回第一个成功的结果
@ -1136,7 +1136,7 @@ class ReWOOEngine:
# Execute the simplified plan # Execute the simplified plan
trajectory: list[ReActStep] = [] trajectory: list[ReActStep] = []
total_tokens = simplified_tokens total_tokens = simplified_tokens
tool_results: list[dict[str, Any]] = [] tool_results: list[dict[str, object]] = []
for plan_step in plan.steps: for plan_step in plan.steps:
if cancellation_token is not None: if cancellation_token is not None:
cancellation_token.check() cancellation_token.check()
@ -1195,9 +1195,9 @@ class ReWOOEngine:
memory_retriever: "MemoryRetriever | None" = None, memory_retriever: "MemoryRetriever | None" = None,
task_id: str | None = None, task_id: str | None = None,
compressor: "CompressionStrategy | 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, cancellation_token: CancellationToken | None = None,
confirmation_handler: Any | None = None, confirmation_handler: Callable[..., Awaitable[object]] | None = None,
) -> ReActResult | None: ) -> ReActResult | None:
"""ReAct fallback: delegate to ReActEngine""" """ReAct fallback: delegate to ReActEngine"""
logger.warning("ReWOO planning failed, falling back to ReActEngine") logger.warning("ReWOO planning failed, falling back to ReActEngine")
@ -1240,7 +1240,7 @@ class ReWOOEngine:
"""Direct fallback: simple LLM call without tools""" """Direct fallback: simple LLM call without tools"""
logger.warning("Falling back to direct LLM call") logger.warning("Falling back to direct LLM call")
try: try:
direct_messages: list[dict[str, Any]] = [] direct_messages: list[dict[str, object]] = []
if effective_system_prompt: if effective_system_prompt:
direct_messages.append({"role": "system", "content": effective_system_prompt}) direct_messages.append({"role": "system", "content": effective_system_prompt})
direct_messages.extend(messages) direct_messages.extend(messages)
@ -1319,7 +1319,7 @@ class ReWOOEngine:
if plan is not None and plan.steps: if plan is not None and plan.steps:
trajectory: list[ReActStep] = [] trajectory: list[ReActStep] = []
total_tokens = plan_tokens total_tokens = plan_tokens
tool_results: list[dict[str, Any]] = [] tool_results: list[dict[str, object]] = []
for plan_step in plan.steps: for plan_step in plan.steps:
if cancellation_token is not None: if cancellation_token is not None:
cancellation_token.check() cancellation_token.check()
@ -1396,7 +1396,7 @@ class ReWOOEngine:
tool_descriptions = self._build_tool_descriptions(tools) 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}, {"role": "system", "content": _PLANNING_SYSTEM_PROMPT},
] ]
@ -1451,7 +1451,7 @@ class ReWOOEngine:
async def _synthesis_phase( async def _synthesis_phase(
self, self,
messages: list[dict[str, str]], messages: list[dict[str, str]],
tool_results: list[dict[str, Any]], tool_results: list[dict[str, object]],
model: str, model: str,
agent_name: str, agent_name: str,
task_type: str, task_type: str,
@ -1468,7 +1468,7 @@ class ReWOOEngine:
cancellation_token.check() cancellation_token.check()
# 构建综合消息 # 构建综合消息
synthesis_messages: list[dict[str, Any]] = [ synthesis_messages: list[dict[str, object]] = [
{"role": "system", "content": _SYNTHESIS_SYSTEM_PROMPT}, {"role": "system", "content": _SYNTHESIS_SYSTEM_PROMPT},
] ]
@ -1600,7 +1600,7 @@ class ReWOOEngine:
return None return None
async def _execute_tool( 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: ) -> dict:
"""执行工具调用,处理成功和失败情况""" """执行工具调用,处理成功和失败情况"""
tool = self._find_tool(tool_name, tools) tool = self._find_tool(tool_name, tools)

View File

@ -8,6 +8,8 @@ import math
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy.exc import DBAPIError
if TYPE_CHECKING: if TYPE_CHECKING:
from agentkit.evolution.evolution_store import InMemoryEvolutionStore from agentkit.evolution.evolution_store import InMemoryEvolutionStore
@ -129,7 +131,7 @@ class ABTester:
sample_count=len(experiment_metrics), sample_count=len(experiment_metrics),
) )
logger.info(f"A/B test results persisted for test '{test_id}'") 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}") logger.error(f"Failed to persist A/B test results: {e}")
async def evaluate(self, test_id: str) -> ABTestResult | None: async def evaluate(self, test_id: str) -> ABTestResult | None:

View File

@ -10,6 +10,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
import math import math
import re import re
@ -18,6 +19,7 @@ from datetime import datetime, timedelta, timezone
from typing import Any from typing import Any
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.exc import DBAPIError
_SAFE_TABLE_NAME_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') _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() text = experience.text_for_embedding()
try: try:
experience.embedding = await self._embedder.embed(text) 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}") logger.warning(f"Failed to generate embedding for experience {experience.experience_id}: {e}")
async with self._session_factory() as db: async with self._session_factory() as db:
@ -122,7 +124,7 @@ class ExperienceStore:
f"task_type={experience.task_type} outcome={experience.outcome}" f"task_type={experience.task_type} outcome={experience.outcome}"
) )
return experience.experience_id return experience.experience_id
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to record experience: {e}") logger.error(f"Failed to record experience: {e}")
raise raise
@ -149,7 +151,7 @@ class ExperienceStore:
if self._pgvector_enabled and self._embedder: if self._pgvector_enabled and self._embedder:
return await self._search_pgvector(db, query, top_k, task_type, search_multiplier) 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) 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}") logger.error(f"Failed to search experiences: {e}")
return [] return []
@ -343,7 +345,7 @@ class ExperienceStore:
) )
) )
return metrics_list return metrics_list
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
logger.error(f"Failed to get metrics: {e}") logger.error(f"Failed to get metrics: {e}")
return [] return []
@ -375,7 +377,7 @@ class InMemoryExperienceStore:
text = experience.text_for_embedding() text = experience.text_for_embedding()
try: try:
experience.embedding = await self._embedder.embed(text) 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}") logger.warning(f"Failed to generate embedding for experience {experience.experience_id}: {e}")
# 存储副本,避免外部修改影响内部状态 # 存储副本,避免外部修改影响内部状态
@ -411,7 +413,7 @@ class InMemoryExperienceStore:
if self._embedder: if self._embedder:
try: try:
query_embedding = await self._embedder.embed(query) 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}") logger.warning(f"Failed to generate query embedding: {e}")
# 筛选候选 # 筛选候选

View File

@ -13,6 +13,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import copy import copy
import logging import logging
import random import random
@ -292,7 +293,7 @@ class MutationOperator:
model="default", model="default",
) )
return response.content.strip() or instructions 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}") logger.warning(f"LLM instruction mutation failed: {e}")
# Fallback: simple text mutation (shuffle paragraphs) # Fallback: simple text mutation (shuffle paragraphs)

View File

@ -8,6 +8,8 @@ from dataclasses import dataclass, field
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any
from sqlalchemy.exc import DBAPIError
from agentkit.core.protocol import EvolutionEvent, TaskMessage, TaskResult from agentkit.core.protocol import EvolutionEvent, TaskMessage, TaskResult
from agentkit.evolution.ab_tester import ABTestConfig, ABTestResult, ABTester from agentkit.evolution.ab_tester import ABTestConfig, ABTestResult, ABTester
from agentkit.evolution.evolution_store import EvolutionStore from agentkit.evolution.evolution_store import EvolutionStore
@ -371,7 +373,7 @@ class EvolutionMixin:
entry.event_id = event_id entry.event_id = event_id
break break
return True return True
except Exception as e: except (DBAPIError, RuntimeError, ValueError, KeyError) as e:
logger.error(f"Failed to apply evolution change: {e}") logger.error(f"Failed to apply evolution change: {e}")
return False return False
@ -382,7 +384,7 @@ class EvolutionMixin:
try: try:
return await self._evolution_store.rollback(log_entry.event_id) 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}") logger.error(f"Failed to rollback evolution change: {e}")
return False return False

View File

@ -3,6 +3,7 @@
通过 LLM 分析执行轨迹生成结构化反思 RuleBasedReflector 提供更深入的洞察 通过 LLM 分析执行轨迹生成结构化反思 RuleBasedReflector 提供更深入的洞察
""" """
import asyncio
import json import json
import logging import logging
import re import re
@ -62,7 +63,7 @@ class LLMReflector:
task_type="reflection", task_type="reflection",
) )
return self._parse_reflection_response(response.content, task, result) 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}") logger.warning(f"LLM reflection failed, returning default: {e}")
return Reflection( return Reflection(
task_id=getattr(task, "task_id", "unknown"), task_id=getattr(task, "task_id", "unknown"),

View File

@ -13,6 +13,7 @@ from typing import Any
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy import Column, DateTime, Float, Integer, String, Text, UniqueConstraint from sqlalchemy import Column, DateTime, Float, Integer, String, Text, UniqueConstraint
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
from agentkit.core.protocol import EvolutionEvent from agentkit.core.protocol import EvolutionEvent
@ -154,7 +155,7 @@ class PostgreSQLEvolutionStore:
event.event_id = event_id event.event_id = event_id
logger.info(f"Evolution event recorded: {event_id} for agent '{event.agent_name}'") logger.info(f"Evolution event recorded: {event_id} for agent '{event.agent_name}'")
return event_id return event_id
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to record evolution event: {e}") logger.error(f"Failed to record evolution event: {e}")
raise raise
@ -178,7 +179,7 @@ class PostgreSQLEvolutionStore:
await db.commit() await db.commit()
logger.info(f"Evolution event {event_id} rolled back") logger.info(f"Evolution event {event_id} rolled back")
return True return True
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to rollback evolution event {event_id}: {e}") logger.error(f"Failed to rollback evolution event {event_id}: {e}")
return False return False
@ -218,7 +219,7 @@ class PostgreSQLEvolutionStore:
} }
for e in entries for e in entries
] ]
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
logger.error(f"Failed to list evolution events: {e}") logger.error(f"Failed to list evolution events: {e}")
return [] return []
@ -242,7 +243,7 @@ class PostgreSQLEvolutionStore:
db.add(entry) db.add(entry)
await db.commit() await db.commit()
return vid return vid
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to record skill version: {e}") logger.error(f"Failed to record skill version: {e}")
raise raise
@ -271,7 +272,7 @@ class PostgreSQLEvolutionStore:
} }
for e in entries for e in entries
] ]
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
logger.error(f"Failed to list skill versions: {e}") logger.error(f"Failed to list skill versions: {e}")
return [] return []
@ -295,7 +296,7 @@ class PostgreSQLEvolutionStore:
db.add(entry) db.add(entry)
await db.commit() await db.commit()
return rid return rid
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to record A/B test result: {e}") logger.error(f"Failed to record A/B test result: {e}")
raise raise
@ -324,6 +325,6 @@ class PostgreSQLEvolutionStore:
} }
for e in entries 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}") logger.error(f"Failed to get A/B test results: {e}")
return [] return []

View File

@ -151,7 +151,7 @@ class PitfallDetector:
task_type=task_type, task_type=task_type,
) )
return results return results
except Exception as e: except (RuntimeError, ValueError, KeyError) as e:
logger.error(f"Failed to search experiences for pitfall detection: {e}") logger.error(f"Failed to search experiences for pitfall detection: {e}")
return [] return []

View File

@ -10,6 +10,7 @@
- LLMPromptOptimizer: 基于 LLM 分析反思结果生成改进指令 - LLMPromptOptimizer: 基于 LLM 分析反思结果生成改进指令
""" """
import asyncio
import logging import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any from typing import Any
@ -202,7 +203,7 @@ class LLMPromptOptimizer:
""" """
try: try:
optimized_instruction = await self._llm_optimize_instruction(module, trace, reflection) 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}") logger.warning(f"LLM prompt optimization failed, falling back to bootstrap: {e}")
return await self._bootstrap.optimize(module) return await self._bootstrap.optimize(module)

View File

@ -8,6 +8,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import json import json
import logging import logging
import re import re
@ -79,7 +80,7 @@ class RiskGuardLearner:
top_k=top_k, top_k=top_k,
task_type=skill_name, 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}") logger.warning(f"RiskGuardLearner: failed to search experiences: {e}")
return [] return []
@ -95,7 +96,7 @@ class RiskGuardLearner:
# 2. 构建 LLM prompt # 2. 构建 LLM prompt
try: try:
prompt = self._build_prompt(failures) prompt = self._build_prompt(failures)
except Exception as e: except (ValueError, KeyError, RuntimeError) as e:
logger.warning(f"RiskGuardLearner: failed to build prompt: {e}") logger.warning(f"RiskGuardLearner: failed to build prompt: {e}")
return [] return []
@ -117,14 +118,14 @@ class RiskGuardLearner:
agent_name="risk_guard_learner", agent_name="risk_guard_learner",
task_type="risk_guard_learning", 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}") logger.warning(f"RiskGuardLearner: LLM call failed: {e}")
return [] return []
# 4. 解析响应 # 4. 解析响应
try: try:
return self._parse_response(response.content, source_ids) 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}") logger.warning(f"RiskGuardLearner: failed to parse response: {e}")
return [] return []

View File

@ -9,7 +9,7 @@ import asyncio
import json import json
import logging import logging
import re import re
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING
from .expert import Expert from .expert import Expert
from .plan import PhaseStatus, PlanPhase, TeamPlan from .plan import PhaseStatus, PlanPhase, TeamPlan
@ -28,7 +28,7 @@ class DebateRunnerMixin:
_phase_semaphore: asyncio.Semaphore _phase_semaphore: asyncio.Semaphore
MAX_DEBATE_ROUNDS: int 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). """Execute a DEBATE phase: Lead-facilitated structured debate (5 stages).
Parse config Lead opens experts argue in parallel rounds Lead Parse config Lead opens experts argue in parallel rounds Lead
summarizes Lead adjudicates write conclusion to workspace.""" summarizes Lead adjudicates write conclusion to workspace."""
@ -86,7 +86,7 @@ class DebateRunnerMixin:
) )
# Debate history for context (Lead opening + expert arguments + Lead summaries) # 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"} {"expert": lead.config.name, "content": opening, "round": 0, "role": "moderator"}
] ]
@ -103,7 +103,7 @@ class DebateRunnerMixin:
break break
# Experts argue in parallel (with concurrency limit) # 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: async with self._phase_semaphore:
return await self._generate_debate_argument(e, topic, history, round_num) return await self._generate_debate_argument(e, topic, history, round_num)
@ -234,7 +234,7 @@ class DebateRunnerMixin:
return f"辩论主题:{topic}。请各位专家发表看法。" return f"辩论主题:{topic}。请各位专家发表看法。"
async def _generate_debate_argument( 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: ) -> str:
"""Generate an expert's debate argument for the current round.""" """Generate an expert's debate argument for the current round."""
gateway = self._get_llm_gateway(expert) gateway = self._get_llm_gateway(expert)
@ -269,7 +269,7 @@ class DebateRunnerMixin:
return response.content.strip() return response.content.strip()
async def _generate_debate_summary( 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: ) -> str:
"""Generate Lead's summary of the current debate round.""" """Generate Lead's summary of the current debate round."""
gateway = self._get_llm_gateway(lead) gateway = self._get_llm_gateway(lead)
@ -307,8 +307,8 @@ class DebateRunnerMixin:
return f"[第 {round_num} 轮辩论完成,小结生成失败]" return f"[第 {round_num} 轮辩论完成,小结生成失败]"
async def _generate_debate_verdict( async def _generate_debate_verdict(
self, lead: Expert, topic: str, history: list[dict[str, Any]] self, lead: Expert, topic: str, history: list[dict[str, object]]
) -> dict[str, Any]: ) -> dict[str, object]:
"""Generate Lead's final verdict for the debate.""" """Generate Lead's final verdict for the debate."""
gateway = self._get_llm_gateway(lead) gateway = self._get_llm_gateway(lead)
if not gateway: if not gateway:
@ -371,7 +371,7 @@ class DebateRunnerMixin:
"conclusion": f"辩论主题:{topic}。裁决生成失败,建议参考辩论历史自行判断。", "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.""" """Format debate history as readable text for LLM prompts."""
if not history: if not history:
return "" return ""

View File

@ -6,7 +6,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING
from .expert import Expert from .expert import Expert
from .plan import PhaseStatus, PhaseType, PlanPhase, TeamPlan from .plan import PhaseStatus, PhaseType, PlanPhase, TeamPlan
@ -23,7 +23,7 @@ class DivergenceDetectorMixin:
# Shared state provided by TeamOrchestrator (annotations only) # Shared state provided by TeamOrchestrator (annotations only)
_team: ExpertTeam _team: ExpertTeam
_debate_count: int _debate_count: int
_checkpoint: Any _checkpoint: object
MAX_DEBATES: int MAX_DEBATES: int
async def _maybe_add_plan_review_debate(self, lead: Expert, plan: TeamPlan, task: str) -> None: async def _maybe_add_plan_review_debate(self, lead: Expert, plan: TeamPlan, task: str) -> None:

View File

@ -9,7 +9,7 @@ import asyncio
import copy import copy
import logging import logging
from datetime import datetime, timezone 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.config_driven import ConfigDrivenAgent
from agentkit.core.protocol import TaskMessage, TaskResult, TaskStatus 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. # U4: State offloading helpers — keep memory lean for long-horizon runs.
_OFFLOAD_SUMMARY_LIMIT = 500 _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.""" """Create an offloaded result: summary in memory, full content in workspace."""
if not isinstance(content, str): if not isinstance(content, str):
content = str(content) if content is not None else "" 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}") logger.warning(f"Failed to read offloaded output '{ref_key}': {e}")
return content 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.""" """Execute a single phase, dispatching by phase_type."""
if phase.phase_type == PhaseType.DEBATE: if phase.phase_type == PhaseType.DEBATE:
return await self._execute_debate_phase(phase, plan) return await self._execute_debate_phase(phase, plan)
return await self._execute_execution_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).""" """Execute a standard EXECUTION phase. Split into 3 sub-methods (U2, KTD3 isolation)."""
expert, agent, lead = await self._prepare_phase_context(phase, plan) expert, agent, lead = await self._prepare_phase_context(phase, plan)
last_error: str | None = None last_error: str | None = None
result: dict[str, Any] | None = None result: dict[str, object] | None = None
try: try:
# U3: 返工循环 — 最多 MAX_REWORKS + 1 次1 次初始 + MAX_REWORKS 次返工) # U3: 返工循环 — 最多 MAX_REWORKS + 1 次1 次初始 + MAX_REWORKS 次返工)
@ -135,11 +135,11 @@ class PhaseExecutorMixin:
self, self,
expert: Expert, expert: Expert,
phase: PlanPhase, phase: PlanPhase,
dependency_outputs: dict[str, Any], dependency_outputs: dict[str, object],
collaboration_outputs: dict[str, str], collaboration_outputs: dict[str, str],
) -> TaskMessage: ) -> TaskMessage:
"""Build TaskMessage for execution with context isolation.""" """Build TaskMessage for execution with context isolation."""
input_data: dict[str, Any] = { input_data: dict[str, object] = {
"task": phase.task_description, "task": phase.task_description,
"team_id": self._team.team_id, "team_id": self._team.team_id,
"phase_id": phase.id, "phase_id": phase.id,
@ -180,12 +180,12 @@ class PhaseExecutorMixin:
lead: Expert, lead: Expert,
phase: PlanPhase, phase: PlanPhase,
plan: TeamPlan, 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 """Run one rework iteration: read deps, build input, execute, review. Returns
(result, last_error, passed, feedback, degraded). Raises RuntimeError on retry (result, last_error, passed, feedback, degraded). Raises RuntimeError on retry
exhaustion.""" exhaustion."""
# 每次迭代重新读取依赖输出(前置阶段可能在返工期间完成) # 每次迭代重新读取依赖输出(前置阶段可能在返工期间完成)
dependency_outputs: dict[str, Any] = {} dependency_outputs: dict[str, object] = {}
for dep_id in phase.depends_on: for dep_id in phase.depends_on:
dep_phase = plan.get_phase(dep_id) dep_phase = plan.get_phase(dep_id)
if dep_phase and dep_phase.status == PhaseStatus.COMPLETED and dep_phase.result: if dep_phase and dep_phase.status == PhaseStatus.COMPLETED and dep_phase.result:
@ -216,7 +216,7 @@ class PhaseExecutorMixin:
# 执行专家任务带重试MAX_RETRIES 处理瞬时失败) # 执行专家任务带重试MAX_RETRIES 处理瞬时失败)
last_error: str | None = None 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): for attempt in range(self.MAX_RETRIES + 1):
try: try:
task_result: TaskResult = await agent.execute(task_msg) task_result: TaskResult = await agent.execute(task_msg)
@ -265,7 +265,7 @@ class PhaseExecutorMixin:
lead: Expert, lead: Expert,
phase: PlanPhase, phase: PlanPhase,
plan: TeamPlan, plan: TeamPlan,
result: dict[str, Any], result: dict[str, object],
passed: bool, passed: bool,
feedback: str, feedback: str,
degraded: bool = False, degraded: bool = False,

View File

@ -10,7 +10,6 @@ import json
import logging import logging
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from agentkit.core.exceptions import LLMProviderError from agentkit.core.exceptions import LLMProviderError
@ -45,7 +44,7 @@ class ReviewGateMixin:
"""Mixin: Lead 验收阶段输出质量 + 解析风险标记。由 TeamOrchestrator 组合。""" """Mixin: Lead 验收阶段输出质量 + 解析风险标记。由 TeamOrchestrator 组合。"""
async def _review_phase_output( async def _review_phase_output(
self, lead: Expert, phase: PlanPhase, result: dict[str, Any] self, lead: Expert, phase: PlanPhase, result: dict[str, object]
) -> ReviewResult: ) -> ReviewResult:
"""Lead 验收阶段输出质量。 """Lead 验收阶段输出质量。
@ -93,7 +92,7 @@ class ReviewGateMixin:
) )
# P2: 优先尝试直接解析整个响应为 JSON避免贪婪正则匹配过多 # P2: 优先尝试直接解析整个响应为 JSON避免贪婪正则匹配过多
review: dict[str, Any] | None = None review: dict[str, object] | None = None
try: try:
review = json.loads(response.content) review = json.loads(response.content)
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):

View File

@ -6,7 +6,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING
from agentkit.orchestrator.rollback import RollbackExecutor from agentkit.orchestrator.rollback import RollbackExecutor
@ -27,7 +27,7 @@ class RollbackHandlerMixin:
_rollback_timeout: float _rollback_timeout: float
async def _mark_dependents_failed( 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: ) -> None:
"""Mark all phases that depend on the failed phase as FAILED.""" """Mark all phases that depend on the failed phase as FAILED."""
for ph in plan.phases: for ph in plan.phases:

View File

@ -7,7 +7,7 @@ from __future__ import annotations
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING
from agentkit.core.protocol import TaskMessage, TaskResult from agentkit.core.protocol import TaskMessage, TaskResult
@ -29,7 +29,7 @@ class SynthesizerMixin:
async def _synthesize_results( async def _synthesize_results(
self, lead: Expert, task: str, completed_phases: list[PlanPhase] self, lead: Expert, task: str, completed_phases: list[PlanPhase]
) -> dict[str, Any]: ) -> dict[str, object]:
"""Lead Expert synthesizes results using BEST strategy. """Lead Expert synthesizes results using BEST strategy.
The Lead Expert evaluates all completed phase results and produces The Lead Expert evaluates all completed phase results and produces
@ -114,8 +114,8 @@ class SynthesizerMixin:
self, self,
task: str, task: str,
plan: TeamPlan, plan: TeamPlan,
phase_results: dict[str, dict[str, Any]], phase_results: dict[str, dict[str, object]],
) -> dict[str, Any]: ) -> dict[str, object]:
"""Fallback to single agent mode when pipeline execution fails. """Fallback to single agent mode when pipeline execution fails.
Uses the lead expert (or first active expert) to complete the original task. Uses the lead expert (or first active expert) to complete the original task.
@ -128,7 +128,7 @@ class SynthesizerMixin:
active = self._team.active_experts active = self._team.active_experts
expert = active[0] if active else None expert = active[0] if active else None
fallback_result: dict[str, Any] | None = None fallback_result: dict[str, object] | None = None
if expert: if expert:
try: try:
task_msg = TaskMessage( task_msg = TaskMessage(

View File

@ -16,7 +16,6 @@ import enum
import logging import logging
import time import time
import uuid import uuid
from typing import Any
from .config import ExpertConfig from .config import ExpertConfig
from .expert import Expert from .expert import Expert
@ -72,7 +71,7 @@ class BoardTeam:
# Discussion state # Discussion state
self._topic: str = "" self._topic: str = ""
self._history: list[dict[str, Any]] = [] self._history: list[dict[str, object]] = []
self._current_round: int = 0 self._current_round: int = 0
self._max_rounds: int = max_rounds self._max_rounds: int = max_rounds
self._user_interventions: list[str] = [] # Pending user messages self._user_interventions: list[str] = [] # Pending user messages
@ -125,7 +124,7 @@ class BoardTeam:
return self._max_rounds return self._max_rounds
@property @property
def history(self) -> list[dict[str, Any]]: def history(self) -> list[dict[str, object]]:
return self._history.copy() return self._history.copy()
def get_expert(self, name: str) -> Expert | None: def get_expert(self, name: str) -> Expert | None:
@ -254,7 +253,7 @@ class BoardTeam:
return "\n\n---\n\n".join(lines) 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. """Compress discussion history when it exceeds token threshold.
The moderator summarizes each round's key points, replacing The moderator summarizes each round's key points, replacing
@ -291,7 +290,7 @@ class BoardTeam:
# Parse compressed history back to entries # Parse compressed history back to entries
# This is a best-effort compression; if parsing fails, keep original # 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 current_round = 0
for line in compressed.split("\n"): for line in compressed.split("\n"):
line = line.strip() line = line.strip()

View File

@ -19,7 +19,6 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import Any
from .expert import Expert from .expert import Expert
from .board import BoardTeam, BoardStatus from .board import BoardTeam, BoardStatus
@ -43,7 +42,7 @@ class BoardOrchestrator:
def __init__(self, team: BoardTeam) -> None: def __init__(self, team: BoardTeam) -> None:
self._team = team 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. """Execute a board meeting discussion.
Flow: Flow:
@ -351,7 +350,7 @@ class BoardOrchestrator:
logger.warning(f"Moderator summary generation failed: {e}") logger.warning(f"Moderator summary generation failed: {e}")
return f"[第 {round} 轮讨论完成,主持人小结生成失败]" 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. """Generate moderator's final conclusion.
The moderator gives: The moderator gives:
@ -428,7 +427,7 @@ class BoardOrchestrator:
"dissent_points": [], "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. """Generate a fallback conclusion when execution fails.
Uses existing discussion history to provide a basic summary. Uses existing discussion history to provide a basic summary.
@ -491,7 +490,7 @@ class BoardOrchestrator:
return True return True
return False 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. """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. Falls back to other active experts if the primary target has no gateway.
@ -509,7 +508,7 @@ class BoardOrchestrator:
return gateway return gateway
return None 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. """Broadcast a board event to the team channel.
Events are emitted via handoff_transport for WebSocket relay. Events are emitted via handoff_transport for WebSocket relay.

View File

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from agentkit.core.config_driven import AgentConfig from agentkit.core.config_driven import AgentConfig
@ -24,12 +23,12 @@ class ExpertConfig(AgentConfig):
task_mode: str = "llm_generate", task_mode: str = "llm_generate",
supported_tasks: list[str] | None = None, supported_tasks: list[str] | None = None,
max_concurrency: int = 1, max_concurrency: int = 1,
input_schema: dict[str, Any] | None = None, input_schema: dict[str, object] | None = None,
output_schema: dict[str, Any] | None = None, output_schema: dict[str, object] | None = None,
prompt: dict[str, str] | 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, tools: list[str] | None = None,
memory: dict[str, Any] | None = None, memory: dict[str, object] | None = None,
custom_handler: str | None = None, custom_handler: str | None = None,
# Expert 专属字段 # Expert 专属字段
persona: str = "", persona: str = "",
@ -70,7 +69,7 @@ class ExpertConfig(AgentConfig):
self.decision_framework = decision_framework self.decision_framework = decision_framework
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> ExpertConfig: def from_dict(cls, data: dict[str, object]) -> ExpertConfig:
"""从字典创建配置""" """从字典创建配置"""
return cls( return cls(
name=data["name"], name=data["name"],
@ -98,7 +97,7 @@ class ExpertConfig(AgentConfig):
decision_framework=data.get("decision_framework", ""), decision_framework=data.get("decision_framework", ""),
) )
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
"""序列化为字典,包含 Expert 专属字段""" """序列化为字典,包含 Expert 专属字段"""
d = super().to_dict() d = super().to_dict()
d["persona"] = self.persona d["persona"] = self.persona
@ -125,7 +124,7 @@ class ExpertTemplate:
is_builtin: bool = False is_builtin: bool = False
description: str = "" description: str = ""
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
"""序列化为字典""" """序列化为字典"""
return { return {
"name": self.name, "name": self.name,
@ -135,7 +134,7 @@ class ExpertTemplate:
} }
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> ExpertTemplate: def from_dict(cls, data: dict[str, object]) -> ExpertTemplate:
"""从字典创建模板""" """从字典创建模板"""
config_data = data["config"] config_data = data["config"]
config = ExpertConfig.from_dict(config_data) config = ExpertConfig.from_dict(config_data)

View File

@ -14,7 +14,6 @@ import asyncio
import json import json
import logging import logging
import re import re
from typing import Any
from agentkit.core.exceptions import LLMProviderError from agentkit.core.exceptions import LLMProviderError
from agentkit.llm.gateway import LLMGateway from agentkit.llm.gateway import LLMGateway
@ -72,7 +71,7 @@ class TeamOrchestrator(
self, self,
team: ExpertTeam, team: ExpertTeam,
max_concurrent_phases: int | None = None, max_concurrent_phases: int | None = None,
checkpoint: Any = None, checkpoint: object | None = None,
workspace_root: str | None = None, workspace_root: str | None = None,
rollback_timeout: float | None = None, rollback_timeout: float | None = None,
) -> None: ) -> None:
@ -95,7 +94,7 @@ class TeamOrchestrator(
self._workspace_root = workspace_root self._workspace_root = workspace_root
self._rollback_timeout = rollback_timeout or self.DEFAULT_ROLLBACK_TIMEOUT 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 a task in pipeline mode. Lead decomposes → topological sort →
execute layers (parallel within layer) synthesize. Returns dict with execute layers (parallel within layer) synthesize. Returns dict with
status/result/phase_results/plan.""" status/result/phase_results/plan."""
@ -175,7 +174,7 @@ class TeamOrchestrator(
# 4. Set EXECUTING status, execute phases # 4. Set EXECUTING status, execute phases
self._team.set_status(TeamStatus.EXECUTING) 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) return await self._run_pipeline(lead, plan, phase_results, task)
@ -183,9 +182,9 @@ class TeamOrchestrator(
self, self,
lead: Expert, lead: Expert,
plan: TeamPlan, plan: TeamPlan,
phase_results: dict[str, dict[str, Any]], phase_results: dict[str, dict[str, object]],
task: str, task: str,
) -> dict[str, Any]: ) -> dict[str, object]:
"""Execute the pipeline loop: run pending phases, synthesize, return result. """Execute the pipeline loop: run pending phases, synthesize, return result.
Shared by execute() and resume(). phase_results may be pre-populated Shared by execute() and resume(). phase_results may be pre-populated
@ -220,7 +219,7 @@ class TeamOrchestrator(
break break
# Execute all phases in this layer in parallel (with concurrency limit) # 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: async with self._phase_semaphore:
return await self._execute_phase(ph, plan) 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}) await self._broadcast_event("team_dissolved", {"team_id": self._team.team_id})
return await self._fallback_to_single_agent(task, plan, phase_results) 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, """Resume from last checkpoint: load plan, restore completed/failed phases,
continue via _run_pipeline. Returns same dict shape as execute().""" continue via _run_pipeline. Returns same dict shape as execute()."""
if self._checkpoint is None: if self._checkpoint is None:
@ -362,7 +361,7 @@ class TeamOrchestrator(
# 3. Load checkpoints, mark completed phases # 3. Load checkpoints, mark completed phases
checkpoints = await self._checkpoint.list_checkpoints(plan_id) 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() completed_phase_ids: set[str] = set()
failed_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 # First pass: create phases with IDs, build name->id mapping
name_to_id: dict[str, str] = {} name_to_id: dict[str, str] = {}
raw_phases: list[dict[str, Any]] = [] raw_phases: list[dict[str, object]] = []
for item in items: for item in items:
if not isinstance(item, dict): if not isinstance(item, dict):
@ -584,7 +583,7 @@ class TeamOrchestrator(
return gateway return gateway
return None 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.""" """Broadcast an orchestration event to the team channel via handoff_transport."""
if self._team.handoff_transport: if self._team.handoff_transport:
try: try:

View File

@ -15,7 +15,6 @@ from __future__ import annotations
import enum import enum
import uuid import uuid
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any
class MergeStrategy(str, enum.Enum): class MergeStrategy(str, enum.Enum):
@ -82,9 +81,9 @@ class SubTask:
description: str = "" description: str = ""
assigned_expert: str = "" assigned_expert: str = ""
status: SubTaskStatus = SubTaskStatus.PENDING 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 { return {
"id": self.id, "id": self.id,
@ -95,7 +94,7 @@ class SubTask:
} }
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> SubTask: def from_dict(cls, data: dict[str, object]) -> SubTask:
"""从字典创建 SubTask""" """从字典创建 SubTask"""
return cls( return cls(
id=data.get("id", str(uuid.uuid4())), id=data.get("id", str(uuid.uuid4())),
@ -124,7 +123,7 @@ class CollaborationContract:
content_description: str = "" content_description: str = ""
status: str = "pending" status: str = "pending"
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
"""序列化为字典""" """序列化为字典"""
return { return {
"from_expert": self.from_expert, "from_expert": self.from_expert,
@ -134,7 +133,7 @@ class CollaborationContract:
} }
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> CollaborationContract: def from_dict(cls, data: dict[str, object]) -> CollaborationContract:
"""从字典创建 CollaborationContract""" """从字典创建 CollaborationContract"""
return cls( return cls(
from_expert=data.get("from_expert", ""), from_expert=data.get("from_expert", ""),
@ -176,9 +175,9 @@ class PlanPhase:
task_description: str = "" task_description: str = ""
depends_on: list[str] = field(default_factory=list) depends_on: list[str] = field(default_factory=list)
status: PhaseStatus = PhaseStatus.PENDING status: PhaseStatus = PhaseStatus.PENDING
result: dict[str, Any] | None = None result: dict[str, object] | None = None
phase_type: PhaseType = PhaseType.EXECUTION 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) collaboration_contracts: list[CollaborationContract] = field(default_factory=list)
rework_count: int = 0 rework_count: int = 0
review_feedback: str | None = None review_feedback: str | None = None
@ -188,7 +187,7 @@ class PlanPhase:
validation_command: str | None = None validation_command: str | None = None
rollback_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 # Serialize result to string to match frontend ITeamPlanPhase.result type
result_str: str | None = None result_str: str | None = None
@ -197,7 +196,7 @@ class PlanPhase:
result_str = self.result.get("content", str(self.result)) result_str = self.result.get("content", str(self.result))
else: else:
result_str = str(self.result) result_str = str(self.result)
out: dict[str, Any] = { out: dict[str, object] = {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"assigned_expert": self.assigned_expert, "assigned_expert": self.assigned_expert,
@ -219,7 +218,7 @@ class PlanPhase:
return out return out
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> PlanPhase: def from_dict(cls, data: dict[str, object]) -> PlanPhase:
"""从字典创建 PlanPhase""" """从字典创建 PlanPhase"""
contracts_data = data.get("collaboration_contracts", []) contracts_data = data.get("collaboration_contracts", [])
if not isinstance(contracts_data, list): if not isinstance(contracts_data, list):
@ -272,7 +271,7 @@ class TeamPlan:
status: PlanStatus = PlanStatus.DRAFT status: PlanStatus = PlanStatus.DRAFT
lead_expert: str = "" lead_expert: str = ""
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, object]:
"""序列化为字典""" """序列化为字典"""
return { return {
"id": self.id, "id": self.id,
@ -284,7 +283,7 @@ class TeamPlan:
} }
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any]) -> TeamPlan: def from_dict(cls, data: dict[str, object]) -> TeamPlan:
"""从字典创建 TeamPlan""" """从字典创建 TeamPlan"""
subtasks = [SubTask.from_dict(st) for st in data.get("subtasks", [])] subtasks = [SubTask.from_dict(st) for st in data.get("subtasks", [])]
phases = [PlanPhase.from_dict(ph) for ph in data.get("phases", [])] phases = [PlanPhase.from_dict(ph) for ph in data.get("phases", [])]
@ -307,7 +306,7 @@ class TeamPlan:
return None return None
def update_subtask_status( 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: ) -> None:
"""更新子任务状态和可选的结果""" """更新子任务状态和可选的结果"""
st = self.get_subtask(subtask_id) st = self.get_subtask(subtask_id)
@ -343,7 +342,7 @@ class TeamPlan:
return None return None
def update_phase_status( 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: ) -> None:
"""更新阶段状态和可选的结果""" """更新阶段状态和可选的结果"""
ph = self.get_phase(phase_id) ph = self.get_phase(phase_id)

View File

@ -17,7 +17,6 @@ import enum
import logging import logging
import time import time
import uuid import uuid
from typing import Any
from .config import ExpertConfig from .config import ExpertConfig
from .expert import Expert from .expert import Expert
@ -63,7 +62,7 @@ class ExpertTeam:
workspace: SharedWorkspace | None = None, workspace: SharedWorkspace | None = None,
pool: AgentPool | None = None, pool: AgentPool | None = None,
template_registry: ExpertTemplateRegistry | 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()) self.team_id = team_id or str(uuid.uuid4())
# U4: Accept redis_client for SharedWorkspace state offloading. # U4: Accept redis_client for SharedWorkspace state offloading.

View File

@ -6,6 +6,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
import httpx import httpx
@ -83,7 +84,7 @@ class ConfluenceAdapter(KBAdapter):
resp = await client.get("/rest/api/user/current") resp = await client.get("/rest/api/user/current")
self._authenticated = resp.status_code == 200 self._authenticated = resp.status_code == 200
return self._authenticated return self._authenticated
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"Confluence auth error: {e}") logger.error(f"Confluence auth error: {e}")
self._authenticated = False self._authenticated = False
return False return False
@ -139,7 +140,7 @@ class ConfluenceAdapter(KBAdapter):
f"Confluence search HTTP error: {e.response.status_code}{e.response.text[:200]}" f"Confluence search HTTP error: {e.response.status_code}{e.response.text[:200]}"
) )
return [] return []
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"Confluence search error: {e}") logger.error(f"Confluence search error: {e}")
return [] return []
@ -170,7 +171,7 @@ class ConfluenceAdapter(KBAdapter):
"version": page.get("version", {}).get("number", 0), "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}") logger.error(f"Confluence get_document error: {e}")
return None 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}") logger.error(f"Confluence list_sources error: {e}")
return [ return [
SourceInfo( SourceInfo(
@ -218,5 +219,7 @@ class ConfluenceAdapter(KBAdapter):
try: try:
resp = await client.get("/rest/api/space", params={"limit": 1}) resp = await client.get("/rest/api/space", params={"limit": 1})
return resp.status_code == 200 return resp.status_code == 200
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False
return False return False

View File

@ -6,6 +6,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
import time import time
from typing import TypeAlias from typing import TypeAlias
@ -101,7 +102,7 @@ class FeishuKBAdapter(KBAdapter):
else: else:
logger.error(f"Feishu auth failed: code={data.get('code')}, msg={data.get('msg')}") logger.error(f"Feishu auth failed: code={data.get('code')}, msg={data.get('msg')}")
return None return None
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"Feishu auth error: {e}") logger.error(f"Feishu auth error: {e}")
return None return None
@ -166,7 +167,7 @@ class FeishuKBAdapter(KBAdapter):
f"Feishu search HTTP error: {e.response.status_code}{e.response.text[:200]}" f"Feishu search HTTP error: {e.response.status_code}{e.response.text[:200]}"
) )
return [] return []
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"Feishu search error: {e}") logger.error(f"Feishu search error: {e}")
return [] return []
@ -199,7 +200,7 @@ class FeishuKBAdapter(KBAdapter):
"obj_type": node.get("obj_type", ""), "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}") logger.error(f"Feishu get_document error: {e}")
return None 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}") logger.error(f"Feishu list_sources error: {e}")
return [ return [
SourceInfo( SourceInfo(
@ -256,5 +257,7 @@ class FeishuKBAdapter(KBAdapter):
try: try:
token = await self._get_access_token() token = await self._get_access_token()
return token is not None return token is not None
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False
return False return False

View File

@ -6,6 +6,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
import httpx import httpx
@ -138,7 +139,7 @@ class GenericHTTPAdapter(KBAdapter):
f"GenericHTTP search HTTP error: {e.response.status_code}{e.response.text[:200]}" f"GenericHTTP search HTTP error: {e.response.status_code}{e.response.text[:200]}"
) )
return [] return []
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"GenericHTTP search error: {e}") logger.error(f"GenericHTTP search error: {e}")
return [] return []
@ -179,7 +180,7 @@ class GenericHTTPAdapter(KBAdapter):
f"GenericHTTP ingest HTTP error: {e.response.status_code}{e.response.text[:200]}" f"GenericHTTP ingest HTTP error: {e.response.status_code}{e.response.text[:200]}"
) )
return [] return []
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"GenericHTTP ingest error: {e}") logger.error(f"GenericHTTP ingest error: {e}")
return [] return []
@ -194,7 +195,7 @@ class GenericHTTPAdapter(KBAdapter):
if resp.status_code in (200, 204): if resp.status_code in (200, 204):
return True return True
return False return False
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"GenericHTTP delete_by_id error: {e}") logger.error(f"GenericHTTP delete_by_id error: {e}")
return False return False
@ -216,7 +217,7 @@ class GenericHTTPAdapter(KBAdapter):
source_id=data.get("source_id", self._source_id), source_id=data.get("source_id", self._source_id),
metadata=data.get("metadata", {}), metadata=data.get("metadata", {}),
) )
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"GenericHTTP get_document error: {e}") logger.error(f"GenericHTTP get_document error: {e}")
return None 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}") logger.debug(f"GenericHTTP list_sources error (endpoint may not exist): {e}")
return [ return [
@ -272,14 +275,18 @@ class GenericHTTPAdapter(KBAdapter):
resp = await client.get("/health") resp = await client.get("/health")
if resp.status_code == 200: if resp.status_code == 200:
return True return True
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — health_check 设计意图:任何异常继续尝试 fallback
pass pass
# Fallback: try the base endpoint # Fallback: try the base endpoint
try: try:
resp = await client.get("/") resp = await client.get("/")
return resp.status_code in (200, 401, 403) return resp.status_code in (200, 401, 403)
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — health_check 设计意图:任何异常都返回 False
return False return False
async def authenticate(self) -> bool: async def authenticate(self) -> bool:
@ -289,6 +296,8 @@ class GenericHTTPAdapter(KBAdapter):
""" """
try: try:
self._authenticated = await self.health_check() self._authenticated = await self.health_check()
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — authenticate 设计意图:任何异常都返回 False
self._authenticated = False self._authenticated = False
return self._authenticated return self._authenticated

View File

@ -6,6 +6,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import hashlib import hashlib
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
@ -199,7 +200,7 @@ class ContextualChunker:
) )
context = response.content.strip() context = response.content.strip()
return context return context
except Exception as e: except (ConnectionError, RuntimeError, asyncio.TimeoutError, ValueError) as e:
logger.warning(f"Context generation failed for chunk: {e}") logger.warning(f"Context generation failed for chunk: {e}")
return "" return ""

View File

@ -9,6 +9,8 @@ from __future__ import annotations
import io import io
import logging import logging
import uuid import uuid
import zipfile
import asyncio
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@ -194,7 +196,9 @@ class DocumentLoader:
return text, meta return text, meta
except ImportError: except ImportError:
pass pass
except Exception as e: except asyncio.CancelledError:
raise
except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本
logger.warning(f"PyMuPDF parsing failed for {filename}: {e}") logger.warning(f"PyMuPDF parsing failed for {filename}: {e}")
# 尝试 pdfplumber # 尝试 pdfplumber
@ -217,7 +221,9 @@ class DocumentLoader:
return text, meta return text, meta
except ImportError: except ImportError:
pass pass
except Exception as e: except asyncio.CancelledError:
raise
except Exception as e: # noqa: BLE001 — 解析器设计意图:任何失败回退到文本
logger.warning(f"pdfplumber parsing failed for {filename}: {e}") logger.warning(f"pdfplumber parsing failed for {filename}: {e}")
# 回退到纯文本 # 回退到纯文本
@ -264,7 +270,9 @@ class DocumentLoader:
except ImportError: except ImportError:
logger.warning(f"python-docx not available for {filename}, falling back to text") logger.warning(f"python-docx not available for {filename}, falling back to text")
return self._parse_text(content, filename) 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}") logger.warning(f"python-docx parsing failed for {filename}: {e}")
return self._parse_text(content, filename) return self._parse_text(content, filename)
@ -333,7 +341,9 @@ class DocumentLoader:
except ImportError: except ImportError:
logger.warning(f"openpyxl not available for {filename}, falling back to text") logger.warning(f"openpyxl not available for {filename}, falling back to text")
return self._parse_text(content, filename) 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}") logger.warning(f"openpyxl parsing failed for {filename}: {e}")
return self._parse_text(content, filename) return self._parse_text(content, filename)
@ -407,7 +417,9 @@ class DocumentLoader:
except ImportError: except ImportError:
logger.warning(f"BeautifulSoup not available for {filename}, falling back to text") logger.warning(f"BeautifulSoup not available for {filename}, falling back to text")
return self._parse_text(content, filename) 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}") logger.warning(f"BeautifulSoup parsing failed for {filename}: {e}")
return self._parse_text(content, filename) return self._parse_text(content, filename)

View File

@ -8,10 +8,8 @@ import os
import time import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import OrderedDict from collections import OrderedDict
from typing import TYPE_CHECKING
if TYPE_CHECKING: import httpx
import httpx
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -108,8 +106,6 @@ class OpenAIEmbedder(Embedder):
def _get_client(self) -> httpx.AsyncClient: def _get_client(self) -> httpx.AsyncClient:
"""Lazily create and reuse a single httpx.AsyncClient.""" """Lazily create and reuse a single httpx.AsyncClient."""
if self._client is None: if self._client is None:
import httpx
self._client = httpx.AsyncClient(timeout=30.0) self._client = httpx.AsyncClient(timeout=30.0)
return self._client return self._client
@ -153,7 +149,7 @@ class OpenAIEmbedder(Embedder):
self._cache.put(text, embedding) self._cache.put(text, embedding)
return embedding return embedding
except Exception as e: except (httpx.HTTPError, ValueError, KeyError, TypeError) as e:
logger.error(f"OpenAI embedding failed: {e}") logger.error(f"OpenAI embedding failed: {e}")
raise raise

View File

@ -9,6 +9,7 @@ from datetime import datetime, timezone
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.exc import DBAPIError
from agentkit.memory.base import Memory, MemoryItem, MetadataDict from agentkit.memory.base import Memory, MemoryItem, MetadataDict
from agentkit.memory.embedder import Embedder from agentkit.memory.embedder import Embedder
@ -94,7 +95,7 @@ class EpisodicMemory(Memory):
) )
db.add(entry) db.add(entry)
await db.commit() await db.commit()
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to store episodic memory: {e}") logger.error(f"Failed to store episodic memory: {e}")
raise raise
@ -111,7 +112,7 @@ class EpisodicMemory(Memory):
if self._pgvector_enabled: if self._pgvector_enabled:
return await self._retrieve_pgvector(db, query_embedding) return await self._retrieve_pgvector(db, query_embedding)
return await self._retrieve_client_side(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}") logger.error(f"Failed to retrieve episodic memory: {e}")
return None return None
@ -223,7 +224,7 @@ class EpisodicMemory(Memory):
if self._pgvector_enabled and self._embedder: if self._pgvector_enabled and self._embedder:
return await self._search_pgvector(db, query, top_k, filters, search_multiplier) 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) 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}") logger.error(f"Failed to search episodic memory: {e}")
return [] return []
@ -405,7 +406,7 @@ class EpisodicMemory(Memory):
await db.execute(stmt) await db.execute(stmt)
await db.commit() await db.commit()
return True return True
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to delete episodic memory: {e}") logger.error(f"Failed to delete episodic memory: {e}")
return False return False

View File

@ -147,7 +147,7 @@ class HttpRAGService:
except httpx.RequestError as e: except httpx.RequestError as e:
logger.error(f"RAG search request error: {e}") logger.error(f"RAG search request error: {e}")
return [] return []
except Exception as e: except (ValueError, KeyError, TypeError, RuntimeError) as e:
logger.error(f"RAG search unexpected error: {e}") logger.error(f"RAG search unexpected error: {e}")
return [] return []
@ -237,7 +237,7 @@ class HttpRAGService:
except httpx.RequestError as e: except httpx.RequestError as e:
logger.error(f"RAG enhanced_search request error for KB {kb_id}: {e}") logger.error(f"RAG enhanced_search request error for KB {kb_id}: {e}")
raise 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}") logger.error(f"RAG enhanced_search unexpected error for KB {kb_id}: {e}")
raise raise
@ -302,7 +302,7 @@ class HttpRAGService:
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
logger.error(f"RAG ingest HTTP error: {e.response.status_code}") logger.error(f"RAG ingest HTTP error: {e.response.status_code}")
return None return None
except Exception as e: except (ValueError, KeyError, TypeError, RuntimeError) as e:
logger.error(f"RAG ingest error: {e}") logger.error(f"RAG ingest error: {e}")
return None return None
@ -312,7 +312,7 @@ class HttpRAGService:
try: try:
resp = await client.get("/bases") resp = await client.get("/bases")
return resp.status_code in (200, 401) # 401 = 服务在但需认证 return resp.status_code in (200, 401) # 401 = 服务在但需认证
except Exception: except (httpx.HTTPError, ValueError, KeyError, TypeError):
return False return False
async def close(self) -> None: async def close(self) -> None:

View File

@ -14,6 +14,8 @@ import re
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, TypeAlias from typing import TYPE_CHECKING, TypeAlias
from sqlalchemy.exc import DBAPIError
from agentkit.memory.chunking import Chunk, StructuralChunker, TextChunker from agentkit.memory.chunking import Chunk, StructuralChunker, TextChunker
from agentkit.memory.document_loader import Document as LoaderDocument from agentkit.memory.document_loader import Document as LoaderDocument
from agentkit.memory.embedder import Embedder from agentkit.memory.embedder import Embedder
@ -108,7 +110,7 @@ class LocalRAGService:
await self._store_chunks(doc, chunks) await self._store_chunks(doc, chunks)
ingested_ids.append(doc.doc_id) ingested_ids.append(doc.doc_id)
logger.info(f"Ingested document '{doc.title}' with {len(chunks)} chunks") 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}") logger.error(f"Failed to ingest document '{doc.title}': {e}")
return ingested_ids return ingested_ids
@ -130,7 +132,7 @@ class LocalRAGService:
if self._pgvector_enabled: if self._pgvector_enabled:
return await self._query_pgvector(db, query_embedding, top_k) return await self._query_pgvector(db, query_embedding, top_k)
return await self._query_client_side(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}") logger.error(f"Failed to query knowledge base: {e}")
return [] return []
@ -151,7 +153,7 @@ class LocalRAGService:
await db.execute(sql, {"doc_id": id}) await db.execute(sql, {"doc_id": id})
await db.commit() await db.commit()
return True return True
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to delete document {id}: {e}") logger.error(f"Failed to delete document {id}: {e}")
return False return False
@ -190,7 +192,7 @@ class LocalRAGService:
) )
) )
return sources return sources
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
logger.error(f"Failed to list sources: {e}") logger.error(f"Failed to list sources: {e}")
return [] return []
@ -202,7 +204,7 @@ class LocalRAGService:
await db.execute(sql_text(f"SELECT 1 FROM {self._table_name} LIMIT 1")) await db.execute(sql_text(f"SELECT 1 FROM {self._table_name} LIMIT 1"))
return True return True
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
logger.error(f"Health check failed: {e}") logger.error(f"Health check failed: {e}")
return False return False
@ -268,7 +270,7 @@ class LocalRAGService:
await db.execute(sql, params_list) await db.execute(sql, params_list)
await db.commit() await db.commit()
except Exception as e: except (DBAPIError, ValueError, KeyError, RuntimeError) as e:
await db.rollback() await db.rollback()
logger.error(f"Failed to store chunks for document '{doc.title}': {e}") logger.error(f"Failed to store chunks for document '{doc.title}': {e}")
raise raise
@ -455,7 +457,7 @@ class InMemoryLocalRAGService:
} }
ingested_ids.append(doc.doc_id) ingested_ids.append(doc.doc_id)
logger.info(f"Ingested document '{doc.title}' with {len(chunks)} chunks") 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}") logger.error(f"Failed to ingest document '{doc.title}': {e}")
return ingested_ids return ingested_ids

View File

@ -378,7 +378,7 @@ class MemoryStore:
try: try:
new_prompt = self.refresh_system_prompt() new_prompt = self.refresh_system_prompt()
self._on_change(new_prompt) self._on_change(new_prompt)
except Exception: except (OSError, ValueError, RuntimeError, TypeError):
import logging import logging
logging.getLogger(__name__).warning("Memory notify_change failed", exc_info=True) logging.getLogger(__name__).warning("Memory notify_change failed", exc_info=True)

View File

@ -392,5 +392,5 @@ class RetrieveKnowledgeTool(Tool):
} }
) )
return {"query": query, "results": results, "call_count": self._call_count} 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": []} return {"error": str(e), "results": []}

View File

@ -13,6 +13,12 @@ import logging
import time import time
from typing import Any, Protocol, runtime_checkable 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__) logger = logging.getLogger(__name__)
@ -157,7 +163,7 @@ class RedisCascadeStateStore:
pipe.expire(key, self._session_ttl) pipe.expire(key, self._session_ttl)
results = pipe.execute() results = pipe.execute()
return results[0] 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}") logger.warning(f"Redis cascade increment failed: {e}")
self._degrade_to_fallback() self._degrade_to_fallback()
if self._fallback is not None: if self._fallback is not None:
@ -171,7 +177,7 @@ class RedisCascadeStateStore:
r = self._get_sync_redis() r = self._get_sync_redis()
val = r.get(f"{self.INTER_PREFIX}{session_id}") val = r.get(f"{self.INTER_PREFIX}{session_id}")
return int(val) if val is not None else 0 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}") logger.warning(f"Redis cascade get failed: {e}")
if self._fallback is not None: if self._fallback is not None:
return self._fallback.get_interaction(session_id) return self._fallback.get_interaction(session_id)
@ -188,7 +194,7 @@ class RedisCascadeStateStore:
pipe.set(key, depth) pipe.set(key, depth)
pipe.expire(key, self._session_ttl) pipe.expire(key, self._session_ttl)
pipe.execute() 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}") logger.warning(f"Redis cascade set_depth failed: {e}")
self._degrade_to_fallback() self._degrade_to_fallback()
if self._fallback is not None: if self._fallback is not None:
@ -201,7 +207,7 @@ class RedisCascadeStateStore:
r = self._get_sync_redis() r = self._get_sync_redis()
val = r.get(f"{self.DEPTH_PREFIX}{session_id}") val = r.get(f"{self.DEPTH_PREFIX}{session_id}")
return int(val) if val is not None else 0 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}") logger.warning(f"Redis cascade get_depth failed: {e}")
if self._fallback is not None: if self._fallback is not None:
return self._fallback.get_depth(session_id) 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.INTER_PREFIX}{session_id}")
pipe.delete(f"{self.DEPTH_PREFIX}{session_id}") pipe.delete(f"{self.DEPTH_PREFIX}{session_id}")
pipe.execute() pipe.execute()
except Exception as e: except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError, TypeError) as e:
logger.warning(f"Redis cascade reset failed: {e}") logger.warning(f"Redis cascade reset failed: {e}")
self._degrade_to_fallback() self._degrade_to_fallback()
if self._fallback is not None: if self._fallback is not None:

View File

@ -81,7 +81,7 @@ def _build_llm_gateway(config: ServerConfig) -> LLMGateway:
backend=config.usage_store.get("backend", "memory"), backend=config.usage_store.get("backend", "memory"),
redis_url=config.usage_store.get("redis_url", "redis://localhost:6379"), 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") logger.warning(f"Failed to initialize usage store: {e}, using in-memory")
gateway = LLMGateway(config=config.llm_config, usage_store=usage_store) gateway = LLMGateway(config=config.llm_config, usage_store=usage_store)
@ -93,7 +93,7 @@ def _build_llm_gateway(config: ServerConfig) -> LLMGateway:
try: try:
provider = _create_provider(name, pconf) provider = _create_provider(name, pconf)
gateway.register_provider(name, provider) 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}") logger.warning(f"Failed to register LLM provider '{name}': {e}")
return gateway return gateway
@ -222,7 +222,7 @@ async def lifespan(app: FastAPI):
if agent is not None: if agent is not None:
agent._system_prompt = new_prompt agent._system_prompt = new_prompt
updated += 1 updated += 1
except Exception: except (AttributeError, TypeError, ValueError):
logger.warning( logger.warning(
f"Failed to update system prompt for agent '{agent_name}'", exc_info=True 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)) agent._tool_registry.register(DocumentTool(service=doc_service))
app.state.document_service = doc_service app.state.document_service = doc_service
logger.info("DocumentTool registered with word/excel/pdf renderers") 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") logger.exception("Failed to register DocumentTool")
# Override system prompt with memory-injected version + available tools # 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") 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}") logger.warning(f"GUI mode: failed to create default agent: {e}")
# Load skills from config and register into SkillRegistry # 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(): for tool in agent._tool_registry.list_tools():
try: try:
tool_registry.register(tool) tool_registry.register(tool)
except Exception: except (ValueError, KeyError, RuntimeError):
pass # Already registered pass # Already registered
# Load skills from configured paths # Load skills from configured paths
@ -331,11 +335,13 @@ async def lifespan(app: FastAPI):
try: try:
loader.load_from_file(str(p)) loader.load_from_file(str(p))
logger.info(f"GUI mode: loaded skill from {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.warning(f"GUI mode: failed to load skill from {p}: {se}")
logger.info(f"GUI mode: {len(skill_registry.list_skills())} skills registered") 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}") logger.warning(f"GUI mode: failed to load skills: {e}")
elif os.environ.get("AGENTKIT_GUI_MODE"): elif os.environ.get("AGENTKIT_GUI_MODE"):
# Agent already exists (e.g. from config), still ensure memory store is available # 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: if agent is not None:
agent._system_prompt = new_prompt agent._system_prompt = new_prompt
updated += 1 updated += 1
except Exception: except (AttributeError, TypeError, ValueError):
logger.warning( logger.warning(
f"Failed to update system prompt for agent '{agent_name}'", f"Failed to update system prompt for agent '{agent_name}'",
exc_info=True, exc_info=True,
@ -416,7 +422,9 @@ async def lifespan(app: FastAPI):
app.state.expert_template_registry = expert_registry app.state.expert_template_registry = expert_registry
if total_loaded: if total_loaded:
logger.info(f"Total {total_loaded} ExpertTemplates registered for @board mode") 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}") logger.warning(f"Failed to load ExpertTemplates: {e}")
# Ensure app.state.expert_template_registry always exists (empty registry) # Ensure app.state.expert_template_registry always exists (empty registry)
from agentkit.experts.registry import ExpertTemplateRegistry from agentkit.experts.registry import ExpertTemplateRegistry
@ -468,7 +476,7 @@ async def lifespan(app: FastAPI):
_row = await _cur.fetchone() _row = await _cur.fetchone()
if _row is not None: if _row is not None:
default_cal_user_id = str(_row["id"]) 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) logger.debug("Could not resolve default user_id for CalendarTool", exc_info=True)
calendar_tool = CalendarTool( calendar_tool = CalendarTool(
@ -492,7 +500,7 @@ async def lifespan(app: FastAPI):
if default_agent is not None: if default_agent is not None:
try: try:
default_agent._tool_registry.register(calendar_tool) default_agent._tool_registry.register(calendar_tool)
except Exception: except (ValueError, KeyError, RuntimeError, AttributeError):
# ponytail: log at debug — CalendarTool double-registration # ponytail: log at debug — CalendarTool double-registration
# is expected on reload, but silent pass hides real errors. # is expected on reload, but silent pass hides real errors.
logger.debug("CalendarTool already registered or registration failed", exc_info=True) 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)", "CalendarTool attached to default agent (tools=%d)",
len(default_agent._tool_registry.list_tools()), 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") 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.exception("Failed to register CalendarTool")
logger.info("Calendar subsystem initialized (service + reminder scheduler)") 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") logger.exception("Failed to initialize calendar subsystem — calendar API unavailable")
# Bitable subsystem: init DB, service, internal token (KTD11). # 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_service = BitableService(db=bitable_db)
app.state.bitable_internal_token = os.environ.get("AGENTKIT_BITABLE_INTERNAL_TOKEN") app.state.bitable_internal_token = os.environ.get("AGENTKIT_BITABLE_INTERNAL_TOKEN")
logger.info("Bitable subsystem initialized") 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") logger.exception("Failed to initialize bitable subsystem")
# RAG platform subsystem (P1): wire KBStore, RetrievalEngine, HitProcessor, # 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 from agentkit.rag_platform.store import ensure_tables
await ensure_tables(rag_database_url) await ensure_tables(rag_database_url)
except Exception: except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError):
logger.exception("Failed to ensure rag_platform tables") logger.exception("Failed to ensure rag_platform tables")
# KBStore — KB/Document persistence # KBStore — KB/Document persistence
@ -561,7 +577,9 @@ async def lifespan(app: FastAPI):
app.state.kb_store = KBStore(session_factory=kb_session_factory) app.state.kb_store = KBStore(session_factory=kb_session_factory)
logger.info("KBStore initialized") 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") logger.exception("Failed to initialize KBStore")
# RetrievalEngine — requires llama_index PGVectorStore # RetrievalEngine — requires llama_index PGVectorStore
@ -576,9 +594,13 @@ async def lifespan(app: FastAPI):
session_factory=kb_session_factory, session_factory=kb_session_factory,
) )
logger.info("RetrievalEngine initialized (pgvector ready)") 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") 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") logger.exception("Failed to initialize rag_platform DB components")
# HitProcessor — LLM-based answer generation from retrieval results # 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) app.state.hit_processor = HitProcessor(llm_gateway=app.state.llm_gateway)
logger.info("HitProcessor initialized") logger.info("HitProcessor initialized")
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — HitProcessor init includes import
logger.exception("Failed to initialize HitProcessor") logger.exception("Failed to initialize HitProcessor")
# TaskManager — async vectorize/batch-index tasks # TaskManager — async vectorize/batch-index tasks
@ -598,7 +622,9 @@ async def lifespan(app: FastAPI):
app.state.task_manager = TaskManager() app.state.task_manager = TaskManager()
logger.info("TaskManager initialized (degraded mode: InMemoryTaskStore)") 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") logger.exception("Failed to initialize TaskManager")
# KBSettingsStore — per-KB retrieval/hit-processing defaults (process singleton) # 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() app.state.kb_settings_store = get_settings_store()
logger.info("KBSettingsStore initialized") logger.info("KBSettingsStore initialized")
except Exception: except asyncio.CancelledError:
raise
except Exception: # noqa: BLE001 — KBSettingsStore init includes import
logger.exception("Failed to initialize KBSettingsStore") logger.exception("Failed to initialize KBSettingsStore")
yield yield
@ -618,7 +646,7 @@ async def lifespan(app: FastAPI):
from agentkit.bitable.db import close_bitable_db from agentkit.bitable.db import close_bitable_db
await close_bitable_db() await close_bitable_db()
except Exception: except (RuntimeError, asyncio.TimeoutError, ConnectionError, OSError):
pass pass
# Stop MCP servers # Stop MCP servers
if mcp_manager is not None: if mcp_manager is not None:
@ -660,7 +688,7 @@ async def lifespan(app: FastAPI):
from agentkit.server.routes.channels import close_all_adapters from agentkit.server.routes.channels import close_all_adapters
await close_all_adapters() await close_all_adapters()
except Exception: except (RuntimeError, asyncio.TimeoutError, ConnectionError, OSError):
logger.debug("close_all_adapters 异常已忽略") 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: if hasattr(app.state, "agent_pool") and app.state.agent_pool is not None:
app.state.agent_pool._llm_gateway = new_gateway app.state.agent_pool._llm_gateway = new_gateway
logger.info(f"LLM Gateway reloaded (config v{current_version})") 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}") logger.error(f"Failed to reload LLM Gateway: {e}")
# Reload skills if skill paths changed # 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"): elif p.is_file() and p.suffix in (".yaml", ".yml"):
try: try:
loader.load_from_file(str(p)) loader.load_from_file(str(p))
except Exception: except (ValueError, TypeError, KeyError, OSError, RuntimeError):
pass pass
app.state.skill_registry = new_skill_registry app.state.skill_registry = new_skill_registry
if hasattr(app.state, "agent_pool") and app.state.agent_pool is not None: if hasattr(app.state, "agent_pool") and app.state.agent_pool is not None:
app.state.agent_pool._skill_registry = new_skill_registry app.state.agent_pool._skill_registry = new_skill_registry
logger.info(f"Skills reloaded (config v{current_version})") 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}") logger.error(f"Failed to reload skills: {e}")
# Update config version on all agents # Update config version on all agents
@ -856,7 +886,7 @@ def create_app(
from agentkit.server.middleware import _load_client_keys from agentkit.server.middleware import _load_client_keys
client_keys = _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}") logger.warning(f"Failed to load client keys for AuthMiddleware: {e}")
app.add_middleware( app.add_middleware(
@ -983,7 +1013,7 @@ def create_app(
try: try:
ts_config = {**ts_config, **_json.loads(ts_env)} ts_config = {**ts_config, **_json.loads(ts_env)}
except Exception: except (ValueError, TypeError):
pass pass
task_store = create_task_store( task_store = create_task_store(
backend=ts_config.get("backend", "memory"), backend=ts_config.get("backend", "memory"),
@ -1039,7 +1069,9 @@ def create_app(
db_path=evo_conf.get("db_path", "~/.agentkit/evolution.db"), db_path=evo_conf.get("db_path", "~/.agentkit/evolution.db"),
database_url=evo_conf.get("database_url"), 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}") logger.warning(f"Failed to initialize evolution store: {e}")
app.state.evolution_store = None app.state.evolution_store = None
else: else:
@ -1056,7 +1088,9 @@ def create_app(
redis_url=cs_conf.get("redis_url", "redis://localhost:6379"), redis_url=cs_conf.get("redis_url", "redis://localhost:6379"),
session_ttl=cs_conf.get("session_ttl", 86400), 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}") logger.warning(f"Failed to initialize cascade state store: {e}")
app.state.cascade_state_store = None app.state.cascade_state_store = None
else: else:
@ -1138,7 +1172,7 @@ def create_app(
try: try:
epi_session_factory = create_episodic_session_factory(database_url) epi_session_factory = create_episodic_session_factory(database_url)
epi_model = EpisodeModel epi_model = EpisodeModel
except Exception as db_err: except (ConnectionError, OSError, asyncio.TimeoutError, ValueError, KeyError, RuntimeError, ImportError) as db_err:
import logging as _log import logging as _log
_log.getLogger(__name__).warning( _log.getLogger(__name__).warning(
@ -1155,7 +1189,9 @@ def create_app(
pgvector_enabled=epi_conf.get("pgvector_enabled", True), pgvector_enabled=epi_conf.get("pgvector_enabled", True),
table_name=epi_conf.get("table_name", "episodic_memories"), 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 import logging
logging.getLogger(__name__).warning( logging.getLogger(__name__).warning(
@ -1174,7 +1210,9 @@ def create_app(
retrieve_tool = memory_retriever.create_retrieve_tool() retrieve_tool = memory_retriever.create_retrieve_tool()
if retrieve_tool: if retrieve_tool:
app.state.retrieve_knowledge_tool = 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 import logging
logging.getLogger(__name__).warning(f"Failed to initialize memory components: {e}") logging.getLogger(__name__).warning(f"Failed to initialize memory components: {e}")

View File

@ -7,6 +7,12 @@ import logging
import os import os
from typing import Any, Protocol, runtime_checkable 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 from agentkit.session.models import Message, Session, SessionStatus
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -207,7 +213,7 @@ class RedisSessionStore:
try: try:
redis = await self._get_redis() redis = await self._get_redis()
return await redis.ping() return await redis.ping()
except Exception: except (ImportError, OSError, _RedisError, ValueError, KeyError, RuntimeError):
return False return False
@ -283,7 +289,7 @@ class FileSessionStore:
session = Session.from_dict(data["session"]) session = Session.from_dict(data["session"])
if agent_name is None or session.agent_name == agent_name: if agent_name is None or session.agent_name == agent_name:
sessions.append(session) sessions.append(session)
except Exception: except (ValueError, KeyError, TypeError, OSError):
continue continue
sessions.sort(key=lambda s: s.updated_at, reverse=True) sessions.sort(key=lambda s: s.updated_at, reverse=True)
return sessions[:limit] return sessions[:limit]
@ -343,7 +349,7 @@ def create_session_store(
store = RedisSessionStore(redis_url=redis_url, ttl_seconds=ttl_seconds) store = RedisSessionStore(redis_url=redis_url, ttl_seconds=ttl_seconds)
logger.info(f"SessionStore backend: redis") logger.info(f"SessionStore backend: redis")
return store 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") logger.warning(f"Failed to initialise RedisSessionStore ({exc}), falling back to InMemorySessionStore")
store = InMemorySessionStore() store = InMemorySessionStore()

View File

@ -56,7 +56,7 @@ class SkillLoader:
try: try:
skill = self._load_skill_from_file(yaml_path) skill = self._load_skill_from_file(yaml_path)
skills.append(skill) 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}") logger.warning(f"Skipping invalid YAML file '{yaml_path}': {e}")
# 加载 SKILL.md 文件 # 加载 SKILL.md 文件
@ -66,7 +66,7 @@ class SkillLoader:
try: try:
skill = self.load_from_skill_md(md_path) skill = self.load_from_skill_md(md_path)
skills.append(skill) 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}") logger.warning(f"Skipping invalid SKILL.md file '{md_path}': {e}")
return skills return skills
@ -144,7 +144,7 @@ class SkillLoader:
from importlib.metadata import entry_points as _entry_points from importlib.metadata import entry_points as _entry_points
eps = _entry_points().get(group_name, []) 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}") logger.warning(f"Failed to discover entry_points for group '{group_name}': {e}")
return skills return skills
@ -192,7 +192,7 @@ class SkillLoader:
logger.info( logger.info(
f"Loaded skill '{skill.name}' v{skill.version} from entry_point '{ep.name}'" 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}") logger.warning(f"Failed to load skill from entry_point '{ep.name}': {e}")
return skills return skills
@ -208,6 +208,6 @@ class SkillLoader:
tool = self._tool_registry.get(tool_name) tool = self._tool_registry.get(tool_name)
tools.append(tool) tools.append(tool)
logger.info(f"Bound tool '{tool_name}' to skill '{config.name}'") 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}") logger.warning(f"Failed to bind tool '{tool_name}' to skill '{config.name}': {e}")
return tools return tools