""" Agent Runtime 配置与数据结构 Schema """ from __future__ import annotations import logging logger = logging.getLogger(__name__) logger.warning("SCHEMAS_MODULE_LOADED_V3_FIELD_VALIDATOR") from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field, field_validator from app.agent_runtime.permissions import DEFAULT_REQUIRE_APPROVAL_TOOLS class AgentToolConfig(BaseModel): """Agent 可用工具配置""" # 若为空列表则使用全部已注册工具 include_tools: List[str] = Field(default_factory=list, description="允许的工具名称白名单") exclude_tools: List[str] = Field(default_factory=list, description="排除的工具名称黑名单") require_approval: List[str] = Field(default_factory=lambda: list(DEFAULT_REQUIRE_APPROVAL_TOOLS), description="需要人工审批的工具名列表(默认包含 destructive 工具)") @field_validator("include_tools", "exclude_tools", "require_approval", "cache_tool_whitelist", "auto_approve_rules", "deny_tools", mode="before") @classmethod def coerce_none_to_empty(cls, v: Any) -> Any: return v if v is not None else [] approval_timeout_ms: int = Field(default=60000, description="审批超时(毫秒),超时使用默认策略") approval_default: str = Field(default="deny", description="超时默认策略: approve | deny | skip") # 结果缓存 cache_enabled: bool = Field(default=True, description="是否启用工具结果缓存(确定性工具默认开启)") cache_tool_whitelist: List[str] = Field(default_factory=list, description="启用缓存的工具名(空=确定性工具默认)") cache_ttl_ms: int = Field(default=3600000, description="缓存 TTL(毫秒),默认 1 小时") # 工具安全分级 (P3 — 参考 Claude Code PermissionMode) permission_level: str = Field( default="default", description="权限级别: bypass | acceptEdits | default | plan" ) auto_approve_rules: List[Dict[str, Any]] = Field( default_factory=list, description="自动批准规则: [{tool_pattern, param_conditions, description}]" ) deny_tools: List[str] = Field(default_factory=list, description="禁用的工具名列表") class AgentMemoryConfig(BaseModel): """Agent 记忆配置""" enabled: bool = True max_history_messages: int = 20 # 注入 LLM 的上文最大消息数 session_key: Optional[str] = None # 会话标识,默认自动生成 persist_to_db: bool = True # 是否写入 MySQL 长期记忆 vector_memory_enabled: bool = True # 是否启用向量记忆(语义检索) vector_memory_top_k: int = 5 # 向量检索 Top-K vector_memory_rerank: bool = False # 是否启用 LLM Rerank(向量 top-20 → LLM 精选 top-K) memory_type_filter: Optional[List[str]] = None # 记忆类型过滤,如 ["user","project"],None=全部 team_id: Optional[str] = None # 团队共享 ID,非空时记忆在团队间共享 team_share_enabled: bool = False # 是否将新记忆自动发布到团队池 learning_enabled: bool = True # 是否启用自主学习(工具模式学习) # 文件式记忆 (MEMORY.md — 参考 Claude Code memdir) memory_dir_enabled: bool = False # 是否启用文件式自动记忆 memory_dir_path: str = "" # 记忆目录路径(空=自动使用项目 .claude/memory) parent_agent_id: Optional[str] = None # 父 Agent ID(继承经验) # 对话自动压缩 (参考 Claude Code src/services/compact/) compaction: Optional[Any] = None # CompactionConfig — 惰性导入避免循环依赖 class AgentLLMConfig(BaseModel): """Agent 模型配置""" provider: str = "openai" # openai / deepseek model: str = "gpt-4o-mini" temperature: float = 0.7 max_tokens: Optional[int] = None api_key: Optional[str] = None base_url: Optional[str] = None max_iterations: int = 10 # ReAct 循环最大步数 request_timeout: float = 120.0 extra_body: Optional[Dict[str, Any]] = None self_review_threshold: float = 0.6 # self-review 通过阈值(0-1) cache_enabled: bool = False # LLM 响应缓存(默认关闭,语义缓存有风险) cache_ttl_ms: int = 300000 # LLM 缓存 TTL,默认 5 分钟 fallback_llm: Optional[Dict[str, Any]] = None # 降级模型配置 {provider, model, api_key, base_url} # 计划模式 (P2 — 参考 Claude Code EnterPlanModeTool) plan_mode_enabled: bool = False # 是否启用计划模式 plan_approval_required: bool = True # 是否需要用户审批计划 plan_model: Optional[str] = None # 计划生成使用的模型(默认复用主模型) # 上下文窗口 (用于 Compaction 触发计算) context_window: int = 128000 # 模型上下文窗口大小(token 数) class AgentBudgetConfig(BaseModel): """Agent 执行预算配置""" max_llm_invocations: int = 200 # LLM 调用次数上限 max_tool_calls: int = 500 # 工具调用次数上限 class AgentTokenBudgetConfig(BaseModel): """Token 预算管理配置 — 参考 Claude Code tokenBudget.ts""" enabled: bool = True context_window: int = 0 # 模型上下文窗口(0=自动检测) output_reserve: int = 8192 # 留给模型输出的空间 warning_threshold_pct: float = Field(default=0.75, ge=0.1, le=1.0) compact_threshold_pct: float = Field(default=0.85, ge=0.1, le=1.0) hard_limit_pct: float = Field(default=0.95, ge=0.1, le=1.0) user_budget: Optional[int] = None # 用户累计 token 目标(如 500000) auto_continue: bool = False # 预算用尽自动继续 compaction_after_warning: bool = True max_compaction_attempts: int = 3 class AgentPromptSectionsConfig(BaseModel): """系统提示词分层装配配置 — 参考 Claude Code systemPromptSections.ts""" # 是否启用分层装配(关闭则退回到简单的 system_prompt 字符串) enabled: bool = True # 静态段开关(段名 → 是否启用) static_sections: Dict[str, bool] = Field(default_factory=lambda: { "persona": True, "capabilities": True, "tool_instructions": True, "safety_rules": True, "output_style": True, }) # 动态段开关 dynamic_sections: Dict[str, bool] = Field(default_factory=lambda: { "environment": True, "language": True, "memory_context": True, "conversation_summary": True, "tool_list": False, # 工具列表默认关闭(太长) }) # 语言偏好(用于 language 段) language: Optional[str] = None class AgentConfig(BaseModel): """Agent 完整配置""" name: str = "default_agent" system_prompt: str = "你是一个有用的AI助手。请使用可用工具来帮助用户完成任务。" llm: AgentLLMConfig = Field(default_factory=AgentLLMConfig) tools: AgentToolConfig = Field(default_factory=AgentToolConfig) memory: AgentMemoryConfig = Field(default_factory=AgentMemoryConfig) budget: AgentBudgetConfig = Field(default_factory=AgentBudgetConfig) user_id: Optional[str] = None # 持久记忆 / 向量记忆的 scope_id;不设时沿用 user_id 或 name(易与其他 Agent 串记忆) memory_scope_id: Optional[str] = None # 是否开启输出质量自检(结束前用轻量 LLM 评审,不达标则追加修正) self_review_enabled: bool = False # 系统提示词分层装配 (P2 — 参考 Claude Code prompts.ts + systemPromptSections.ts) prompt_sections: AgentPromptSectionsConfig = Field(default_factory=AgentPromptSectionsConfig) # Token 预算管理 (P2 — 参考 Claude Code tokenBudget.ts) token_budget: AgentTokenBudgetConfig = Field(default_factory=AgentTokenBudgetConfig) class AgentMessage(BaseModel): """Agent 对话消息""" role: str # user / assistant / tool content: str tool_calls: Optional[List[Dict[str, Any]]] = None tool_call_id: Optional[str] = None name: Optional[str] = None class AgentStep(BaseModel): """Agent 单步执行记录(用于执行追踪)""" iteration: int = Field(..., description="第几步") type: str = Field(..., description="步骤类型: think / tool_call / tool_result / final") content: str = Field(default="", description="步骤内容") tool_name: Optional[str] = Field(default=None, description="工具名称(tool_call/tool_result 类型时)") tool_input: Optional[Dict[str, Any]] = Field(default=None, description="工具输入参数") tool_result: Optional[str] = Field(default=None, description="工具执行结果") reasoning: Optional[str] = Field(default=None, description="思考过程") class TokenUsageInfo(BaseModel): """Token 预算信息 — 随 AgentResult 返回给前端展示用量条""" input_tokens: int = 0 input_remaining: int = 0 input_usage_pct: float = 0.0 effective_window: int = 128_000 context_window: int = 128_000 cumulative_total: int = 0 cumulative_prompt: int = 0 cumulative_completion: int = 0 llm_call_count: int = 0 is_warning: bool = False is_critical: bool = False is_exhausted: bool = False compaction_attempts: int = 0 user_budget: Optional[int] = None user_budget_used: Optional[int] = None user_budget_remaining: Optional[int] = None user_budget_pct: Optional[float] = None class AgentResult(BaseModel): """Agent 执行结果""" success: bool = True content: str = "" truncated: bool = False iterations_used: int = 0 tool_calls_made: int = 0 error: Optional[str] = None steps: List[AgentStep] = Field(default_factory=list, description="执行追踪步骤详情") token_usage: Optional[TokenUsageInfo] = Field(default=None, description="Token 预算摘要")