Skip to content

文件管理 Agent

本教程将带你构建一个文件管理 Agent,支持 LLM 驱动的工具调用。

你将学到

  • 定义多个 Skill(工具)
  • LLM 自动选择工具
  • 工具执行结果处理

最终效果

用户: 读取 package.json 文件
助手: [调用 readFile 工具] 文件内容如下...

用户: 列出当前目录的文件
助手: [调用 listFiles 工具] 找到 5 个文件...

第一步:创建项目

bash
mkdir tool-agent && cd tool-agent
bun init -y
bun add @multi-agent/a2a zod @langchain/openai

第二步:定义工具 Schema

创建 server.ts,定义文件操作工具:

typescript
import { z } from 'zod'

// 读取文件
const readFileSchema = z.object({
  path: z.string().describe('文件路径'),
})

// 写入文件
const writeFileSchema = z.object({
  path: z.string().describe('文件路径'),
  content: z.string().describe('文件内容'),
})

// 列出文件
const listFilesSchema = z.object({
  path: z.string().optional().describe('目录路径,默认当前目录'),
})
typescript
// server.ts
import { z } from 'zod'
// 读取文件
const readFileSchema = z.object({ 
  path: z.string().describe('文件路径'), 
}) 
// 写入文件
const writeFileSchema = z.object({ 
  path: z.string().describe('文件路径'), 
  content: z.string().describe('文件内容'), 
}) 
// 列出文件
const listFilesSchema = z.object({ 
  path: z.string().optional().describe('目录路径,默认当前目录'), 
}) 

第三步:实现工具 Handler

typescript
import { readFile, writeFile, readdir } from 'fs/promises'
import { type Context } from '@multi-agent/a2a'

// 读取文件
async function readFileHandler(params: { path: string }, ctx: Context) {
  ctx.stream.send({ type: 'progress', text: `读取文件: ${params.path}` })
  const content = await readFile(params.path, 'utf-8')
  ctx.stream.send({
    type: 'done',
    text: '读取完成',
    data: { path: params.path, content, size: content.length },
  })
}

// 写入文件
async function writeFileHandler(params: { path: string; content: string }, ctx: Context) {
  ctx.stream.send({ type: 'progress', text: `写入文件: ${params.path}` })
  await writeFile(params.path, params.content, 'utf-8')
  ctx.stream.send({
    type: 'done',
    text: '写入完成',
    data: { path: params.path, size: params.content.length },
  })
}

// 列出文件
async function listFilesHandler(params: { path?: string }, ctx: Context) {
  const dir = params.path || '.'
  ctx.stream.send({ type: 'progress', text: `列出目录: ${dir}` })
  const entries = await readdir(dir, { withFileTypes: true })
  const files = entries.map(e => ({
    name: e.name,
    isDirectory: e.isDirectory(),
  }))
  ctx.stream.send({
    type: 'done',
    text: `找到 ${files.length} 个文件`,
    data: { path: dir, files },
  })
}
typescript
// server.ts
import { z } from 'zod'
import { readFile, writeFile, readdir } from 'fs/promises'
import { type Context } from '@multi-agent/a2a'

const readFileSchema = z.object({
  path: z.string().describe('文件路径'),
})

const writeFileSchema = z.object({
  path: z.string().describe('文件路径'),
  content: z.string().describe('文件内容'),
})

const listFilesSchema = z.object({
  path: z.string().optional().describe('目录路径,默认当前目录'),
})

// 读取文件
async function readFileHandler(params: { path: string }, ctx: Context) { 
  ctx.stream.send({ type: 'progress', text: `读取文件: ${params.path}` }) 
  const content = await readFile(params.path, 'utf-8') 
  ctx.stream.send({ 
    type: 'done', 
    text: '读取完成', 
    data: { path: params.path, content, size: content.length }, 
  }) 
} 
// 写入文件
async function writeFileHandler(params: { path: string; content: string }, ctx: Context) { 
  ctx.stream.send({ type: 'progress', text: `写入文件: ${params.path}` }) 
  await writeFile(params.path, params.content, 'utf-8') 
  ctx.stream.send({ 
    type: 'done', 
    text: '写入完成', 
    data: { path: params.path, size: params.content.length }, 
  }) 
} 
// 列出文件
async function listFilesHandler(params: { path?: string }, ctx: Context) { 
  const dir = params.path || '.'
  ctx.stream.send({ type: 'progress', text: `列出目录: ${dir}` }) 
  const entries = await readdir(dir, { withFileTypes: true }) 
  const files = entries.map(e => ({ 
    name: e.name, 
    isDirectory: e.isDirectory(), 
  })) 
  ctx.stream.send({ 
    type: 'done', 
    text: `找到 ${files.length} 个文件`, 
    data: { path: dir, files }, 
  }) 
} 

第四步:配置 Agent Server

typescript
import { createAgentServer, type AgentConfig } from '@multi-agent/a2a'

const config: AgentConfig = {
  agentId: 'tool-agent',
  name: 'Tool Agent',
  version: '1.0.0',
  description: '文件管理 Agent',
  address: 'a2a://0.0.0.0:50055',
  skills: [
    {
      name: 'readFile',
      description: '读取文件内容',
      handler: readFileHandler,
      inputSchema: z.toJSONSchema(readFileSchema),
    },
    {
      name: 'writeFile',
      description: '写入文件内容',
      handler: writeFileHandler,
      inputSchema: z.toJSONSchema(writeFileSchema),
    },
    {
      name: 'listFiles',
      description: '列出目录中的文件',
      handler: listFilesHandler,
      inputSchema: z.toJSONSchema(listFilesSchema),
    },
  ],
}

