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