datasette/datasette-agent

GitHub: datasette/datasette-agent

Datasette-agent 是一个由大语言模型驱动的 Datasette 插件,提供基于聊天的数据库交互、SQL 生成与保存、后台数据探索及可扩展的工具注册机制。

Stars: 107 | Forks: 6

# datasette-agent [![PyPI](https://img.shields.io/pypi/v/datasette-agent.svg)](https://pypi.org/project/datasette-agent/) [![更新日志](https://img.shields.io/github/v/release/datasette/datasette-agent?include_prereleases&label=changelog)](https://github.com/datasette/datasette-agent/releases) [![测试](https://static.pigsec.cn/wp-content/uploads/repos/cas/ce/ce733292a922c08274cf5a2096f8fa4cf01023bfa51a36ef6beecaaef371a9d9.svg)](https://github.com/datasette/datasette-agent/actions/workflows/test.yml) [![许可证](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/datasette/datasette-agent/blob/main/LICENSE) 一个由 LLM 驱动的 Datasette 代理助手 请参阅 [Datasette Agent:一个可扩展的 Datasette AI 助手](https://datasette.io/blog/2026/datasette-agent/) 了解有关此项目的更多信息,包括在你自己的机器上运行它的提示。 ## 安装 在与 Datasette 相同的环境中安装此插件。 ``` datasette install datasette-agent ``` ## 用法 访问 `/-/agent` 以开始与聊天助手的对话。 该代理使用 [datasette-llm](https://github.com/datasette/datasette-llm) 来调用语言模型。在访问 `/-/agent` 之前为其配置默认模型,例如在 `datasette.yml` 中: ``` plugins: datasette-llm: default_model: gpt-5.4-mini ``` 出现在数据库和表格操作菜单中的“使用 AI 代理探索”条目会启动一个后台代理,它会探索选定的数据库或表格并编写报告。报告位于 `/-/agent/explore/` 下。 访问 `/-/agent/background` 可直接启动后台代理。每个代理都会被分配一个目标,并在无需进一步输入的情况下运行以实现该目标。列表中包含一个停止按钮,用于取消仍在运行的代理。 ### 保存查询 该代理具有一个内置的 `save_query` 工具,可将它编写的 SQL 保存为 [Datasette 存储查询](https://docs.datasette.io/en/latest/sql_queries.html)。查询可以是只读或写入 SQL - Datasette 会对其进行分析以决定类型,并且命名的 `:parameters` 将成为保存查询页面上的表单字段。 保存始终需要人工批准:代理会向你展示完整的 SQL 以及建议的名称、数据库和可见性,在你点击“是”之前不会存储任何内容。验证和持久化通过 Datasette 自身的 `/-/queries/analyze` 和 `/-/queries/store` 端点作为请求的执行者运行,因此执行者需要对目标数据库具有 `execute-sql` 和 `store-query` 权限(对于写入查询,还需要相关的行权限)——这与查询创建 Web UI 的规则相同。保存的查询默认为私有。 ### 执行写入 SQL 代理还具有一个内置的 `execute_write_sql` 工具,可以针对可变数据库运行一个或多个有序的写入 SQL 语句。它会首先分析每条语句,并在运行任何内容之前在聊天中请求用户明确批准。 批准提示会显示 SQL、参数、所需权限和破坏性操作警告。执行通过 Datasette 自身的 `/-/execute-write` 端点作为请求的执行者运行,因此执行者需要对目标数据库具有 `execute-write-sql` 权限,以及执行操作所需的 Datasette 写入权限。语句按顺序运行;如果其中一条失败,则跳过后续语句,且不会回滚之前已成功的语句。请使用 `sql_query` 进行只读 SQL 操作。 ### 权限 此插件注册了三个独立的权限: - `datasette-agent` —— 在 `/-/agent` 下使用聊天助手所必需。 - `datasette-agent-explore` —— 在数据库/表格操作菜单中查看“使用 AI 代理探索”条目以及使用 `/-/agent/explore/` 下的探索器路由所必需。 - `datasette-agent-background` —— 在聊天中使用 `spawn_background_agent` 和 `check_background_agent` 工具,以及访问 `/-/agent/background` 页面和 `/-/agent/api/background/*` 端点所必需。后台代理端点需要同时具备 `datasette-agent` 和 `datasette-agent-background` 权限。 这三个权限是独立的:执行者可以持有其中的任意组合。`--root` 用户拥有全部权限。 ## 从插件注册其他工具 其他 Datasette 插件可以使用 `register_agent_tools` 插件钩子为代理注册其他工具。 ### 定义工具 创建一个实现 `register_agent_tools` 钩子的 Datasette 插件,返回一个 `AgentTool` 实例列表: ``` from datasette import hookimpl from datasette_agent.tools import AgentTool @hookimpl def register_agent_tools(datasette): return [ AgentTool( name="my_tool", description="Description of what this tool does, used by the LLM to decide when to call it.", input_schema={ "type": "object", "properties": { "query": { "type": "string", "description": "The query to run", }, "style": { "type": "string", "enum": ["brief", "detailed"], "description": "Output style", }, }, "required": ["query"], }, fn=my_tool_handler, # Optional: name a Datasette permission action that gates this tool. # required_permission="myplugin-write", ), ] ``` ### 使用权限控制工具 `AgentTool` 接受一个可选的 `required_permission: str | None` 字段。设置后,代理框架在将工具列表发送给 LLM 之前,会为当前执行者调用 `datasette.allowed(action=required_permission, actor=actor)`。如果执行者缺少该权限,该工具将从列表中过滤掉——模型永远不会看到它,也无法调用它。你的 `fn` 不需要处理任何运行时的“权限拒绝”分支。 你的插件负责通过 Datasette 的 `register_actions` 插件钩子注册操作: ``` from datasette import hookimpl from datasette.permissions import Action from datasette_agent.tools import AgentTool @hookimpl def register_actions(): return [ Action( name="myplugin-write", description="Allow my plugin's write tools", ), ] @hookimpl def register_agent_tools(datasette): return [ AgentTool( name="my_write_tool", description="Writes things", input_schema={"type": "object", "properties": {}}, fn=my_write_handler, required_permission="myplugin-write", ), ] ``` 有关工作示例,请参阅此插件自身的 `spawn_background_agent` 和 `check_background_agent` 工具,它们使用 `required_permission="datasette-agent-background"`。 ### 工具处理函数 每个工具的 `fn` 必须是一个异步函数,它接受 `datasette` 和 `actor` 作为关键字参数,以及 `input_schema` 中定义的任何参数。它必须返回一个 JSON 字符串: ``` import json async def my_tool_handler(datasette, actor, query, style=None): # Do work here... return json.dumps({ "result": "Tool output that the LLM will see", }) ``` 要在聊天 UI 中内联呈现丰富的 HTML,请在返回的 JSON 中包含一个 `_html` 键。在将工具结果发送给 LLM 之前,任何名称以 `_` 开头的顶级键都会被删除,因此 HTML 会显示给用户,但不会传递回模型: ``` return json.dumps({ "_html": '
Rich content here
', "summary": "Widget rendered successfully", }) ``` ### 从工具向用户提问 工具可以在执行过程中暂停并向人类用户提问。在你的处理程序上声明一个 `context` 参数并调用 `await context.ask_user(...)`: ``` import json async def edit_files(datasette, actor, context, path): ok = await context.ask_user( "Is it OK to edit files in {}?".format(path) ) if not ok: return json.dumps({"cancelled": True}) mode = await context.ask_user( "How should I apply this?", options=["dry-run", "apply"] ) note = await context.ask_user("Any notes?", free_text=True) # ... do the work ... return json.dumps({"edited": path, "mode": mode, "note": note}) ``` 支持三种类型的问题: - `await context.ask_user("Approve?")` - 是/否,返回一个 `bool` - `await context.ask_user("Which?", options=["a", "b"])` - 多项选择,返回所选的 `str` - `await context.ask_user("Describe it", free_text=True)` - 自由格式,返回一个 `str` 传入 `html=` 以在问题上方显示受信任的 HTML - 使用此选项可以向用户准确展示他们正在批准的内容,例如 `
` 标签内查询的完整 SQL。自行转义任何插值内容(例如使用 `html.escape()`);该字符串将按原样呈现在聊天 UI 中。

传入 `text=` 可以为终端上下文(例如 `datasette agent chat`)提供纯文本版本。如果提供了 `text=` 而没有 `html=`,Web 聊天也会显示该文本,并在 `
` 块内进行 HTML 转义。CLI 在可用时显示 `text=`;如果仅提供了 `html=`,它将直接打印该 HTML。

当 `ask_user()` 尚未有答案时,它会暂停代理回合:问题在聊天 UI 中呈现为表单,并持久化到内部数据库中,因此即使服务器重启它也能保留——重新加载对话页面时表单会重新呈现。一旦用户回答,工具函数将**从头开始重新执行**;先前回答的问题会立即返回其存储的答案,并且执行会越过 `ask_user()` 调用继续进行。由于这种重播模型,你应该在执行副作用*之前*调用 `ask_user()`。

`context` 对象还公开了 `context.actor`、`context.conversation_id`、`context.tool_name`、`context.arguments` 和 `context.tool_call_id`。

在没有人关注的场景下——例如后台代理,或者终端输入不可用的 CLI 聊天——`ask_user()` 会引发 `QuestionsNotSupported` 异常,该异常作为工具错误呈现给模型,以便它可以在没有输入的情况下继续执行。仅声明 `datasette` 和 `actor` 的工具不受此影响。

### 通过工具在用户的浏览器中运行工作

某些工具需要代码在用户的浏览器中运行——截取呈现页面的屏幕截图、针对活动的 DOM 执行 JavaScript、测量布局。**浏览器任务**是实现此目的的原语:工具向用户的浏览器交付一个工作单元,代理回合暂停,当浏览器回传结果时工具接收该结果——即使这需要几分钟、页面重新加载或服务器重启。

在你的处理程序上声明一个 `context` 参数并调用 `await context.browser_task(...)`:


```
import json