const server = createAgentServer(config)
await server.start()
console.log('Tool Agent 已启动: localhost:50055')
typescript
// server.ts
import { z } from 'zod'
import { readFile, writeFile, readdir } from 'fs/promises'
import { createAgentServer, type AgentConfig, type Context } from '@multi-agent/a2a'

const readFileSchema = z.object({
  path: z.string().describe('文件路径'),
})

const writeFileSchema = z.object({
  path: z.string().describe('文件路径'),
  content: z.string().describe('文件内容'),
})

const listFilesSchema = z.object({
  path: z.string().optional().describe('目录路径,默认当前目录'),
})

async function readFileHandler(params: { path: string }, ctx: Context) {
  ctx.stream.send({ type: 'progress', text: `读取文件: ${params.path}` })
  const content = await readFile(params.path, 'utf-8')
  ctx.stream.send({
    type: 'done',
    text: '读取完成',
    data: { path: params.path, content, size: content.length },
  })
}

async function writeFileHandler(params: { path: string; content: string }, ctx: Context) {
  ctx.stream.send({ type: 'progress', text: `写入文件: ${params.path}` })
  await writeFile(params.path, params.content, 'utf-8')
  ctx.stream.send({
    type: 'done',
    text: '写入完成',
    data: { path: params.path, size: params.content.length },
  })
}

async function listFilesHandler(params: { path?: string }, ctx: Context) {
  const dir = params.path || '.'
  ctx.stream.send({ type: 'progress', text: `列出目录: ${dir}` })
  const entries = await readdir(dir, { withFileTypes: true })
  const files = entries.map(e => ({
    name: e.name,
    isDirectory: e.isDirectory(),
  }))
  ctx.stream.send({
    type: 'done',
    text: `找到 ${files.length} 个文件`,
    data: { path: dir, files },
  })
}

const config: AgentConfig = { 
  agentId: 'tool-agent', 
  name: 'Tool Agent', 
  version: '1.0.0', 
  description: '文件管理 Agent', 
  address: 'a2a://0.0.0.0:50055', 
  skills: [ 
    { 
      name: 'readFile', 
      description: '读取文件内容', 
      handler: readFileHandler, 
      inputSchema: z.toJSONSchema(readFileSchema), 
    }, 
    { 
      name: 'writeFile', 
      description: '写入文件内容', 
      handler: writeFileHandler, 
      inputSchema: z.toJSONSchema(writeFileSchema), 
    }, 
    { 
      name: 'listFiles', 
      description: '列出目录中的文件', 
      handler: listFilesHandler, 
      inputSchema: z.toJSONSchema(listFilesSchema), 
    }, 
  ], 
} 
const server = createAgentServer(config) 
await server.start() 
console.log('Tool Agent 已启动: localhost:50055') 

第五步:创建测试客户端

创建 client.ts

typescript
// client.ts
import { createAgentClient } from '@multi-agent/a2a'

// 1. 创建客户端
const client = createAgentClient({
  agentId: 'tool-agent',
  address: 'a2a://localhost:50055',
})

// 2. 列出文件
console.log('--- 列出文件 ---')
const listStream = await client.call('listFiles', { path: '.' })
for await (const msg of listStream) {
  if (msg.type === 'progress') console.log(msg.text)
  if (msg.type === 'done') console.log('文件列表:', msg.data.files)
}

// 3. 读取文件
console.log('\n--- 读取文件 ---')
const readStream = await client.call('readFile', { path: 'package.json' })
for await (const msg of readStream) {
  if (msg.type === 'progress') console.log(msg.text)
  if (msg.type === 'done') console.log('文件大小:', msg.data.size, '字节')
}

// 4. 写入文件
console.log('\n--- 写入文件 ---')
const writeStream = await client.call('writeFile', {
  path: 'test.txt',
  content: 'Hello from Tool Agent!',
})
for await (const msg of writeStream) {
  if (msg.type === 'progress') console.log(msg.text)
  if (msg.type === 'done') console.log('写入完成')
}

// 5. 关闭连接
await client.close()

第六步:运行

bash
# 终端 1
bun run server.ts

# 终端 2
bun run client.ts

输出:

--- 列出文件 ---
列出目录: .
文件列表: [ { name: 'package.json', isDirectory: false }, ... ]

--- 读取文件 ---
读取文件: package.json
文件大小: 234 字节

--- 写入文件 ---
写入文件: test.txt
写入完成

进阶:LLM 驱动工具选择

让 LLM 根据用户请求自动选择工具:

typescript
import { createAgentClient, buildTools } from '@multi-agent/a2a'
import { ChatOpenAI } from '@langchain/openai'

const client = createAgentClient({
  agentId: 'tool-agent',
  address: 'a2a://localhost:50055',
})

// 1. 获取 Agent 能力,构建 LangChain Tools
const agentCard = await client.getAgentCard()
const tools = buildTools(client, agentCard)

// 2. 绑定工具到 LLM
const llm = new ChatOpenAI({ model: 'gpt-4o-mini' })
const llmWithTools = llm.bindTools(tools)

// 3. 用户请求
const response = await llmWithTools.invoke('帮我看看当前目录有哪些文件')

// 4. 执行工具调用
if (response.tool_calls?.length) {
  for (const toolCall of response.tool_calls) {
    console.log(`调用工具: ${toolCall.name}`)
    const stream = await client.call(toolCall.name, toolCall.args)
    for await (const msg of stream) {
      if (msg.type === 'done') console.log('结果:', msg.data)
    }
  }
}

await client.close()

交互流程图

工具调用交互流程

MIT Licensed