当前位置: 首页 > news >正文

第一篇:把任意 HTTP API 一键变成 Agent 工具

第零篇里,Agent 只能用本地函数干活。今天把边界推到外部世界:给它一个 OpenAPI 片段,就能自动生成可被模型“点名调用”的 HTTP 工具。重点不在花式封装,而是把“可调用契约、参数校验、错误回传”做扎实,保证模型每次出手都在你的防护网里。

先给 Agent 一个朴素但工程可用的 HTTP 执行层。我用统一返回结构把成功与失败都收敛成同一个形态,便于模型学习与我们做观测;同时限制返回大小,防止大响应淹没上下文。生产里你可以接入重试、熔断和指标,这里先把主干打通。

import requests, json
from dataclasses import dataclass
from typing import Any@dataclass
class HttpResult:ok: boolstatus: intdata: Any | Noneerror: str | Nonedef http_call(method: str, url: str, *, params=None, headers=None, json_body=None, timeout_s: int = 8) -> HttpResult:try:r = requests.request(method.upper(), url, params=params, headers=headers, json=json_body, timeout=timeout_s)ctype = r.headers.get("content-type", "")if "application/json" in ctype.lower():payload = r.json()else:payload = r.text[:20000]                      # 控制返回上限,避免拖垮上下文return HttpResult(r.ok, r.status_code, payload if r.ok else None, None if r.ok else r.text[:2000])except Exception as e:return HttpResult(False, 0, None, str(e))

模型得知道能用哪些工具、如何传参。我们沿用第零篇的思路:函数即工具,docstring 即说明;但今天的函数不是手写,而是由 OpenAPI 语义生成。为了降低门槛,我选了“只解析我们要用的那点信息”的策略:base URL、path、HTTP 方法、path/query 参数与其类型。别被完整规范吓到,能跑通 80% 的公共 API 已经够 Agent 发挥。

from typing import Dict, Callable
import reTOOLS: Dict[str, Callable[..., Any]] = {}def tool_specs() -> str:lines = []for name, fn in TOOLS.items():lines.append(f"- {name}{fn.__doc__ and ':' + fn.__doc__}")return "\n".join(lines)def register_openapi_tool(spec: dict, path: str, method: str, name: str | None = None) -> str:base = (spec.get("servers") or [{"url": ""}])[0]["url"].rstrip("/")op = spec["paths"][path][method.lower()]params = op.get("parameters", [])summary = op.get("summary", "").strip() or f"{method.upper()} {path}"tool_name = name or op.get("operationId") or f"{method.lower()}_{path.strip('/').replace('/','_')}"doc_param = []for p in params:t = ((p.get("schema") or {}).get("type") or "any").lower()need = "必填" if p.get("required") else "可选"loc = p.get("in")doc_param.append(f"{p['name']}:{t}({loc},{need})")doc = f"{summary}。参数:{', '.join(doc_param)}"def _fill_path(tmpl: str, kv: dict) -> str:def rep(m):k = m.group(1)if k not in kv: raise ValueError(f"缺少路径参数 {k}")return str(kv.pop(k))return re.sub(r"\{(\w+)\}", rep, tmpl)def _typecheck(v, t: str, n: str):if t == "integer" and not isinstance(v, int): raise TypeError(f"{n} 需要 integer")if t == "number" and not isinstance(v, (int, float)): raise TypeError(f"{n} 需要 number")if t == "boolean" and not isinstance(v, bool): raise TypeError(f"{n} 需要 boolean")if t == "string" and not isinstance(v, str): passdef created_tool(**kwargs):path_vars, query_vars = {}, {}for p in params:n, loc = p["name"], p.get("in")need = p.get("required", False)if need and n not in kwargs: raise ValueError(f"缺少必填参数 {n}")if n in kwargs:t = ((p.get("schema") or {}).get("type") or "any").lower()_typecheck(kwargs[n], t, n)if loc == "path": path_vars[n] = kwargs[n]elif loc == "query": query_vars[n] = kwargs[n]url = base + _fill_path(path, dict(path_vars))res = http_call(method, url, params=query_vars)return {"ok": res.ok, "status": res.status, "data": res.data, "error": res.error}created_tool.__doc__ = doccreated_tool.__name__ = tool_nameTOOLS[tool_name] = created_toolreturn tool_name

到这一步,任何符合“有 servers、有 paths、有 parameters”的 OpenAPI 片段都能被注册成一个工具。为了让你能当场试起来,我准备了一个极简规范片段,使用公开天气服务的查询参数语义;真实项目里你会从远端拉取 API 文档,挑你愿意暴露给模型的少数几个端点注册进去。

openapi_spec = {"servers": [{"url": "https://api.open-meteo.com/v1"}],"paths": {"/forecast": {"get": {"summary": "查询天气预报","parameters": [{"name": "latitude",  "in": "query", "required": True,  "schema": {"type": "number"}},{"name": "longitude", "in": "query", "required": True,  "schema": {"type": "number"}},{"name": "hourly",    "in": "query", "required": False, "schema": {"type": "string"}}]}}}
}tool_name = register_openapi_tool(openapi_spec, "/forecast", "get", name="weather_forecast")
print(tool_specs())

