工具 API 参考¶
English · 简体中文
本文档描述 P-A-R SDK 提供的 23 个内置工具。新增工具通过 Runtime.register_tool 注册,工具描述会被注入 LLM 的 system prompt,让 LLM 知道何时调用哪个工具。
版本: v0.7.1
工具总数: 23(v0.3.0 的 19 个 + v0.3.1 新增的 bash + v0.7.1 新增的 3 个 memory 工具)
概览¶
每个工具在注册时携带以下元数据:
type tool_descriptor = {
name : string; (* 工具名,agent 调用时引用 *)
description : string; (* 描述,注入 LLM system prompt *)
input_schema : Yojson.Safe.t; (* JSON Schema *)
output_schema : Yojson.Safe.t option; (* 可选 JSON Schema,用于结构化输出 *)
permission : tool_permission; (* Allow / Confirm / Deny / ... *)
timeout : float option; (* 秒;None 表示无超时 *)
concurrency_limit : int option; (* 最大并发调用数 *)
on_update : (string -> unit) option; (* v0.3+ 进度回调 *)
cache_control : cache_control option; (* 可选的 provider 端缓存提示,例如 Anthropic prompt caching *)
}
工具按用途分五类:
| 类别 | 数量 | 工具 |
|---|---|---|
| math | 1 | calculator |
| utility | 9 | get_time / echo / generate_uuid / hash_text / generate_password / string_stats / json_format / convert_temperature / url_encode |
| web | 3 | fetch_url / read_webpage / web_search |
| fs (read) | 4 | read / ls / find / grep |
| fs (write) | 2 | write / edit |
| exec | 1 | bash(v0.3.1 新增,唯一带安全策略) |
| memory | 3 | recall_memory / remember_memory / search_history(v0.7.1 新增,需配置 Memory_service) |
除 bash 外,所有工具的 permission = Allow。文件路径类工具(read / ls / find / grep / write / edit)拒绝绝对路径与含 : 的路径。Memory 工具(recall_memory / remember_memory / search_history)需要配置 Memory_service,从 Invoke_context.get_current_exn().session_id 读取 scope。
快速索引¶
| 工具 | 类别 | 风险 | 超时 | 备注 |
|---|---|---|---|---|
calculator |
math | 低 | 5s | +/-/*// 表达式求值 |
get_time |
utility | 低 | 2s | 返回 UTC ISO 8601 时间 |
echo |
utility | 低 | 2s | 回显输入字符串 |
generate_uuid |
utility | 低 | 1s | UUID v4 |
hash_text |
utility | 低 | 2s | md5 / sha1 / sha256(默认 sha256) |
generate_password |
utility | 低 | 1s | 长度 4-128,符号可选 |
string_stats |
utility | 低 | 1s | 字符 / 词 / 行数 |
json_format |
utility | 低 | 2s | 校验 + 美化 |
convert_temperature |
utility | 低 | 1s | C / F / K 互转 |
url_encode |
utility | 低 | 1s | encode / decode |
fetch_url |
web | 中 | 15s | HTTP GET,10MB 上限 |
read_webpage |
web | 中 | 15s | fetch + HTML 解析,剥离 script/style |
web_search |
web | 中 | 15s | DuckDuckGo lite |
read |
fs (read) | 低 | 30s | 10MB 上限;二进制返回 base64 |
ls |
fs (read) | 低 | 10s | 目录列表,按名排序 |
find |
fs (read) | 低 | 30s | glob 模式,跳过 .git / _build 等 |
grep |
fs (read) | 低 | 30s | 正则匹配,输出 path:line:text |
write |
fs (write) | 中 | 30s | 可选 create_dirs 自动 mkdir -p |
edit |
fs (write) | 中 | 30s | 批量替换;重叠区间被拒 |
bash |
exec | 高 | 60s | v0.3.1 新增,9 维安全机制 |
recall_memory |
memory | 低 | 10s | 按关键词搜索记忆,按会话分区 |
remember_memory |
memory | 低 | 10s | 存储新记忆,按会话分区 |
search_history |
memory | 低 | 10s | 搜索跨会话的对话历史 |
math 类¶
calculator¶
评估算术表达式,支持 +、-、*、/ 和括号。
输入:
输出(数字,无理数保留为 float):
注意:实现是手写词法 + 递归下降(不依赖外部 eval 库),只接受数字与四则运算符;空格被忽略但不支持负数前缀("-1+2" 解析为 1 + 2,需要写为 "0-1+2")。
utility 类¶
get_time¶
返回当前 UTC 时间(ISO 8601 格式)。
输入:{}(无参数)
输出:
echo¶
回显输入文本(用于调试 / agent 间通信)。
输入:
输出:
generate_uuid¶
生成随机 UUID v4。
输入:{}
输出:
hash_text¶
对文本计算哈希,支持 md5 / sha1 / sha256(默认 sha256,大小写不敏感)。
输入:
输出:
安全提示:MD5 / SHA1 已不抗碰撞攻击,仅用于非安全场景(去重、缓存键)。
generate_password¶
生成随机密码,长度 4-128(输入范围外自动夹紧),符号默认包含。
输入:
输出:
注意:使用 Random.State.make_self_init,不保证密码学强度。生产场景请用专业库。
string_stats¶
统计字符数、词数、行数。词以空格切分。
输入:
输出:
json_format¶
校验 + 美化 JSON 字符串。
输入:
输出:
注意:解析失败返回 Error (Invalid_input "Invalid JSON: ..."),retryable = false。
convert_temperature¶
C / F / K 互转。
输入:
输出:
url_encode¶
URL 编码或解码。默认 encode;decode: true 时反向操作。解码时 + 视为空格(form-encoding 兼容)。
输入:
输出:
web 类¶
三个网络工具共享 validate_url(仅允许 http:// 与 https://)+ max_download_size = 10MB + 系统 CA 证书 + TLS 主机名校验。
fetch_url¶
HTTP GET 原始文本。
输入:
输出:
{
"url": "https://example.com",
"status": 200,
"content": "...",
"content_length": 1234,
"truncated": false
}
read_webpage¶
fetch + HTML 解析,剥离 <script> / <style> / <noscript>,返回纯文本。
输入:
输出:
{
"url": "https://example.com",
"title": "Example Domain",
"text": "...",
"text_length": 567,
"truncated": false
}
注意:依赖 lambdasoup。HTTP 4xx / 5xx 返回 Error (External_failure "HTTP <code>");5xx 与 429 标记 retryable = true。
web_search¶
DuckDuckGo lite 搜索。
输入:
输出:
{
"query": "ocaml lwt tutorial",
"results": [
{ "title": "...", "url": "...", "snippet": "..." }
],
"result_count": 5
}
注意:抓取 DuckDuckGo lite HTML(无需 API key)。网络或解析失败返回 Error (External_failure ...)。
fs (read) 类¶
四个读类工具拒绝绝对路径与含 : 的路径(Windows 盘符防护)。find / grep 默认跳过 .git / node_modules / _build / _opam 目录。
read¶
读文件内容,可指定行偏移与行数上限。
输入:
输出(带行号,类似 cat -n):
限制:
- 文件大小 ≤ 10MB(超过返回 Error (Invalid_input "File too large"))
- 路径必须相对于 workspace 根,或为 workspace 根下的绝对路径
ls¶
列目录内容。
输入:
输出:
{
"path": ".",
"entries": [
{ "name": "src", "type": "dir", "size": null, "modified": 1717... },
{ "name": "README.md", "type": "file", "size": 4321, "modified": 1717... }
]
}
注意:子项按文件名升序;type 是 dir / file / link / other / unknown;非目录路径返回 Error (Invalid_input "Not a directory")。
find¶
glob 匹配文件名(** 跨目录,* 跨组件但不含 /)。
输入:
输出:
grep¶
正则搜索文件内容。path 目录下递归匹配,glob 过滤文件名。
输入:
输出(每条 path:line:match):
注意:正则语法为 OCaml Str(POSIX 扩展正则)。当前实现忽略 context_lines(保留参数供未来扩展)。
fs (write) 类¶
write¶
写文件,可选 create_dirs 自动 mkdir -p。
输入:
输出:
注意:覆盖已存在文件;目录不存在时若 create_dirs 为 false 则报错。
edit¶
批量精确字符串替换。重叠区间被拒(避免编辑顺序歧义)。
输入:
{
"path": "src/main.ml",
"edits": [
{ "old": "let x = 1", "new": "let x = 2" },
{ "old": "foo", "new": "bar" }
]
}
输出:
注意:
- 每个 old 必须是文件的精确子串(包含空格、换行、缩进)
- 使用 Str.replace_first,不修改 old 之后可能出现的同名串
- 重叠检测:若两个 old 区间在文件中有交叠,工具拒绝并返回 Error (Invalid_input "Overlapping edits")
bash(v0.3.1 新增)¶
LLM 调用 shell 是最危险的内置工具。v0.3.0 故意没做 bash,等 v0.3.1 单独设计。核心理念:从"裸 shell string + 黑名单"升级为"类型化 Safe_command ADT + Policy Functor + 黑名单",把安全检查从运行时前移到编译期。
用途¶
执行 shell 命令,配合三层防御:
- 类型层:
argv强制为string list,没有Exec_raw_shell构造器(shell 注入在类型层不可表示) - 策略层:
POLICY.filter在运行时校验,可基于用户场景选择Coder/ReadOnly/ReadOnlyNoNet - 黑名单层:
Bash_blacklist31 条正则兜底(rm -rf /、dd of=/dev/sda、fork bomb 等)
输入¶
字段说明:
- argv(必需):参数数组,不是 shell 字符串
- cwd:工作目录(相对于 workspace 根,或 workspace 根下的绝对路径)。默认:"."
- timeout:最大执行秒数,默认 30,硬上限 600
输出¶
truncated: true 表示输出被 50KB / 2000 行截断(marker 追加在末尾)。
三个预置策略¶
| 策略 | 网络 | 写操作 | 用途 |
|---|---|---|---|
Coder(默认) |
✅ | ✅ | "AI 写代码",只拦截黑名单命中 |
ReadOnly |
✅ | ❌ | 纯只读工具(ls / cat / find / grep) |
ReadOnlyNoNet |
❌ | ❌ | 最大安全(敏感代码库 review 场景) |
自定义策略¶
实现 Bash_policy.POLICY 模块类型并传给 Runtime.create:
module type POLICY = sig
val name : string
val filter :
Bash_safe_command.command ->
(Bash_safe_command.command, Types.error_category) result
val max_cpu_seconds : float
val max_memory_kb : int
val allow_network : bool
val allow_write : bool
end
module MyStrictPolicy : Bash_policy.POLICY = struct
let name = "MyStrict"
let allow_network = false
let allow_write = false
let max_cpu_seconds = 10.0
let max_memory_kb = 524288
let filter cmd =
(* 你的额外检查:黑名单、白名单、argv 限制... *)
Ok cmd
end
(* Runtime.create ~bash_policy:(module MyStrictPolicy) *)
安装¶
bash 工具不随 Runtime.create 自动注册。需要在 Runtime.create 之后调用 install_bash_tool:
let () = Eio_main.run (fun env ->
Eio.Switch.run (fun sw ->
let mgr = Eio.Stdenv.process_mgr env in
let clock = Eio.Stdenv.clock env in
let fs = Eio.Stdenv.fs env in
match Runtime.create ~config:my_config sw with
| Error _ -> failwith "runtime create failed"
| Ok rt ->
(match Runtime.install_bash_tool ~process_mgr:mgr ~clock ~fs rt with
| Ok () -> () (* bash 工具就绪 *)
| Error e -> Printf.failwithf "bash install failed: %a"
Yojson.Safe.pp (Types.error_category_to_yojson e))
))
幂等:第二次调用返回 Error (Invalid_input "bash tool already installed")。
必需参数:
- process_mgr:Eio.Stdenv.process_mgr env,用于 Eio.Process.spawn
- clock:Eio.Stdenv.clock env,用于 timeout 强制(无 clock 则超时失效)
- fs:Eio.Stdenv.fs env,文件系统 capability,用于设置子进程的 cwd。不传则 bash 命令在 PAR 进程的 cwd 运行,workspace 沙箱失效。
9 维安全机制¶
| # | 机制 | 实现 |
|---|---|---|
| 1 | Workspace 锁定 | Workspace.sandboxed_path 抽象类型,构造时拒绝 ..、: 及敏感前缀(/etc、~/.ssh 等),workspace 根下的绝对路径被放行 |
| 2 | 黑名单 | Bash_blacklist 31 条正则(rm -rf /、dd of=/dev/sda、:(){:|:&};: 等) |
| 3 | 白名单(可选) | 自定义 POLICY 实现白名单逻辑 |
| 4 | 超时 | Eio.Process.spawn + Eio.Fiber.first race;硬上限 600s |
| 5 | 进程组清理 | Eio.Process + setpgid;超时通过 killpg 杀整组 |
| 6 | 环境脱敏 | Bash_policy.sanitize_env 剥离 *_SECRET* / *_KEY* / AWS_* / OPENAI_API_KEY / ANTHROPIC_API_KEY / GITHUB_TOKEN 等 |
| 7 | 输出截断 | 50KB 字节 + 2000 行;marker 追加 |
| 8 | ANSI 剥离 | 移除 CSI(ESC[...])与 OSC(ESC]...BEL)序列 |
| 9 | 审计日志 | event bus 发送 Bash_invoked / Bash_completed 事件(携带 risk 评分与 argv) |
安全建议¶
- 默认
Coder是"AI 写代码"场景的正确选择(与 pi 默认行为接近) - 只想让 LLM 检视(code review 场景)用
ReadOnly - 敏感代码库用
ReadOnlyNoNet - 永远不要禁用 timeout,它是 fork bomb 与网络挂起的最后防线
- 环境变量自动脱敏。如需传 secret,写到文件后用
read工具读取(不在env字段里传) - 黑名单是最后一道防线,不是主防御。安全关键场景请在自定义
POLICY里设allow_write:false+allow_network:false - OS 层沙箱(bwrap / landlock)v0.3.1 不提供
风险评分¶
Bash_safe_command.assess_risk 返回 Low / Medium / High / Critical,挂在 Bash_invoked 事件的 risk 字段上。
memory 类(v0.7.1 新增)¶
三个 memory 工具需要配置 Memory_service。当 Runtime.create 接收 ?memory 参数时自动注册。所有工具从 Invoke_context.get_current_exn().session_id 读取 scope,记忆自动按会话隔离。
recall_memory¶
按关键词查询搜索已存储的记忆。使用 FTS5 全文搜索和 BM25 排序。
输入:
输出:
{
"results": [
{ "id": "...", "content": "...", "summary": "...", "scope": "...", "categories": [...], "created_at": 1717... }
],
"count": 2
}
注意:limit 默认 5,上限 50。scope 从当前 invoke_context.session_id 读取。
remember_memory¶
存储新记忆以供将来检索。记忆按当前会话分区。
输入:
输出(创建的 memory_object):
{ "id": "550e8400-...", "content": "...", "summary": "...", "scope": "...", "categories": ["架构", "技术选型"], "created_at": 1717... }
参数:
- content(必需):要记住的完整文本
- summary(可选):简短摘要,被 FTS5 索引
- categories(可选):分类标签
search_history¶
搜索最近会话的对话历史。对消息文本执行大小写不敏感的子串匹配。
输入:
输出:
注意:limit 控制搜索的会话数(默认 10,上限 50)。匹配文本截断到 500 字符。当前会话优先搜索。
注册自定义工具¶
工具是简单的 { descriptor; handler } 二元组。Runtime.register_tool 是便利函数;底层可通过 Tool_registry.register 直接注册:
完整示例(含 tool_descriptor 字段说明)见 agent.md。
安全审计清单¶
新工具提交前自检:
- [ ]
permission字段已设置(Allow/Confirm/Deny/Role_based/Condition_based) - [ ] 长时间运行工具设置了
timeout - [ ] 资源受限工具设置了
concurrency_limit - [ ] 网络 / 写类工具通过 event bus 输出审计日志
- [ ] 文件路径类工具拒绝绝对路径与
:路径 - [ ] 危险工具走
Bash_policy(如适用) - [ ]
description包含 input 示例(让 LLM 知道怎么调用)
可行动的校验错误¶
PAR 中的工具拒绝不只是说"已拒绝"。它们解释出了什么问题以及如何修复。这是设计原则:错误信息是模型在 ReAct 循环中唯一的反馈信号。把规则写在 system prompt 里导致模型拒绝尝试工作;在拒绝点教学让模型学会正确重试。
标记词汇¶
三种标记前缀出现在工具错误信息中。工具描述引用相同的常量,因此描述和拒绝消息不会产生偏差(两者均来源于 lib/tools/tool_error.ml)。
| 标记 | 含义 |
|---|---|
[workspace] |
路径校验失败(workspace 沙箱拒绝) |
[bash-policy] |
Bash 命令被策略拒绝(Coder / ReadOnly / 自定义) |
[cancelled] |
工具调用因运行被取消而中止 |
Workspace 路径拒绝¶
所有文件工具(read / ls / find / grep / write / edit)共享相同的路径校验。存在四种不同的拒绝类型,每种都有特定的消息和元数据代码:
路径遍历(代码 workspace_parent_traversal):
[workspace] Path rejected: '../etc/passwd' contains '..' (path traversal is not allowed).
Use a clean relative path like 'src/main.ml', or an absolute path under the workspace root.
路径中含冒号(代码 workspace_colon):
[workspace] Path rejected: 'C:\Users' contains ':' (reserved/ambiguous in tool arguments).
Use a path without ':'.
绝对路径在 workspace 外(代码 workspace_absolute_outside):
[workspace] Path rejected: '/etc/passwd' is an absolute path outside the workspace (2 root(s)).
Use a relative path like 'src/main.ml' (resolved against the workspace root)
or an absolute path under a workspace root.
注意根数量:消息告诉模型有多少个 workspace 根,以便它推理哪些绝对路径是允许的。
受保护位置(代码 workspace_protected_location):
[workspace] Path rejected: '~/.ssh/id_rsa' matches a protected location.
Choose a path outside protected system areas.
两个额外的元数据代码用于缺失参数:
| 代码 | 场景 |
|---|---|
workspace_path_required |
缺少必需的 path 参数 |
pattern_required |
缺少必需的 pattern 参数(用于 find / grep) |
Bash 策略拒绝¶
当 bash 工具的策略过滤器拒绝命令时,错误会指出策略和命令:
[bash-policy] Command rejected by ReadOnly policy: ["rm", "-rf", "node_modules"].
This policy does not allow write operations. Use a read-only command or switch to a different policy.
标记 [bash-policy] 与 bash 工具描述教给模型的内容一致,因此模型知道这是策略约束,而非 bug。
Edit 工具:old_text 未找到¶
此前,edit 工具在 old_text 未在文件中找到时静默成功。这是最严重的缺陷类别:模型以为它做了更改,但什么都没发生。现在返回错误:
Edit failed: old_text was not found in 'src/main.ml'. The file may have changed
since it was last read, or the text does not match exactly (including whitespace).
Read the file first, then retry with the exact current text.
元数据代码:edit_old_text_not_found。
元数据代码参考¶
| 代码 | 工具 | 含义 |
|---|---|---|
workspace_parent_traversal |
read/ls/find/grep/write/edit | 路径包含 .. |
workspace_colon |
read/ls/find/grep/write/edit | 路径包含 : |
workspace_absolute_outside |
read/ls/find/grep/write/edit | 绝对路径在 workspace 根外 |
workspace_protected_location |
read/ls/find/grep/write/edit | 路径匹配受保护的系统区域 |
workspace_path_required |
read/ls/find/grep/write/edit | 缺少必需的 path 参数 |
pattern_required |
find/grep | 缺少必需的 pattern 参数 |
edit_old_text_not_found |
edit | 在文件中未找到 old_text 子串 |
设计原则:在拒绝点教学¶
标记和指导性消息的存在是因为错误信息是模型在 ReAct 循环中唯一接收的反馈信号。System prompt 规则("永远不要使用绝对路径")导致模型完全拒绝尝试。相比之下,像 [workspace] Path rejected: '/tmp/out.txt' is an absolute path outside the workspace (1 root(s)). Use a relative path like 'src/main.ml' ... 这样的拒绝给了模型足够的上下文来用正确形式重试。
lib/tools/tool_error.ml 中的常量是唯一的真实来源:工具描述 TEACH 标记,工具处理器 EMIT 标记。两者引用相同的常量,因此不会产生偏差。
另请参阅¶
agent.md-- Agent 定义、Runtime API、工具注册overview.md-- SDK 架构概览lib/tools/bash_safe_command.mli-- Safe_command ADT 完整 APIlib/tools/bash_policy.mli-- POLICY 接口 + 3 预置lib/tools/bash_blacklist.mli-- 31 条黑名单正则