news 2026/9/25 4:02:09

使用 TypeScript + LangChain.js + Bindu 构建 10 题 MCQ 测验 Agent:从 bindufy 配置到 JSON-RPC 调用完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 TypeScript + LangChain.js + Bindu 构建 10 题 MCQ 测验 Agent:从 bindufy 配置到 JSON-RPC 调用完整实战

【免费下载链接】Bindu

Bindu: The identity, communication, and payments layer for AI agents.

项目地址:https://gitcode.com/gh_mirrors/bin/Bindu
点击查看免费下载

本文以 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

两点必须注意的细节:

  1. 入口文件是quiz-agent.ts,不是index.ts。package.json的scripts.start也明确写为npx tsx quiz-agent.ts(见 package.json),tsx直接执行 TypeScript 文件,无需先编译。
  2. 端口占用问题。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 行为约束的核心,值得完整阅读。它规定:

  1. 恰好生成 10 道多选题;
  2. 每道题 4 个选项 A/B/C/D;
  3. 每题仅一个正确答案;
  4. 每个正确答案附带一句解析;
  5. 语言清晰、学术化。

同时给出标准输出模板:标题# 📝 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理解:

配置项示例值说明(以源码注释与实现为准)
authoryour.email@example.comAgent 作者邮箱,必填,用于生成 DID 身份的一部分
namequiz-generator-agentAgent 名称,必填
description一句话描述参与 Agent 卡片与能力协商
version1.0.0默认"1.0.0"
deployment.urlhttp://localhost:3773Agent 对外 A2A HTTP 地址
deployment.exposetrue是否对外暴露
deployment.cors_origins["http://localhost:5173"]CORS 白名单(示例为配合前端开发端口)
skills["skills/quiz-generation"]技能文件路径,SDK 会读取目录下skill.yaml或SKILL.md并随注册请求上送核心
coreAddresslocalhost:4774Bindu 核心 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()实现 可以完整看到"一次函数调用"背后的装配过程:

  1. 拉起 Bindu Python 核心:launchCore(grpcPort, httpPort)以子进程方式启动核心。gRPC 端口从coreAddress解析(示例中 4774 即由此而来),HTTP 端口从deployment.url解析(示例中 3773)。
  2. 启动 AgentHandler gRPC 服务:startAgentHandlerServer(handler, callbackPort)在本地开启一个实现AgentHandler.HandleMessages的 gRPC 服务(server.ts#L39-L60),核心收到任务后回调它,它再调用你的业务 handler。端口为 0 时自动分配。
  3. 加载技能并注册:loadSkills(config.skills, callerDir)读取技能目录下的skill.yaml或SKILL.md内容,拼装成注册请求中的skills数组(index.ts#L52-L107);随后通过registerAgent()调用 gRPCRegisterAgent(client.ts#L40-L78)把配置 JSON、技能与回调地址上送核心,返回agentId、did、agentUrl。
  4. 心跳保活:每 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} 的签名

服务端四道校验闸门

闸门校验内容失败表现
1bearer token 存在且在 Hydra 中有效HTTP 401 + JSON-RPC-32009 "Authentication is required..."
2X-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 才会运行。

每次请求的四个步骤

  1. 铸 token:向 Hydra 的/oauth2/token以client_credentials换取access_token(expires_in约 3599 秒),在内存中缓存、到期前约 60 秒刷新;
  2. 构造 JSON-RPC body:序列化一次并保持字节不变——签名用的字节必须等于发送的字节;
  3. 签名:对第二个 JSON 对象{"body": <body字符串>, "did": <did>, "timestamp": <ts>}做sort_keys=True的序列化,再用 Ed25519 私钥签名并 base58 编码;
  4. 发送:带上上述四个 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 +-32009token 缺失/失效重新铸 token,以Authorization: Bearer …携带
HTTP 403did_mismatchX-DID与 token 的client_id不一致用与X-DID相同的 DID 铸 token
HTTP 403public_key_unavailableHydra 客户端元数据缺public_key用GET /admin/clients/<did>检查注册
HTTP 403invalid_signature时钟偏差 >300s、重放、body 字节漂移、排序/空格不一致、用了错误的种子每次请求重新签名;对要发送的精确字节签名;对照 canonical fixture 校验
HTTP 400-32700body 结构错误(如缺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.

项目地址:https://gitcode.com/gh_mirrors/bin/Bindu
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 4:02:06

Windows下Nginx常用命令详解:启动、停止、重载与排查

只要你在 Windows 上碰过 Nginx&#xff0c;大概率经历过这样的瞬间&#xff1a;双击 nginx.exe 之后窗口一闪而过&#xff0c;心里完全没底&#xff0c;不知道进程到底起来没有&#xff1b;好不容易把配置改了&#xff0c;又不知道该执行哪条常用命令才能让改动生效&#xff1…

作者头像 李华
网站建设 2026/9/25 4:00:12

华旭金卡身份证阅读器JS集成实战:Node桥接+WebUSB绕过方案

简介&#xff1a;本资源是一套面向Web开发者与前端工程师的华旭金卡身份证阅读器JS集成实战方案&#xff0c;专为需在网页端快速接入二代身份证读取功能的项目场景设计&#xff0c;解决浏览器环境下调用硬件设备的核心技术难点。压缩包共31个文件&#xff0c;含6个DLL驱动库&am…

作者头像 李华
网站建设 2026/9/25 3:59:21

ESP32 -O2优化崩溃排查指南:volatile、内存对齐与竞态实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 3:58:32

C语言练手项目:手写Linux终端动态进度条,搞懂缓冲区与回车换行

经常有刚入坑 Linux 的朋友跑来问我&#xff1a;C 语言基础语法学完了&#xff0c;vim 也会开了&#xff0c;gcc 也会用了&#xff0c;下一步做点什么练手最有价值&#xff1f;我反反复复推荐的都是同一个项目&#xff1a;写一个 Linux 终端下的动态进度条。别急着翻白眼。这玩…

作者头像 李华
网站建设 2026/9/25 3:58:24

Ventoy多重启动U盘制作:NTFS支持与Secure Boot兼容实战

简介&#xff1a;Ventoy 1.1.11 Windows版是一款面向系统运维人员、IT支持工程师及装机爱好者的开源U盘启动盘制作工具&#xff0c;彻底解决传统方式需反复格式化U盘、逐个制作启动盘的低效问题。用户仅需将多个ISO镜像&#xff08;如微PE、大白菜、Ubuntu、CentOS、Windows Se…

作者头像 李华