初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,5 @@
|
||||
"""随应用部署的受信任 Python Skills。
|
||||
|
||||
新增 Skill 时创建同级模块并导出 ``setup(registry)``。目录仅接收经过代码审查的模块;
|
||||
数据库 Skill 与用户上传内容不得写入这里或在 FastAPI 进程内动态执行。
|
||||
"""
|
||||
@@ -0,0 +1,39 @@
|
||||
"""工具参数守卫:拒绝参数过大的工具调用,防止模型生成超大输入。"""
|
||||
|
||||
import json
|
||||
|
||||
from app.application.agent.extensions import (
|
||||
AgentRunContext,
|
||||
BeforeToolCallDecision,
|
||||
ExtensionRegistry,
|
||||
ToolCallContext,
|
||||
)
|
||||
from app.application.agent.python_skills import PythonSkillDefinition
|
||||
|
||||
# 单次工具调用参数序列化后的字符上限;超出则拒绝并要求模型精简。
|
||||
_MAX_ARG_CHARS = 8000
|
||||
|
||||
|
||||
async def _reject_oversized_args(
|
||||
call: ToolCallContext, _run: AgentRunContext
|
||||
) -> BeforeToolCallDecision | None:
|
||||
size = len(json.dumps(call.input, ensure_ascii=False))
|
||||
if size <= _MAX_ARG_CHARS:
|
||||
return None
|
||||
return BeforeToolCallDecision(
|
||||
allowed=False,
|
||||
reason=f"工具 {call.name} 的参数过大({size} 字符超过 {_MAX_ARG_CHARS}),请精简后重试。",
|
||||
)
|
||||
|
||||
|
||||
def _setup(registry: ExtensionRegistry) -> None:
|
||||
registry.add_before_tool_hook(_reject_oversized_args)
|
||||
|
||||
|
||||
SKILL = PythonSkillDefinition(
|
||||
name="arg_guard",
|
||||
description="拒绝参数过大的工具调用,避免超大输入挤占上下文或触发限制。",
|
||||
triggers=("大参数", "arg guard"),
|
||||
instructions="发起工具调用时保持参数精简;超大参数会被拒绝。",
|
||||
setup=_setup,
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Token 预算守卫:累计消耗超阈值时优雅收尾。"""
|
||||
|
||||
from app.application.agent.extensions import (
|
||||
AgentRunContext,
|
||||
ExtensionRegistry,
|
||||
TurnSnapshot,
|
||||
)
|
||||
from app.application.agent.python_skills import PythonSkillDefinition
|
||||
|
||||
# 累计 total_tokens 软上限。达到后 TurnPolicy 要求停止,让 Agent 用当前内容自然
|
||||
# 收尾——区别于 max_iterations 的硬截断(truncated=True,回复可能只说了一半)。
|
||||
_BUDGET_TOTAL_TOKENS = 20000
|
||||
|
||||
|
||||
async def _stop_when_budget_exhausted(
|
||||
turn: TurnSnapshot, _run: AgentRunContext
|
||||
) -> bool:
|
||||
return turn.usage.total_tokens >= _BUDGET_TOTAL_TOKENS
|
||||
|
||||
|
||||
def _setup(registry: ExtensionRegistry) -> None:
|
||||
registry.add_turn_policy(_stop_when_budget_exhausted)
|
||||
|
||||
|
||||
SKILL = PythonSkillDefinition(
|
||||
name="budget_guard",
|
||||
description="累计 token 消耗超预算时优雅停止 Agent 循环,避免成本失控。",
|
||||
triggers=("预算", "budget", "长任务"),
|
||||
instructions=(
|
||||
"本轮受 token 预算约束。若已接近上限,请用当前已有信息给出最终回复,"
|
||||
"不要发起新的工具调用或长篇推理。"
|
||||
),
|
||||
setup=_setup,
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
"""上下文预算守卫:历史过长时折叠中间消息,保护 prompt 预算。"""
|
||||
|
||||
from app.ai_provider.types.messages import BaseMessage
|
||||
from app.application.agent.extensions import (
|
||||
AgentContext,
|
||||
AgentRunContext,
|
||||
ExtensionRegistry,
|
||||
)
|
||||
from app.application.agent.python_skills import PythonSkillDefinition
|
||||
|
||||
# messages 总字符数软上限;超出则保留首条与末尾 _TAIL_KEEP 条,中间折叠。
|
||||
_MAX_CONTEXT_CHARS = 12000
|
||||
_TAIL_KEEP = 8
|
||||
|
||||
|
||||
async def _trim_history(context: AgentContext, _run: AgentRunContext) -> AgentContext:
|
||||
messages = context.messages
|
||||
total = sum(len(message.content) for message in messages)
|
||||
if total <= _MAX_CONTEXT_CHARS or len(messages) <= _TAIL_KEEP + 1:
|
||||
return context
|
||||
first = messages[0]
|
||||
tail = messages[-_TAIL_KEEP:]
|
||||
dropped = len(messages) - 1 - _TAIL_KEEP
|
||||
# 在首条追加折叠提示(新建对象避免污染原 message);不在历史中间插入新消息,
|
||||
# 以免破坏 provider 对 system 位置或多轮 tool 消息配对的预期。
|
||||
folded = BaseMessage(
|
||||
content=f"{first.content}\n\n[已省略 {dropped} 条较早历史]",
|
||||
role=first.role,
|
||||
)
|
||||
return AgentContext(
|
||||
messages=(folded, *tail),
|
||||
tools=context.tools,
|
||||
model_hint=context.model_hint,
|
||||
)
|
||||
|
||||
|
||||
def _setup(registry: ExtensionRegistry) -> None:
|
||||
registry.add_context_transformer(_trim_history)
|
||||
|
||||
|
||||
SKILL = PythonSkillDefinition(
|
||||
name="context_budget",
|
||||
description="对话历史过长时折叠中间消息,控制 prompt 预算。",
|
||||
triggers=("长对话", "context", "历史"),
|
||||
instructions="历史较长时优先依赖近期消息,不必回溯很早的内容。",
|
||||
setup=_setup,
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
"""工具输出防护示例 Skill。"""
|
||||
|
||||
from dataclasses import replace
|
||||
|
||||
from app.application.agent.extensions import (
|
||||
AfterToolCallDecision,
|
||||
AgentRunContext,
|
||||
ExtensionRegistry,
|
||||
ToolCallContext,
|
||||
)
|
||||
from app.application.agent.python_skills import PythonSkillDefinition
|
||||
from app.modules.tool_runtime.definition import ToolResult
|
||||
|
||||
_MAX_OUTPUT_CHARS = 4000
|
||||
|
||||
|
||||
async def _limit_tool_output(
|
||||
_call: ToolCallContext,
|
||||
result: ToolResult,
|
||||
_run: AgentRunContext,
|
||||
) -> AfterToolCallDecision | None:
|
||||
if len(result.stdout) <= _MAX_OUTPUT_CHARS and len(result.stderr) <= _MAX_OUTPUT_CHARS:
|
||||
return None
|
||||
return AfterToolCallDecision(
|
||||
result=replace(
|
||||
result,
|
||||
stdout=result.stdout[:_MAX_OUTPUT_CHARS],
|
||||
stderr=result.stderr[:_MAX_OUTPUT_CHARS],
|
||||
truncated=True,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _setup(registry: ExtensionRegistry) -> None:
|
||||
registry.add_after_tool_hook(_limit_tool_output)
|
||||
|
||||
|
||||
SKILL = PythonSkillDefinition(
|
||||
name="response_guard",
|
||||
description="限制过长工具输出,避免无关内容挤占模型上下文。",
|
||||
triggers=("长输出", "大量输出", "response guard"),
|
||||
instructions="工具输出可能很长;优先分段读取,只保留完成当前任务所需的内容。",
|
||||
setup=_setup,
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
"""运行指标采集:监听 Agent 生命周期事件,结束时记录摘要日志。"""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from app.application.agent.extensions import (
|
||||
AgentEvent,
|
||||
AgentRunContext,
|
||||
ExtensionRegistry,
|
||||
)
|
||||
from app.application.agent.python_skills import PythonSkillDefinition
|
||||
|
||||
|
||||
def _setup(registry: ExtensionRegistry) -> None:
|
||||
turns = 0
|
||||
tool_calls = 0
|
||||
tool_errors = 0
|
||||
tool_denied = 0
|
||||
|
||||
async def listen(event: AgentEvent, run: AgentRunContext) -> None:
|
||||
nonlocal turns, tool_calls, tool_errors, tool_denied
|
||||
# 工具事件经 Agent._on_tool_event 桥接进扩展事件流,故此处可直接观测
|
||||
# tool.started / tool.completed(status: success / error / denied)。
|
||||
if event.type == "agent.turn.started":
|
||||
turns += 1
|
||||
elif event.type == "tool.started":
|
||||
tool_calls += 1
|
||||
elif event.type == "tool.completed":
|
||||
status = event.payload.get("status")
|
||||
if status == "error":
|
||||
tool_errors += 1
|
||||
elif status == "denied":
|
||||
tool_denied += 1
|
||||
elif event.type == "agent.settled":
|
||||
logger.info(
|
||||
"agent_run_metrics",
|
||||
run_id=run.run_id,
|
||||
turns=turns,
|
||||
tool_calls=tool_calls,
|
||||
tool_errors=tool_errors,
|
||||
tool_denied=tool_denied,
|
||||
status=event.payload.get("status"),
|
||||
)
|
||||
|
||||
registry.add_event_listener(listen)
|
||||
|
||||
|
||||
SKILL = PythonSkillDefinition(
|
||||
name="run_metrics",
|
||||
description="采集 Agent 轮次指标并在运行结束时记录摘要日志。",
|
||||
triggers=("指标", "metrics", "观测"),
|
||||
instructions="",
|
||||
setup=_setup,
|
||||
)
|
||||
Reference in new issue
Block a user