【免费下载链接】Bindu
Bindu: The identity, communication, and payments layer for AI agents.
本文以 Bindu 仓库中的 typescript-langchain-quiz-agent 示例 为线索,完整讲解如何把一个普通的 TypeScript LangChain.js 程序,通过@bindu/sdk的bindufy()一键改造成具备 A2A(Agent-to-Agent)协议能力、DID 身份、技能声明与可选认证的 AI Agent 微服务。读完你将掌握:示例工程的安装与运行、bindufy()配置项的完整语义、底层"Python 核心 + gRPC 回调"的启动原理、技能文件(skill.yaml/SKILL.md)的声明方式,以及无认证与开启 DID 签名认证两种场景下通过 JSON-RPC 与 Agent 对话的完整方法。
示例概览:输入一段文本,返回一份 10 题测验
typescript-langchain-quiz-agent是一个极简但完整的 Agent 示例:把一段源文本交给它,它会基于 LangChain.js + OpenRouter(模型为openai/gpt-oss-120b),在一条严格约束的系统提示词下生成一份10 道单选题(MCQ)的测验——每道题 4 个选项(A/B/C/D)、只有一个正确答案、附一句解析。这个 Agent 被bindufy()包装后注册进 Bindu 核心,对外暴露一个符合 A2A 协议的 HTTP 端点,外部客户端通过 JSON-RPCmessage/send即可调用。
示例的核心价值在于两点:
- 语言无关性:Agent 的逻辑(生成测验)与 Bindu 的身份/通信/支付层完全解耦,开发者只写业务 handler,其余(DID、认证、调度、存储、HTTP 网关)全部由 Bindu 核心接管;
- 零成本上手:
bindufy()会在本地自动拉起 Bindu Python 核心(DID、x402、A2A 协议、调度与存储均在其中),开发者看到的是一个函数调用、一个终端。
快速开始:安装与运行
示例的安装与运行步骤见 示例 README,整理如下:
export OPENROUTER_API_KEY=<在 openrouter.ai/keys 获取的 API Key> cd examples/typescript-langchain-quiz-agent npm install然后运行:
npx tsx quiz-agent.ts # Agent 对外地址:http://localhost:3773两点必须注意的细节:
- 入口文件是
quiz-agent.ts,不是index.ts。package.json的scripts.start也明确写为npx tsx quiz-agent.ts(见 package.json),tsx直接执行 TypeScript 文件,无需先编译。 - 端口占用问题。SDK 会以子进程方式启动 Bindu Python 核心,命令形如
uv run bindu serve --grpc --grpc-port 4774。示例的bindufy()配置中coreAddress为localhost:4774,如果 4774 被占用,请修改quiz-agent.ts中的coreAddress字段。
依赖方面,package.json(查看完整清单)声明了@bindu/sdk(以file:../../sdks/typescript方式指向仓库内 SDK)、@langchain/openai、dotenv、yaml,开发依赖为tsx与typescript。其中@bindu/sdk是仓库内本地包,说明该示例依赖的是仓库自带的 TypeScript SDK 实现(sdks/typescript),而不是 npm 上的独立发布包。
核心代码解剖:quiz-agent.ts
完整源码见 quiz-agent.ts,它由四个部分组成:LLM 初始化、系统提示词、bindufy()配置与业务 handler。
1. LangChain LLM 初始化(OpenRouter 的 baseURL 覆盖写法)
const llm = new ChatOpenAI({ model: "openai/gpt-oss-120b", // 与 Python 版本一致 temperature: 0.3, configuration: { baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPENROUTER_API_KEY, }, });关键点是 OpenRouter 通过baseURL覆盖工作:LangChain 的ChatOpenAI默认指向 OpenAI 官方端点,此处改为https://openrouter.ai/api/v1,模型名带openai/前缀,API Key 使用 OpenRouter 的 Key。temperature: 0.3偏保守,适合需要稳定输出格式的测验生成任务。
2. 系统提示词:把输出格式"焊死"
SYSTEM_PROMPT(quiz-agent.ts#L27-L55)是整个 Agent 行为约束的核心,值得完整阅读。它规定:
- 恰好生成 10 道多选题;
- 每道题 4 个选项 A/B/C/D;
- 每题仅一个正确答案;
- 每个正确答案附带一句解析;
- 语言清晰、学术化。
同时给出标准输出模板:标题# 📝 Quiz: Knowledge Check,每题以### Question N开头、**Correct Answer:**与**Explanation:**结尾。这条提示词与技能声明文件SKILL.md中的输出格式模板(SKILL.md#L52-L70)完全一致,保证"技能声明的能力"与"实际行为"对齐。
3. bindufy():把 Agent 包装成微服务
bindufy( { author: "your.email@example.com", name: "quiz-generator-agent", description: "Educational assessment expert for MCQ generation", version: "1.0.0", deployment: { url: "http://localhost:3773", expose: true, cors_origins: ["http://localhost:5173"], }, skills: ["skills/quiz-generation"], coreAddress: "localhost:4774", capabilities: { streaming: false, push_notifications: false, }, }, async (messages: ChatMessage[]) => { ... } );各配置项含义可对照 sdks/typescript/src/types.ts 中的BinduConfig理解:
| 配置项 | 示例值 | 说明(以源码注释与实现为准) |
|---|---|---|
author | your.email@example.com | Agent 作者邮箱,必填,用于生成 DID 身份的一部分 |
name | quiz-generator-agent | Agent 名称,必填 |
description | 一句话描述 | 参与 Agent 卡片与能力协商 |
version | 1.0.0 | 默认"1.0.0" |
deployment.url | http://localhost:3773 | Agent 对外 A2A HTTP 地址 |
deployment.expose | true | 是否对外暴露 |
deployment.cors_origins | ["http://localhost:5173"] | CORS 白名单(示例为配合前端开发端口) |
skills | ["skills/quiz-generation"] | 技能文件路径,SDK 会读取目录下skill.yaml或SKILL.md并随注册请求上送核心 |
coreAddress | localhost:4774 | Bindu 核心 gRPC 地址,默认localhost:3774 |
capabilities | {streaming: false, push_notifications: false} | 能力声明,关闭流式与推送 |
kind | (未填,默认"agent") | 可选agent/team/workflow |
callbackPort | (未填,默认 0) | SDK 本地 AgentHandler gRPC 端口,0 表示自动分配 |
execution_cost | (可选) | x402 支付相关的执行成本声明 |
debug_mode/telemetry/num_history_sessions | 默认false/true/10 | 调试、遥测与历史会话数 |
4. 业务 handler:接收消息,调用 LLM,返回结果
async (messages: ChatMessage[]) => { if (!messages || messages.length === 0) { return "Error: No input provided."; } // 只取最后一条用户消息,避免盲目传递完整历史 const userInput = messages[messages.length - 1].content; const langchainMessages = [ { role: "system", content: SYSTEM_PROMPT }, { role: "user", content: userInput }, ]; const response = await llm.invoke(langchainMessages); return typeof response.content === "string" ? response.content : JSON.stringify(response.content); }handler 的签名是(messages: ChatMessage[]) => Promise<string | HandlerResponse>,其中ChatMessage仅含role与content两个字段(见 types.ts#L9-L12)。这里有两个实现要点:一是取messages[messages.length - 1]只提取最新输入而非把全部历史交给 LLM;二是对response.content做字符串/对象分支处理,兼容 LangChain 的不同返回形态。错误会被捕获并转成Error: ...文本返回,不会让调用方看到未处理的异常。
bindufy() 底层做了什么:一次调用,四步装配
从 sdks/typescript/src/index.ts 的bindufy()实现 可以完整看到"一次函数调用"背后的装配过程:
- 拉起 Bindu Python 核心:
launchCore(grpcPort, httpPort)以子进程方式启动核心。gRPC 端口从coreAddress解析(示例中 4774 即由此而来),HTTP 端口从deployment.url解析(示例中 3773)。 - 启动 AgentHandler gRPC 服务:
startAgentHandlerServer(handler, callbackPort)在本地开启一个实现AgentHandler.HandleMessages的 gRPC 服务(server.ts#L39-L60),核心收到任务后回调它,它再调用你的业务 handler。端口为 0 时自动分配。 - 加载技能并注册:
loadSkills(config.skills, callerDir)读取技能目录下的skill.yaml或SKILL.md内容,拼装成注册请求中的skills数组(index.ts#L52-L107);随后通过registerAgent()调用 gRPCRegisterAgent(client.ts#L40-L78)把配置 JSON、技能与回调地址上送核心,返回agentId、did、agentUrl。 - 心跳保活:每 30 秒调用一次
sendHeartbeat(coreAddress, agentId),并在SIGINT时清理资源退出。
核心启动本身也有三级降级策略(core-launcher.ts#L94-L111):优先bindu serve --grpc(pip 安装的 CLI),其次uv run bindu serve --grpc,最后python3 -m bindu.cli serve --grpc。启动后会轮询探测 gRPC 端口是否就绪(waitForPort),超时 30 秒。也就是说,README 中提到的"SDK 会 spawnuv run bindu serve --grpc --grpc-port 4774"是uv可用、且未安装独立 CLI 时的典型路径。
技能声明:让 Agent 的能力可被发现、可被协商
示例把生成测验的能力声明为一个技能,位于 skills/quiz-generation,由skill.yaml与SKILL.md双文件构成。
skill.yaml(完整内容)声明了技能的元数据:id: quiz-generation-v1、name: quiz-generation、version: 1.0.0、标签(education、mcq-creation、assessment 等)、输入输出模式(text/plain与application/json)、示例查询("Generate a quiz from this chapter about photosynthesis" 等)。其中assessment 部分专门服务于技能协商机制:
keywords:quiz、test、assessment、mcq、multiple-choice、generate、knowledge、check 等,用于匹配用户请求;specializations:对education(confidence_boost 0.4)、assessment(0.3)、quiz-generation(0.5)领域给出置信度加成;anti_patterns:明确声明该技能不处理实时数据、图片/视频、音频、数据库查询、文件上传、代码执行等请求,防止被错误路由;complexity_indicators:按 simple/medium/complex 三档关键词区分请求复杂度。
SKILL.md(完整内容)则是人类与 Agent 可读的能力说明,包含输出格式模板、示例查询、性能参考(如"每份测验恰好 10 题"、上下文窗口 128k tokens)以及 Integration 代码片段。注意:skill.yaml中的avg_processing_time_ms: 5000、context_window_tokens: 128000等是技能元数据中声明的参考值,实际表现取决于所选模型与网络状况,不应视为性能承诺。
另外,SDK 的loadSkills逻辑(index.ts#L70-L92)优先读取skill.yaml,解析成功则取其name与description;若 YAML 解析失败或文件不存在,则回退读取SKILL.md并将格式标记为markdown。因此技能目录中两个文件至少保留一个,SDK 即可正常工作。
与 Agent 对话(无认证模式):JSON-RPC 调用
以AUTH__ENABLED=false启动时,直接用 curl 发送 JSON-RPC 请求即可。以下是示例 README 提供的完整请求(见 README.md#L24-L30):
curl -sS http://localhost:3773/ \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"message/send","id":"00000000-0000-0000-0000-000000000004","params":{"message":{"role":"user","parts":[{"kind":"text","text":"Generate a quiz from this text: The mitochondrion is the powerhouse of the cell."}],"kind":"message","messageId":"00000000-0000-0000-0000-000000000001","contextId":"00000000-0000-0000-0000-000000000002","taskId":"00000000-0000-0000-0000-000000000003"},"configuration":{"acceptedOutputModes":["application/json"]}}}'请求结构要点:
- 顶层为 JSON-RPC 2.0 格式:
jsonrpc: "2.0"、id、method: "message/send"、params; params.message中parts数组承载具体内容(kind: "text"的文本),并配套messageId、contextId、taskId三个 UUID;params.configuration.acceptedOutputModes: ["application/json"]声明可接受的输出模式。
发送后通过tasks/get拉取任务结果——产物是一份 markdown 格式的 10 题测验,含答案键(answer key)与每题解析。也就是说,完整链路是"发送消息 → 核心调度 → 回调你的 handler → LLM 生成 → 产物落库 → 客户端取回"。
开启认证:DID 签名与四个 Header 四道闸门
当AUTH__ENABLED=true时,每次调用必须同时满足两个条件:携带 Hydra 签发的短时 bearer token(证明你有权限),并用 Agent 的 DID 私钥对请求体签名(证明请求确实是你发出的)。任一缺失,Agent 都会以 JSON-RPC-32009拒绝。完整流程见 docs/AUTH.md,核心要点如下。
每个请求携带的四个 Header
Authorization: Bearer <access_token> ← 来自 Hydra,约 1 小时过期 X-DID: did:bindu:<author>:<name>:<id> ← 你的身份 X-DID-Timestamp: <unix-seconds> ← 与服务器时钟相差须在 300 秒内 X-DID-Signature: <base58 Ed25519 sig> ← 对 {body, did, timestamp} 的签名服务端四道校验闸门
| 闸门 | 校验内容 | 失败表现 |
|---|---|---|
| 1 | bearer token 存在且在 Hydra 中有效 | HTTP 401 + JSON-RPC-32009 "Authentication is required..." |
| 2 | X-DID与 token 的client_id一致 | HTTP 403 +{"error":"Invalid DID signature","details":{"reason":"did_mismatch"}} |
| 3 | 该 DID 的公钥已在 Hydra 客户端元数据中注册 | HTTP 403 +details.reason = public_key_unavailable |
| 4 | 时间戳在 300 秒内且签名验证通过 | HTTP 403 +details.reason = invalid_signature(时钟偏差与坏签名被合并处理) |
四道闸门按序执行,第一处失败即停止;全部通过后你的 handler 才会运行。
每次请求的四个步骤
- 铸 token:向 Hydra 的
/oauth2/token以client_credentials换取access_token(expires_in约 3599 秒),在内存中缓存、到期前约 60 秒刷新; - 构造 JSON-RPC body:序列化一次并保持字节不变——签名用的字节必须等于发送的字节;
- 签名:对第二个 JSON 对象
{"body": <body字符串>, "did": <did>, "timestamp": <ts>}做sort_keys=True的序列化,再用 Ed25519 私钥签名并 base58 编码; - 发送:带上上述四个 Header。
跨语言头号坑:JavaScript 的JSON.stringify在冒号和逗号后不加空格,而 Python 的json.dumps默认带空格。签名载荷必须采用同一形态,否则签名无法验证、报invalid_signature。仓库提供了 canonical fixture(docs/AUTH.md#L230-L253)用于跨语言校验:种子为 32 个零字节、DID 为did:bindu:test、body 为{"test": "value"}、时间戳为1000,签名载荷为{"body": "{\"test\": \"value\"}", "did": "did:bindu:test", "timestamp": 1000},期望的 base58 签名为3SfU4VPTHLbzZzCn17ZqU6y2tnzHQbdo2nnXQr6XZXk34XgyzwSKRrCYEWRmmGXrV39mdkyhTsy5oasfTpNuqyM2。若结果不一致,通常是缺了空格、键未排序或 base58 字母表错误(Bindu 使用 Bitcoin 字母表)。
若不想手写签名逻辑,仓库提供了现成实现供参考:gateway/src/bindu/identity/local.ts(TypeScript 参考实现)、docs/postman-did-signing.js(Postman 预请求脚本)、docs/AUTHENTICATION.md(bearer token 侧完整讲解)与 docs/DID.md(签名侧完整讲解)。
常见问题速查
| 现象 | 最可能原因 | 修复 |
|---|---|---|
HTTP 401 +-32009 | token 缺失/失效 | 重新铸 token,以Authorization: Bearer …携带 |
HTTP 403did_mismatch | X-DID与 token 的client_id不一致 | 用与X-DID相同的 DID 铸 token |
HTTP 403public_key_unavailable | Hydra 客户端元数据缺public_key | 用GET /admin/clients/<did>检查注册 |
HTTP 403invalid_signature | 时钟偏差 >300s、重放、body 字节漂移、排序/空格不一致、用了错误的种子 | 每次请求重新签名;对要发送的精确字节签名;对照 canonical fixture 校验 |
HTTP 400-32700 | body 结构错误(如缺params.configuration),发生在认证之前 | 先修 body,可先对未认证的 peer 验证 |
继续深入:相关仓库资源
- 示例本体:examples/typescript-langchain-quiz-agent(README、源码、技能、package.json、tsconfig.json)
- SDK 实现:sdks/typescript/src/index.ts、sdks/typescript/src/core-launcher.ts、sdks/typescript/src/client.ts、sdks/typescript/src/server.ts、sdks/typescript/src/types.ts
- 认证文档:docs/AUTH.md、docs/AUTHENTICATION.md、docs/DID.md
- 协议定义:proto/agent_handler.proto
- 同类示例对照:Python 版 LangChain 示例见 examples/beginner、TypeScript OpenAI 直连版见 examples/typescript-openai-agent、TypeScript LangChain 研究 Agent 见 examples/typescript-langchain-agent
从一份简单的"文本 → 测验"业务逻辑出发,本示例完整展示了 Bindu 的 Agent 化范式:bindufy()负责装配,skills/负责能力声明,A2A + JSON-RPC 负责对外通信,DID + Hydra 负责身份与认证。你可以把它当作模板,替换 handler 与技能声明,即可快速产出自己的 TypeScript Agent。
【免费下载链接】Bindu
Bindu: The identity, communication, and payments layer for AI agents.
相关推荐
@bindu/sdk TypeScript SDK 实战指南:用 bindufy() 一键将任意 TypeScript Agent 升级为 A2A 微服务
@bindu/sdk TypeScript SDK 实战指南:用 bindufy 一键将任意 TypeScript Agent 升级为 A2A 微服务 @bin
为 Bindu 构建新语言 SDK:从 proto 契约到 `bindufy()` 的完整实现指南
为 Bindu 构建新语言 SDK:从 proto 契约到 bindufy 的完整实现指南 Bindu 通过 gRPC 实现了语言无关的 Agent 微服务化:
使用 Bindu TypeScript SDK 将 LangChain.js 研究 Agent 一键封装为带 DID 认证的 A2A 微服务
使用 Bindu TypeScript SDK 将 LangChain.js 研究 Agent 一键封装为带 DID 认证的 A2A 微服务 本篇技术指南以仓库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考