跳转至

工具 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

评估算术表达式,支持 +-*/ 和括号。

输入

{ "expression": "2 + 3 * 4" }

输出(数字,无理数保留为 float):

14

注意:实现是手写词法 + 递归下降(不依赖外部 eval 库),只接受数字与四则运算符;空格被忽略但不支持负数前缀"-1+2" 解析为 1 + 2,需要写为 "0-1+2")。


utility 类

get_time

返回当前 UTC 时间(ISO 8601 格式)。

输入{}(无参数)

输出

"2026-06-04T12:34:56Z"

echo

回显输入文本(用于调试 / agent 间通信)。

输入

{ "text": "hello" }

输出

"hello"

generate_uuid

生成随机 UUID v4。

输入{}

输出

"550e8400-e29b-41d4-a716-446655440000"

hash_text

对文本计算哈希,支持 md5 / sha1 / sha256(默认 sha256,大小写不敏感)。

输入

{ "text": "hello", "algorithm": "sha1" }

输出

{ "hash": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", "algorithm": "sha1" }

安全提示:MD5 / SHA1 已不抗碰撞攻击,仅用于非安全场景(去重、缓存键)。

generate_password

生成随机密码,长度 4-128(输入范围外自动夹紧),符号默认包含。

输入

{ "length": 24, "include_symbols": true }

输出

"a8K!mZ3xQ9wL7nV2"

注意:使用 Random.State.make_self_init,不保证密码学强度。生产场景请用专业库。

string_stats

统计字符数、词数、行数。词以空格切分。

输入

{ "text": "hello world\nfoo bar" }

输出

{ "characters": 22, "words": 4, "lines": 2 }

json_format

校验 + 美化 JSON 字符串。

输入

{ "json": "{\"a\":1,\"b\":2}" }

输出

"{\n  \"a\": 1;\n  \"b\": 2\n}"

注意:解析失败返回 Error (Invalid_input "Invalid JSON: ...")retryable = false

convert_temperature

C / F / K 互转。

输入

{ "value": 100, "from": "C", "to": "F" }

输出

{ "value": 212, "unit": "F", "original_value": 100, "original_unit": "C" }

url_encode

URL 编码或解码。默认 encode;decode: true 时反向操作。解码时 + 视为空格(form-encoding 兼容)。

输入

{ "text": "hello world", "decode": false }

输出

"hello%20world"


web 类

三个网络工具共享 validate_url(仅允许 http://https://)+ max_download_size = 10MB + 系统 CA 证书 + TLS 主机名校验。

fetch_url

HTTP GET 原始文本。

输入

{ "url": "https://example.com", "max_length": 50000 }

输出

{
  "url": "https://example.com",
  "status": 200,
  "content": "...",
  "content_length": 1234,
  "truncated": false
}

read_webpage

fetch + HTML 解析,剥离 <script> / <style> / <noscript>,返回纯文本。

输入

{ "url": "https://example.com", "max_length": 10000 }

输出

{
  "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

DuckDuckGo lite 搜索。

输入

{ "query": "ocaml lwt tutorial", "max_results": 5 }

输出

{
  "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

读文件内容,可指定行偏移与行数上限。

输入

{ "path": "src/main.ml", "offset": 0, "limit": 100 }

输出(带行号,类似 cat -n):

"   1\topen Par\n   2\t...\n"

限制: - 文件大小 ≤ 10MB(超过返回 Error (Invalid_input "File too large")) - 路径必须相对于 workspace 根,或为 workspace 根下的绝对路径

ls

列目录内容。

输入

{ "path": "." }

输出

{
  "path": ".",
  "entries": [
    { "name": "src", "type": "dir", "size": null, "modified": 1717... },
    { "name": "README.md", "type": "file", "size": 4321, "modified": 1717... }
  ]
}

注意:子项按文件名升序;typedir / file / link / other / unknown;非目录路径返回 Error (Invalid_input "Not a directory")

find

glob 匹配文件名(** 跨目录,* 跨组件但不含 /)。

输入

{ "pattern": "**/*.ml", "path": "." }

输出

["src/main.ml", "src/agent.ml", "test/test.ml"]

grep

正则搜索文件内容。path 目录下递归匹配,glob 过滤文件名。

输入

{ "pattern": "TODO", "path": "lib", "glob": "*.ml", "context_lines": 2 }

输出(每条 path:line:match):

["lib/runtime.ml:142:(* TODO: handle ... *)"]

注意:正则语法为 OCaml Str(POSIX 扩展正则)。当前实现忽略 context_lines(保留参数供未来扩展)。


fs (write) 类

write

写文件,可选 create_dirs 自动 mkdir -p

输入

{ "path": "out/result.txt", "content": "hello", "create_dirs": true }

输出

"Wrote 5 bytes to out/result.txt"

注意:覆盖已存在文件;目录不存在时若 create_dirsfalse 则报错。

edit

批量精确字符串替换。重叠区间被拒(避免编辑顺序歧义)。

输入

{
  "path": "src/main.ml",
  "edits": [
    { "old": "let x = 1", "new": "let x = 2" },
    { "old": "foo", "new": "bar" }
  ]
}

输出

"Applied 2 edit(s) to src/main.ml"

注意: - 每个 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 命令,配合三层防御:

  1. 类型层argv 强制为 string list没有 Exec_raw_shell 构造器(shell 注入在类型层不可表示)
  2. 策略层POLICY.filter 在运行时校验,可基于用户场景选择 Coder / ReadOnly / ReadOnlyNoNet
  3. 黑名单层Bash_blacklist 31 条正则兜底(rm -rf /dd of=/dev/sda、fork bomb 等)

输入

{
  "argv": ["ls", "-la"],
  "cwd": "src",
  "timeout": 30
}

字段说明: - argv(必需):参数数组,不是 shell 字符串 - cwd:工作目录(相对于 workspace 根,或 workspace 根下的绝对路径)。默认:"." - timeout:最大执行秒数,默认 30,硬上限 600

输出

{
  "stdout": "...",
  "stderr": "...",
  "exit_code": 0,
  "duration": 0.123,
  "truncated": false
}

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_mgrEio.Stdenv.process_mgr env,用于 Eio.Process.spawn - clockEio.Stdenv.clock env,用于 timeout 强制(无 clock 则超时失效) - fsEio.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 排序。

输入

{ "query": "ocaml concurrency", "limit": 5 }

输出

{
  "results": [
    { "id": "...", "content": "...", "summary": "...", "scope": "...", "categories": [...], "created_at": 1717... }
  ],
  "count": 2
}

注意limit 默认 5,上限 50。scope 从当前 invoke_context.session_id 读取。

remember_memory

存储新记忆以供将来检索。记忆按当前会话分区。

输入

{ "content": "项目使用 OCaml 5.4 和 Eio 实现并发", "summary": "OCaml 并发技术栈", "categories": ["架构", "技术选型"] }

输出(创建的 memory_object):

{ "id": "550e8400-...", "content": "...", "summary": "...", "scope": "...", "categories": ["架构", "技术选型"], "created_at": 1717... }

参数: - content(必需):要记住的完整文本 - summary(可选):简短摘要,被 FTS5 索引 - categories(可选):分类标签

search_history

搜索最近会话的对话历史。对消息文本执行大小写不敏感的子串匹配。

输入

{ "query": "Eio concurrency", "limit": 10 }

输出

{
  "results": [
    { "session_id": "...", "role": "user", "content": "跟我说说 Eio..." }
  ],
  "count": 3
}

注意limit 控制搜索的会话数(默认 10,上限 50)。匹配文本截断到 500 字符。当前会话优先搜索。


注册自定义工具

工具是简单的 { descriptor; handler } 二元组。Runtime.register_tool 是便利函数;底层可通过 Tool_registry.register 直接注册:

let my_tool = { descriptor; handler } in
Tool_registry.register rt.tool_registry descriptor handler

完整示例(含 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 完整 API
  • lib/tools/bash_policy.mli -- POLICY 接口 + 3 预置
  • lib/tools/bash_blacklist.mli -- 31 条黑名单正则