第13章:AI Agent 生态系统与工具集成
前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
🎯 本章学习目标
- 体系化掌握 Agent 工具生态接入:搜索、日历、邮件、工单、文档、数据仓库等
- 设计安全合规的工具适配层:鉴权与密钥管理、限流与配额、审计与回放、沙箱执行
- 建立可扩展的 Tool Registry 与协议:Zod Schema、幂等设计、超时/重试/熔断
- 用 LangGraph 编排多工具与多 Agent 协作:计划→并行执行→归并→冲突解决
- 在 Next.js 中实现 OAuth 回调、工具授权页面、执行可视化时间线
- 实战项目:企业协同助手(会议安排 + 纪要生成 + 工单创建 + 通知发送)
🧩 工具适配层设计(Adapter Pattern)
13.1 设计原则
- 标准化:统一
name/description/schema/_call
接口,与 LangChain Tool 对齐 - 安全性:最小权限(OAuth scope)、租户隔离、速率限制、审计留痕
- 稳定性:超时/重试/熔断/降级;幂等键(Idempotency-Key)
- 可观测:结构化日志、指标打点、RunId 串联全链路
- 可扩展:注册中心 + 插件化加载 + 版本管理(v1/v2)
13.2 基础接口与注册中心
// 文件:src/ch13/tooling/core.ts
import { Tool } from "@langchain/core/tools";
import { z } from "zod";
export interface ToolContext {
userId: string;
tenantId: string;
auth?: Record<string, any>; // 已授权的凭据/令牌
runId?: string;
}
export abstract class SafeTool<T extends z.ZodTypeAny> extends Tool {
schema!: T;
abstract _callWithContext(input: z.infer<T>, ctx: ToolContext): Promise<any>;
async _call(input: any) { throw new Error("Use _callWithContext with ToolRunner"); }
}
export class ToolRegistry {
private tools = new Map<string, SafeTool<any>>();
register(t: SafeTool<any>) { this.tools.set(t.name, t); }
get(name: string) { return this.tools.get(name); }
list() { return [...this.tools.values()]; }
}
export class ToolRunner {
constructor(private registry: ToolRegistry) {}
async run(name: string, args: any, ctx: ToolContext) {
const tool = this.registry.get(name);
if (!tool) throw new Error(`工具不存在: ${name}`);
// 基础策略:超时、重试、速率限制、审计
return withTimeout(() => withRetry(() => tool._callWithContext(args, ctx), 2), 15_000);
}
}
export async function withRetry<T>(fn: () => Promise<T>, tries = 2) {
let last: any; for (let i=0;i<tries;i++){ try{ return await fn(); }catch(e){ last=e; await sleep(200*(i+1)); }}
throw last;
}
export async function withTimeout<T>(fn: () => Promise<T>, ms: number) {
let t: any; return Promise.race([fn(), new Promise((_,rej)=> t=setTimeout(()=>rej(new Error("timeout")), ms))]).finally(()=>clearTimeout(t));
}
export const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
🔐 授权与密钥管理(OAuth2/OIDC + KMS)
13.3 凭据模型
// 文件:src/ch13/auth/creds.ts
export type Credential = {
provider: 'google'|'slack'|'jira'|'notion'|'github'|'outlook';
tenantId: string; userId: string; scope: string[];
accessToken: string; refreshToken?: string; expiresAt?: number; meta?: any;
};
13.4 Next.js OAuth 回调(示意)
// 文件:src/app/api/oauth/callback/route.ts
import { NextRequest } from "next/server";
export const runtime = "edge";
export async function GET(req: NextRequest) {
const url = new URL(req.url);
const provider = url.searchParams.get('provider');
const code = url.searchParams.get('code');
// 交换 token(省略具体实现),写入加密存储(KMS/Secrets Manager/DB KMS 列)
// 重定向到工具授权成功页面
return new Response(null, { status: 302, headers: { Location: `/tools?provider=${provider}&ok=1` } });
}
13.5 工具执行时提取凭据
// 文件:src/ch13/auth/context.ts
import { ToolContext } from "../tooling/core";
export async function buildToolContext(userId: string, tenantId: string): Promise<ToolContext> {
// 从安全存储读取用户已授权的第三方凭据
const auth = { google: { accessToken: "..." }, slack: { accessToken: "..." } };
return { userId, tenantId, auth };
}
🔌 常用企业工具适配实现
13.6 Google Calendar(日程)
// 文件:src/ch13/tools/google-calendar.ts
import { z } from "zod";
import { SafeTool, ToolContext } from "../tooling/core";
export class GoogleCalendarTool extends SafeTool<typeof GoogleCalendarTool.schema> {
name = "google_calendar";
description = "在 Google Calendar 中创建会议事件";
static schema = z.object({
title: z.string(),
start: z.string().describe("ISO 时间"),
end: z.string().describe("ISO 时间"),
attendees: z.array(z.string()).default([]),
description: z.string().optional(),
});
schema = GoogleCalendarTool.schema;
async _callWithContext(input: z.infer<typeof GoogleCalendarTool.schema>, ctx: ToolContext) {
if (!ctx.auth?.google?.accessToken) throw new Error("未授权 Google");
// 伪调用:实际应请求 Google Calendar API
const eventId = `ev_${Date.now()}`;
return { ok: true, eventId, link: `https://calendar.google.com/event?eid=${eventId}` };
}
}
13.7 Slack(消息通知)
// 文件:src/ch13/tools/slack.ts
import { z } from "zod";
import { SafeTool, ToolContext } from "../tooling/core";
export class SlackNotifyTool extends SafeTool<typeof SlackNotifyTool.schema> {
name = "slack_notify";
description = "在 Slack 频道或用户发送消息";
static schema = z.object({ channel: z.string(), text: z.string() });
schema = SlackNotifyTool.schema;
async _callWithContext(input: z.infer<typeof SlackNotifyTool.schema>, ctx: ToolContext) {
if (!ctx.auth?.slack?.accessToken) throw new Error("未授权 Slack");
// 伪调用 Slack chat.postMessage
return { ok: true, ts: Date.now(), channel: input.channel };
}
}
13.8 Jira(工单)
// 文件:src/ch13/tools/jira.ts
import { z } from "zod";
import { SafeTool, ToolContext } from "../tooling/core";
export class JiraIssueTool extends SafeTool<typeof JiraIssueTool.schema> {
name = "jira_create_issue";
description = "在 Jira 中创建工单(示例项目 KEY=PROJ)";
static schema = z.object({
summary: z.string(),
description: z.string(),
priority: z.enum(["Low","Medium","High"]).default("Medium"),
});
schema = JiraIssueTool.schema;
async _callWithContext(input: z.infer<typeof JiraIssueTool.schema>, ctx: ToolContext) {
// 伪调用 Jira REST API
const key = `PROJ-${Math.floor(Math.random()*10000)}`;
return { ok: true, key, url: `https://jira.example/browse/${key}` };
}
}
13.9 Notion(知识卡片)
// 文件:src/ch13/tools/notion.ts
import { z } from "zod";
import { SafeTool, ToolContext } from "../tooling/core";
export class NotionPageTool extends SafeTool<typeof NotionPageTool.schema> {
name = "notion_create_page";
description = "在 Notion 创建会议纪要/知识卡片";
static schema = z.object({ title: z.string(), content: z.string() });
schema = NotionPageTool.schema;
async _callWithContext(input: z.infer<typeof NotionPageTool.schema>, ctx: ToolContext) {
const pageId = `pg_${Date.now()}`;
return { ok: true, pageId, url: `https://notion.so/${pageId}` };
}
}
13.10 Web 搜索(代理)
// 文件:src/ch13/tools/search.ts
import { z } from "zod"; import { SafeTool, ToolContext } from "../tooling/core";
export class WebSearchTool extends SafeTool<typeof WebSearchTool.schema> {
name = "web_search"; description = "通用 Web 搜索(代理到自建/第三方搜索服务)";
static schema = z.object({ query: z.string(), k: z.number().default(5) }); schema = WebSearchTool.schema;
async _callWithContext(input: any, ctx: ToolContext) { return { results: Array.from({length: input.k}).map((_,i)=>({ title:`${input.query}-${i}`, url:`https://example.com/${i}`})) }; }
}
🧠 Agent × 工具:计划与执行
13.11 工具注册与执行策略
// 文件:src/ch13/tooling/register.ts
import { ToolRegistry, ToolRunner } from "./core";
import { GoogleCalendarTool } from "../tools/google-calendar";
import { SlackNotifyTool } from "../tools/slack";
import { JiraIssueTool } from "../tools/jira";
import { NotionPageTool } from "../tools/notion";
import { WebSearchTool } from "../tools/search";
export function buildRegistry() {
const reg = new ToolRegistry();
[new GoogleCalendarTool(), new SlackNotifyTool(), new JiraIssueTool(), new NotionPageTool(), new WebSearchTool()].forEach(t=>reg.register(t));
return { registry: reg, runner: new ToolRunner(reg) };
}
13.12 计划 → 并行执行 → 归并
// 文件:src/ch13/agent/plan-exec.ts
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { buildRegistry } from "../tooling/register";
import { buildToolContext } from "../auth/context";
const planPrompt = PromptTemplate.fromTemplate(`
将目标分解为工具调用步骤(JSON): [{id, tool, args, description}]
目标: {goal}
可用工具: {tools}
输出: JSON 数组
`);
export async function runPlanned(goal: string, userId: string, tenantId: string) {
const llm = new ChatOpenAI({ temperature: 0 });
const { registry, runner } = buildRegistry();
const toolsDesc = registry.list().map(t=>`${t.name}: ${t.description}`).join("\n");
const planRes = await planPrompt.pipe(llm).invoke({ goal, tools: toolsDesc });
let steps: any[]; try { steps = JSON.parse(String(planRes.content)); } catch { steps = []; }
const ctx = await buildToolContext(userId, tenantId);
const results = await Promise.allSettled(steps.map(s => runner.run(s.tool, s.args, ctx)));
return steps.map((s, i) => ({ step: s, result: results[i].status === 'fulfilled' ? results[i].value : { error: (results[i] as any).reason?.message } }));
}
🌳 LangGraph 多工具协作(含冲突解决)
13.13 状态与节点
// 文件:src/ch13/graph/state.ts
export type CoopState = {
goal: string;
plan?: any[];
exec?: Array<{ step: any; result: any }>;
merged?: any;
conflict?: { reason: string; details: any };
timeline: any[];
};
// 文件:src/ch13/graph/nodes.ts
import { StateGraph } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { runPlanned } from "../agent/plan-exec";
import { CoopState } from "./state";
export async function planNode(s: CoopState): Promise<Partial<CoopState>> {
const out = await runPlanned(`请完成:${s.goal}`, 'u1', 't1');
return { exec: out, timeline: [{ type: 'exec', at: Date.now(), data: out }] };
}
const mergePrompt = PromptTemplate.fromTemplate(`
给定多工具结果,整合为最终结构:
输入: {results}
输出JSON: {"summary": string, "actions": string[]}
`);
export async function mergeNode(s: CoopState): Promise<Partial<CoopState>> {
const llm = new ChatOpenAI({ temperature: 0.2 });
const res = await mergePrompt.pipe(llm).invoke({ results: JSON.stringify(s.exec) });
let merged: any; try { merged = JSON.parse(String(res.content)); } catch { merged = { summary: String(res.content) }; }
return { merged, timeline: [{ type: 'merge', at: Date.now(), data: merged }] };
}
const conflictPrompt = PromptTemplate.fromTemplate(`
检查是否存在冲突(时间冲突/重复通知/权限不足),输出:{"conflict": boolean, "reason": string}
输入: {merged}
`);
export async function conflictNode(s: CoopState): Promise<Partial<CoopState>> {
const llm = new ChatOpenAI({ temperature: 0 });
const res = await conflictPrompt.pipe(llm).invoke({ merged: JSON.stringify(s.merged) });
let j: any; try { j = JSON.parse(String(res.content)); } catch { j = { conflict: false }; }
return j.conflict ? { conflict: { reason: j.reason, details: s.merged } } : { };
}
13.14 图与条件流
// 文件:src/ch13/graph/app.ts
import { StateGraph } from "@langchain/langgraph";
import { CoopState } from "./state";
import { planNode, mergeNode, conflictNode } from "./nodes";
export async function buildCoopGraph() {
const g = new StateGraph<CoopState>({ channels: {
goal: { value: "" }, plan: { value: [] }, exec: { value: [] }, merged: { value: {} }, conflict: { value: undefined }, timeline: { value: [], merge: (a,b)=>[...a,...b] }
}});
g.addNode('plan', planNode);
g.addNode('merge', mergeNode);
g.addNode('conflict', conflictNode);
g.addEdge('start','plan'); g.addEdge('plan','merge');
g.addConditionalEdges('merge', s => s.merged ? 'conflict' : 'end', { conflict: 'conflict' });
g.addConditionalEdges('conflict', s => s.conflict ? 'end' : 'end', {});
return g.compile();
}
🌐 Next.js 集成:授权页、执行 API 与可视化
13.15 授权页(前端)
// 文件:src/app/tools/page.tsx
"use client";
export default function ToolsPage() {
const providers = [
{ id: 'google', name: 'Google Calendar' },
{ id: 'slack', name: 'Slack' },
{ id: 'jira', name: 'Jira' },
{ id: 'notion', name: 'Notion' },
];
return (
<main className="max-w-3xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold">工具授权</h1>
<ul className="space-y-2">
{providers.map(p => (
<li key={p.id} className="flex items-center justify-between border rounded p-3">
<div>{p.name}</div>
<a className="px-3 py-1 rounded bg-blue-600 text-white" href={`/api/oauth/callback?provider=${p.id}`}>授权</a>
</li>
))}
</ul>
</main>
);
}
13.16 执行 API
// 文件:src/app/api/coop/route.ts
import { NextRequest } from "next/server";
import { buildCoopGraph } from "@/src/ch13/graph/app";
export const runtime = "edge";
export async function POST(req: NextRequest) {
const { goal } = await req.json();
const app = await buildCoopGraph();
const out = await app.invoke({ goal, timeline: [] });
return Response.json({ ok: true, data: out });
}
13.17 可视化页面
// 文件:src/app/coop/page.tsx
"use client";
import { useState } from "react";
export default function CoopPage(){
const [goal, setGoal] = useState("");
const [data, setData] = useState<any>(null);
const run = async ()=>{
const res = await fetch('/api/coop', { method:'POST', body: JSON.stringify({ goal }) });
const json = await res.json(); setData(json.data);
};
return (
<main className="max-w-3xl mx-auto p-4 space-y-3">
<h1 className="text-2xl font-bold">企业协同助手</h1>
<div className="flex gap-2">
<input className="flex-1 border rounded px-3 py-2" value={goal} onChange={e=>setGoal(e.target.value)} placeholder="例如:安排本周团队会议并发送通知,记录纪要,若有问题创建工单" />
<button onClick={run} className="px-4 py-2 bg-blue-600 text-white rounded">运行</button>
</div>
{data && <pre className="whitespace-pre-wrap break-words text-sm bg-gray-50 p-3 rounded">{JSON.stringify(data, null, 2)}</pre>}
</main>
);
}
🚀 实战:企业协同助手(会议安排 + 纪要 + 工单 + 通知)
13.18 场景描述
- 输入:自然语言目标,例如“安排周三下午30分钟评审会,参与 A/B/C;会后出纪要并将 action items 生成 Jira 工单;在 Slack 通知团队。”
- 流程:计划 → 创建日历 → 生成纪要模版 → 创建工单 → 发送通知 → 回执
- 守护:权限检查/时间冲突/重复通知/失败重试/人工确认
13.19 关键策略
- 幂等:对同一
goal + time
生成相同Idempotency-Key
,避免重复 - 安全:OAuth scope 最小化;工具参数白名单;对外链接检测
- 可观测:每步计时、错误分类、结果回放(含外部链接和响应摘要)
13.20 示例执行(伪)
{
"goal": "安排周三评审会+纪要+工单+通知",
"timeline": [
{"type":"exec","at":1710000000,"data":[{"step":{"id":1,"tool":"google_calendar"},"result":{"ok":true,"eventId":"ev_..."}}]},
{"type":"exec","at":1710000100,"data":[{"step":{"id":2,"tool":"notion_create_page"},"result":{"ok":true,"pageId":"pg_..."}}]},
{"type":"exec","at":1710000200,"data":[{"step":{"id":3,"tool":"jira_create_issue"},"result":{"ok":true,"key":"PROJ-123"}}]},
{"type":"exec","at":1710000300,"data":[{"step":{"id":4,"tool":"slack_notify"},"result":{"ok":true,"channel":"#team"}}]}
],
"merged": {"summary":"会议已安排,纪要创建,工单生成,已通知"}
}
⚙️ 工程化:配额、限流、审计与沙箱
13.21 配额与限流
- 维度:按租户/用户/工具/分钟/天;超限后软性降级或硬性拒绝
- 共享额度:团队级池化;关键工具独立额度(如 Calendar)
13.22 审计与回放
- 记录:输入、调用参数、响应摘要、外部链接、操作者与 RunId
- 回放:敏感工具需双人复核模式(四眼原则)
13.23 沙箱与策略
- 代码执行工具需强沙箱(见第8章),禁止文件/网络/系统调用
- 生成类工具输出需正则/策略检查(敏感词/泄露检测)
🧪 测试与验收
13.24 工具级测试
- 单元测试:参数校验、错误路径、重试与超时
- 集成测试:OAuth 授权回调、令牌刷新、权限拒绝
13.25 端到端回归
- 场景脚本:常见协同任务的黄金集;比对成功率、平均用时、失败分类
- 灰度策略:新工具/新版本先小流量试运行,观测稳定后全量
📚 延伸链接
- Google Calendar API:
https://developers.google.com/calendar/api
- Slack API:
https://api.slack.com/
- Jira REST API:
https://developer.atlassian.com/cloud/jira/platform/rest/v3/
- Notion API:
https://developers.notion.com/
- OAuth 2.0:
https://oauth.net/2/
✅ 本章小结
- 构建了安全可扩展的工具适配层与注册执行框架
- 集成常用企业工具(日历/消息/工单/文档/搜索)并与 Agent 协作
- 用 LangGraph 编排多工具结果并进行冲突检测与归并
- 在 Next.js 中实现授权、执行与可视化,完成企业协同助手实战
🎯 下章预告
下一章《生产环境部署与 DevOps 实践》中,我们将:
- 设计多环境部署与密钥管理
- 构建 CI/CD 与灰度回滚
- 打造生产级可观测与告警
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!