模型看见的“工具清单”此刻已经包含了 weather_forecast,docstring 里明明白白写着参数、类型和是否必填。第零篇的系统提示里只需把 tool_specs() 串起来,模型就能在需要外部信息时,生成形如 {"action":"tool","tool":"weather_forecast","args":{"latitude":..., "longitude":...}} 的调用请求。要注意的一点是,HTTP 返回常常体积巨大,不要把原样 JSON 生吞进上下文,你应该只保留关键字段或者先做一次下采样,再反馈给模型继续决策。

def shrink(payload: Any, budget: int = 4000) -> Any:try:s = json.dumps(payload)if len(s) <= budget: return payloadif isinstance(payload, dict):keep = {}for k in list(payload.keys())[:20]:keep[k] = payload[k]return {"__truncated__": True, "keys": list(keep.keys()), "sample": keep}if isinstance(payload, list):return {"__truncated__": True, "len": len(payload), "head": payload[:20]}return str(payload)[:budget]except Exception:return str(payload)[:budget]

这段压缩器不是“可有可无”的优化,而是把 HTTP→LLM 的信息通道稳定化的关键。很多人把“API 调得越多越好”当成 Agent 的聪明,现实里你需要的是“每一步都把有用的信息以可控大小回传”,否则上下文反而会被噪声淹没,模型开始瞎猜。你完全可以在 created_tool 里把 res.data 先喂给 shrink 再返回,这样即使遇到异常大的 JSON,也不会拖垮循环。

在工程边界上再把两件事做硬。第一件是参数层面的自修复,当类型或必填校验失败时,不要静默失败,直接把错误打包成一个结构化的 tool 事件回消息,让模型据此改参重试。这个反馈比“自然语言报错”有效得多,因为它是可机读的、可学习的。

def created_tool(**kwargs):try:path_vars, query_vars = {}, {}for p in params:n, loc = p["name"], p.get("in")need = p.get("required", False)if need and n not in kwargs: raise ValueError(f"missing:{n}")if n in kwargs:t = ((p.get("schema") or {}).get("type") or "any").lower()_typecheck(kwargs[n], t, n)(path_vars if loc=="path" else query_vars)[n] = kwargs[n]url = base + _fill_path(path, dict(path_vars))res = http_call(method, url, params=query_vars)data = shrink(res.data) if res.ok else Nonereturn {"ok": res.ok, "status": res.status, "data": data, "error": res.error}except Exception as e:return {"ok": False, "status": 0, "data": None, "error": f"arg_error:{e}"}

第二件是最小化暴露面。不要把整个 OpenAPI 丢给模型,更不要把“写操作”轻易打开。只注册你愿意承担后果的只读端点,并在 docstring 里说清可接受的参数范围。很多线上事故不是模型“变坏”,而是我们“给多了”。约束就是能力。

收个尾,把这一小块拼回第零篇的闭环。System Prompt 里还是那条严格 JSON 协议,只是工具清单里多了一个 weather_forecast。当用户问“明天深圳还下雨吗”,模型首先选择工具并给出参数,你执行 HTTP、裁剪响应、回填观测,再让模型根据观测收口或继续。如果今天的这条路走通了,明天你只需再注册两个端点,就能让 Agent 会查汇率、读日历或看库存,业务价值的曲线会非常陡。

下一篇换一条完全不同的路线,我会把“检索当工具而不是把长文硬塞进提示”的做法拿出来,教 Agent 用向量检索与 rerank 做信息定位,用三分钟搭一个不会幻觉的知识问答。

http://www.dtcms.com/a/516034.html

相关文章:

  • 使用PCIE B210烧写SIM卡
  • 大模型太贵太慢?豆包1.6想打破这个“行业幻觉”
  • 卖酒网站排名阳江 网站建设
  • 唐宇迪2025最新机器学习课件——学习心得(1)
  • python基于卷积神经网络的桥梁裂缝检测系统(django),附可视化界面,源码
  • 网站建设要学什么asp.net做电商网站设计
  • OpenTelemetry日志采集和链路跟踪部署与问题解决文档
  • Rocky 9 单机安装elastic-9.1.5
  • 黑马程序员C++提高编程_3.STL- 常用容器_list容器
  • 免费模板网站word医疗室内设计网站推荐
  • flutter实现web端实现效果
  • 网站建设与管理题目wordpress页面标题标签
  • 在线预览docx、ppt、excel、doc、pdf等文档解决方案
  • !process 命令详解
  • 渗透测试(4):SQL注入示例
  • 三明做网站全球速卖通规则
  • python3编程基础
  • 解决时序违例(四)
  • 容器化安装新玩法:轻量高效一键部署
  • JavaScript函数基础
  • 实木餐桌椅移动网站建设网站建设定制开发
  • 邯郸网站设计价格特色产品推广方案
  • vscode安装、部署和小技巧 记录
  • 简单常见的勒索病毒加密
  • docker基本知识
  • 什么网站服务器好wordpress如何添加目录菜单
  • 12、【Ubuntu】【VSCode】VSCode 断联问题分析:getent 命令(二)
  • RHCSA作业2
  • 如何才能提升视频的「听觉质感」
  • 江西省赣州电子商务网站怎么做seo