Files
AgentLab/backend/app/db/models_capabilities.py
T

83 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""能力登记与登记的端点(应用平台 A6,docs/design/app-platform-and-look.md §2.6、§3.9)。
capabilities 一行 = 图里一个可调用的工具(主键就是图里写的工具名),分两层:
- 系统同步:内置工具、数据源、自定义工具、MCP 启动时和各自增删改时同步进来(effect_system、params_schema、
source_hash、synced_at、missing);
- 人工治理:effect、approval、unattended_write、returns_schema、tabular、fields、masked、credential_mode、lookup、
endpoint_id。每改一次 version 加一,运行发起时把用到的能力的治理快照固定下来(Run.options["capabilities"])。
endpoints 是管理员登记的外部系统:按环境(test / prod)存 base_url 和认证头(值加密)。只有登记的端点能开内网访问;
模型给出的任意地址照旧走 tools/net.py 的完整 SSRF 检查。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, TimestampMixin, new_id
#: 能力对外部世界的效果。dynamic:同一个工具看这一次的参数定(可写数据源上的 db_query__:查询只读、DML 是写)
EFFECTS = ("read", "write", "dynamic")
#: 审批:inherit 跟三档信任;confirm 总要确认;none 只读能力在正式运行里免审批
APPROVALS = ("inherit", "confirm", "none")
#: 凭据:service 服务凭据;service_on_behalf 服务凭据 + AgentLab 签发的身份断言(不存用户自己的 token)
CREDENTIAL_MODES = ("service", "service_on_behalf")
class Capability(Base, TimestampMixin):
__tablename__ = "capabilities"
name: Mapped[str] = mapped_column(String(200), primary_key=True)
# 所属系统:数据源名、端点名、MCP 服务名、builtin、custom
system: Mapped[str] = mapped_column(String(100), default="")
title: Mapped[str] = mapped_column(String(200), default="")
description: Mapped[str] = mapped_column(Text, default="")
params_schema: Mapped[dict[str, Any]] = mapped_column(default=dict)
returns_schema: Mapped[dict[str, Any] | None] = mapped_column(default=None)
# 端点返回落成表格:{"rows": "/data/items", "columns": {"单号": "/id", "金额": "/amount"}}。声明了才能作证
tabular: Mapped[dict[str, Any] | None] = mapped_column(default=None)
# 同步时得出的效果(数据源可写为 dynamic,内置工具按 ToolSpec.dangerous,MCP 的 readOnlyHint 只当建议)
effect_system: Mapped[str] = mapped_column(String(20), default="dynamic")
# 治理后的效果;为空表示还没人确认过,按 effect_system 算
effect: Mapped[str | None] = mapped_column(String(20), default=None)
approval: Mapped[str] = mapped_column(String(20), default="inherit")
unattended_write: Mapped[bool] = mapped_column(default=False)
# 返回字段的语义,按 JSON 指针:{"/amount": {"label": 项, "unit": 项, ...}},「项」的形状同数据目录
# (data/catalog.py:{value, source, status, note, reason, updated_at})
fields: Mapped[dict[str, Any]] = mapped_column(default=dict)
# 遮罩字段(JSON 指针);查看、快照、视图都按它遮
masked: Mapped[list[Any]] = mapped_column(default=list)
credential_mode: Mapped[str] = mapped_column(String(30), default="service")
endpoint_id: Mapped[str | None] = mapped_column(String(32), default=None)
# 按幂等键查写入结果用的能力名(结果未知时自动核实)
lookup: Mapped[str | None] = mapped_column(String(200), default=None)
version: Mapped[int] = mapped_column(Integer, default=1)
source_hash: Mapped[str | None] = mapped_column(String(64), default=None)
synced_at: Mapped[datetime | None] = mapped_column(default=None)
# 来源已经删掉了(数据源、自定义工具、MCP 工具不在了)。不删行:运行和发布记录还引用它的版本
missing: Mapped[bool] = mapped_column(default=False)
updated_by: Mapped[str | None] = mapped_column(String(100), default=None)
class Endpoint(Base, TimestampMixin):
"""登记的外部系统。
environments = {"test": {...}, "prod": {...}},每个环境:
base_url、headers(值经 core/crypto 加密,接口只回掩码)、allow_intranet(只有管理员能开)、verify_tls。
正式运行走 prod;探索运行走 test,没有 test 时演练(不发送,记 simulated)。
"""
__tablename__ = "endpoints"
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(100), unique=True)
description: Mapped[str] = mapped_column(Text, default="")
environments: Mapped[dict[str, Any]] = mapped_column(default=dict)
idempotency_header: Mapped[str] = mapped_column(String(100), default="Idempotency-Key")
timeout_s: Mapped[int] = mapped_column(Integer, default=20)
version: Mapped[int] = mapped_column(Integer, default=1)
updated_by: Mapped[str | None] = mapped_column(String(100), default=None)