async def measure_page(datasette, actor, context, url):

    outcome = await context.browser_task(

        html=BROWSER_HARNESS_HTML,

        payload={"url": url, "token": make_capability_token(url)},

        label="Measuring {} in your browser".format(url),

        timeout_ms=30_000,

    )

    if not outcome["ok"]:

        return json.dumps({"error": outcome["error"], "outcome": outcome["outcome"]})

    return json.dumps({"measurements": outcome["result"]})
```


四个参数:

- `html` - **受信任的、服务器端编写的 HTML**,呈现到聊天页面中。与问题的 `html=` 不同,它的 `

""" ``` 你的 `html` 中任何位置的 `__DATASETTE_TASK_ID__` 字面量字符串都会在呈现时替换为真实的任务 ID。(经典非模块脚本也可以通过 `document.currentScript.closest("[data-task-id]").dataset.taskId` 在结构上找到它——模块脚本应使用占位符,因为 `document.currentScript` 在其中为 `null`。) API: - `claimTask(taskId)` - 原子性地声明任务,并**每个任务仅一次**解析为 `{ok: true, payload, timeoutMs}`。任何后续或并发的声明——重复的标签页、重新呈现对话历史的重新加载页面——都会解析为 `{ok: false, state}`,调用者应停止操作。正是这个声明门控使得带有脚本的 HTML 能够安全地重新呈现:无论标记出现多少次,执行最多只发生一次。 - `completeTask(taskId, envelope)` - 回传 `{ok, result?, error?}`。首次写入有效。暂停的回合立即恢复,其事件流会在同一连接上流入记录。 - `cancelTask(taskId)` - 相当于用户点击“跳过”。 一旦任务离开挂起状态,其 HTML 就会被拆除并且永远不会再次呈现——重新加载对话会显示一个无操作的单行记录。保持任务 HTML 独立:呈现、执行、完成、拆除。需要进行多轮浏览器工作的工具应该按顺序发出多个 `browser_task()` 调用。 #### 能力检测和测试 `context.supports_browser_tasks` 在 Web 聊天中为 `True`,在后台代理和 CLI 聊天中为 `False`,在这些情况下调用 `browser_task()` 会引发 `BrowserTasksNotSupported` 异常——像 `QuestionsNotSupported` 一样作为工具错误呈现给模型。如果你的工具有无浏览器的后备方案,请先检查此标志。 对于测试,请在构造 `ToolContext` 时传入 `browser_task_callback`(或通过 `make_llm_tools()`):它接收 `{tool_name, html, payload, label, timeout_ms}` 并直接同步或作为协程返回信封,而无需接触数据库或任何浏览器。以这种方式返回的信封会像真实完成情况一样被规范化为 `outcome: "completed"`,因此你的工具无法区分执行器。 ## 从工具呈现自定义 HTML 工具插件可以通过返回带有 `_html` 键的 JSON 对象,在聊天 UI 中呈现丰富的 HTML。HTML 直接在对话中呈现。其余键将作为工具结果返回给 LLM,任何名称以 `_` 开头的键都会先被删除。 工具实现示例: ``` import json async def _render_widget(datasette, actor, database, sql): html = ( '\n' '\n' f'\n' '' ) return json.dumps({ "_html": html, "database": database, "sql": sql, "summary": "Widget rendered successfully", }) ``` `_html` 的值作为原始 HTML 插入聊天中,因此它可以包含自定义元素、脚本和样式。其他键(在此示例中为 `database`、`sql` 和 `summary`)是 LLM 作为工具结果接收的内容。 如果你的插件运行 SQL 并以 HTML 显示结果,请使用 Datasette Agent 内置的 SQL 链接样式在的输出下方添加一个链接: ``` ``` ### 示例插件 - [datasette-agent-charts](https://github.com/datasette/datasette-agent-charts) - 使用 Observable Plot 从 SQL 查询结果呈现图表 - [datasette-agent-openai-imagegen](https://github.com/datasette/datasette-agent-openai-imagegen) - 使用 OpenAI 的图像生成 API 生成图像 ## CLI 命令 ### 交互式聊天 从命令行启动与代理的交互式聊天会话: ``` datasette agent chat mydata.db ``` 你可以传递多个数据库文件,使用 `:memory:` 作为内存数据库,指定模型,或发送单个提示: ``` datasette agent chat mydata.db -m gpt-5.4-mini datasette agent chat mydata.db -m gpt-5.4-mini -p "List all tables" ``` 选项: - `-p`, `--prompt` —— 发送单个提示并退出(非交互模式) - `-m`, `--model` —— 要使用的 LLM 模型 - `--root` —— 以 Datasette root 执行者运行,允许所有权限 - `--yes` —— 自动批准是/否确认提示 - `--unsafe` —— 等同于 `--root --yes` 默认情况下,CLI 以名为 `cli` 的执行者身份运行,并遵守 Datasette 权限。请求批准的工具会显示终端提示;例如,`execute_write_sql` 在请求确认之前会显示 SQL、参数、权限和警告的纯文本版本。使用 `--yes` 跳过是/否确认,使用 `--root` 以 root 权限运行,或使用 `--unsafe` 同时执行两者。 ### 列出可用工具 要查看所有注册的代理工具(按插件分组): ``` datasette agent tools ``` 输出包括: ``` agent: list_databases_and_tables List all available databases and their tables describe_table Get column names, types, and foreign keys for a table sql_query Execute a read-only SQL query against a database execute_write_sql Execute ordered write SQL statements against a database ``` 添加 `--json` 获取机器可读的输出: ``` datasette agent tools --json ``` ## 开发 要在本地设置此插件,首先检入代码。运行测试,如下所示: ``` cd datasette-agent uv run pytest ``` 要运行具有持久内部数据库并使用 GPT-5.5 作为模型的开发服务器: ``` uv run datasette --internal internal.db \ --root --secret 1 \ -s plugins.datasette-llm.default_model gpt-5.5 ``` 向该命令添加额外的数据库文件,以使代理能够查询它们。 ## 致谢 此插件采用了 Damian Tarnawski 的 [streaming-markdown](https://github.com/thetarnav/streaming-markdown),基于 MIT 许可。
标签:AI助手, C2, Datasette, DLL 劫持, 大语言模型, 数据库工具, 数据探索, 逆向工具