chore(backend): 补充核心流程注释与待办

This commit is contained in:
2026-08-30 22:59:45 +08:00
parent df044d7888
commit 629a6bda9c
6 changed files with 39 additions and 1 deletions
+6
View File
@@ -1,3 +1,5 @@
"""Agent 工具权限策略与一次性确认票据。"""
import asyncio
from dataclasses import dataclass
from enum import Enum
@@ -51,6 +53,7 @@ class PermissionPolicy:
def mode_for(self, permission: str | None) -> PermissionMode:
if permission is None:
return PermissionMode.allow
# 未登记权限一律拒绝,防止扩展通过拼写错误或新权限绕过策略。
return self._rules.get(permission, PermissionMode.deny)
@@ -63,6 +66,8 @@ class PermissionTicket:
class PermissionManager:
"""管理当前进程内的确认请求与会话级授权。"""
def __init__(self, policy: PermissionPolicy) -> None:
self.policy = policy
self._pending: dict[tuple[str, str], PermissionTicket] = {}
@@ -94,6 +99,7 @@ class PermissionManager:
if ticket is None or ticket.future.done():
return False
if decision == "allow_session":
# 会话授权只存在于进程内,应用重启后按默认策略重新确认。
self._session_grants.add(ticket.permission)
ticket.future.set_result(decision)
return True
+13
View File
@@ -1,3 +1,5 @@
"""Agent 运行时:负责模型轮次、工具调用、权限确认与事件发布。"""
from __future__ import annotations
import asyncio
@@ -50,6 +52,8 @@ MAX_TOOL_CALLS_PER_TURN = 50
@dataclass(slots=True)
class RunRecord:
"""单次运行的可变上下文,仅由 AgentRuntime 持有。"""
run: AgentRun
request: AgentRunCreateRequest
skill_config: AgentConfiguration | None = None
@@ -60,6 +64,8 @@ class RunRecord:
class AgentRuntime:
"""进程内 Agent 编排器;对外返回深拷贝,避免调用方修改运行状态。"""
def __init__(
self,
providers: ProviderRegistry,
@@ -98,6 +104,7 @@ class AgentRuntime:
)
allowed_tools = list(request.allowed_tools)
if skill_config is not None:
# 同时指定 Skill 与工具白名单时取交集,避免 Skill 扩大调用权限。
allowed_tools = (
[name for name in skill_config.allowed_tools if name in allowed_tools]
if allowed_tools
@@ -142,6 +149,8 @@ class AgentRuntime:
async def events(self, run_id: str) -> AsyncIterator[AgentEvent]:
record = self._get_record(run_id)
# 先回放快照再订阅实时事件,使晚加入的 SSE 客户端也能恢复界面状态。
# TODO(agent): 持久化事件并支持 Last-Event-ID,进程重启后仍可续传。
queue: asyncio.Queue[AgentEvent] = asyncio.Queue()
record.subscribers.add(queue)
history = [event.model_copy(deep=True) for event in record.events]
@@ -243,6 +252,7 @@ class AgentRuntime:
messages.append(
Message(role=MessageRole.assistant, content=turn.text or "", tool_calls=calls)
)
# 工具可以并发执行,但结果按模型原始调用顺序写回上下文,保证轮次可复现。
semaphore = asyncio.Semaphore(record.request.max_concurrent_tools)
async def execute(call: ToolCall) -> ToolResult:
@@ -313,6 +323,7 @@ class AgentRuntime:
if mode == PermissionMode.deny:
result = self._permission_denied(call)
elif mode == PermissionMode.confirm and permission:
# 运行状态必须在等待期间可见,前端才能展示并处理权限确认卡片。
ticket = self.permissions.create_ticket(record.run.run_id, permission)
record.run.status = AgentRunStatus.waiting_permission
self._publish(
@@ -408,6 +419,7 @@ class AgentRuntime:
timestamp=datetime.now(timezone.utc),
)
record.events.append(event)
# 内存事件只保留最近窗口;完整审计轨迹应由后续持久化层承担。
if len(record.events) > MAX_EVENTS_PER_RUN:
del record.events[: len(record.events) - MAX_EVENTS_PER_RUN]
for queue in record.subscribers:
@@ -448,6 +460,7 @@ class AgentRuntime:
raise AgentRunNotFoundError(run_id) from exc
def _prune_records(self) -> None:
# 只清理终态记录,绝不为了容量取消仍在执行或等待授权的任务。
overflow = len(self._records) - MAX_RUN_RECORDS + 1
if overflow <= 0:
return
+6 -1
View File
@@ -1,3 +1,5 @@
"""Agent 工具注册与执行边界。"""
import inspect
from dataclasses import dataclass
from time import perf_counter
@@ -29,6 +31,8 @@ class ToolNotFoundError(LookupError):
class ToolRegistry:
"""统一校验工具入参并隔离执行异常,避免单个工具击穿 Agent 主循环。"""
def __init__(self) -> None:
self._tools: dict[str, RegisteredTool] = {}
@@ -80,6 +84,7 @@ class ToolRegistry:
)
try:
# JSON Schema 约束模型可见的协议,Pydantic 再完成运行时类型转换。
Draft202012Validator(registered.definition.parameters).validate(call.arguments)
arguments = registered.arguments_model.model_validate(call.arguments)
except (ValidationError, JsonSchemaValidationError) as exc:
@@ -103,7 +108,7 @@ class ToolRegistry:
output=output,
duration_ms=round((perf_counter() - started) * 1000),
)
except Exception as exc: # Tool failures are isolated from the Agent loop.
except Exception as exc: # 工具失败转换成结构化结果,由模型决定是否降级或重试。
return ToolResult(
tool_call_id=call.tool_call_id,
name=call.name,