《NestJS智能体开发》(五):结构化输出与工具调用

509 阅读6分钟

结构化输出

大语言模型(如DeepSeek、Claude等)的卓越性能已经在文本生成领域得到广泛应用,能够生成看似自然流畅的文本内容。然而,这些生成的文本往往是自由形式的,缺乏明确的结构,这给后续的处理和利用带来了挑战。例如,当需要从生成的文本中提取关键信息(如姓名、年龄、地址等)时,传统的方法需要复杂的文本解析和正则表达式匹配,开发成本高,鲁棒性差。如果我们能够以结构化的方式获取生成内容,这些问题将迎刃而解。

image.png

提升数据处理效率

结构化输出使得生成的内容可以直接以JSON或其他数据格式返回,无需额外的文本解析步骤。例如,当涉及到用户资料处理时,模型可以生成如下结构化数据:

{
  "user": {
    "name": "李华",
    "age": 25,
    "address": "北京市朝阳区",
    "email": "lihua@example.com"
  }
}

在前端应用中,可以立即用这些数据填充表单或用户信息卡片,无需复杂的后端处理。

智能体驱动开发

假设我们有一个创客大赛管理系统开发项目,需要设计数据库、开发 API 接口,并完成 CI/CD 部署。为了高效完成任务,我们构建了一个基于 Agentic Mesh 的智能体编码团队,团队成员包括:

  1. 系统架构师:负责整体架构规划,包括数据库设计和 API 接口设计。
  2. Node.js 工程师:负责后端开发,使用 Nest.js 框架和 TypeScript 语言。
  3. DevOps 工程师:负责 CI/CD 流程和基础设施部署,使用 Docker、Kubernetes 和 Jenkins 等工具。

在传统的开发流程中,任务分配和计划制定通常依赖于手动文档编写和会议讨论,效率低下且容易出错。通过引入结构化输出,我们可以让编排智能体生成开发计划,并将其分配给不同的专家智能体。

import { generateObject, generateText } from "ai";
import { z } from 'zod';

const agents = [
    {
        id: 'system-architect',
        name: '系统架构师',
        description: '专业的系统架构师,精通Restful API设计,MySQL数据库设计和优化',
    },
    {
        id: 'nodejs-developer',
        name: 'Node.js工程师',
        description: '专业的Node.js工程师,精通Nest.js框架,精通TypeScript语言',
    },
    {
        id: 'devops-engineer',
        name: 'DevOps工程师',
        description: '专业的DevOps工程师,精通Docker和Kubernetes、Jenkins等主流DevOps工具',
    }
];

const model = this.agent.createQWenModel();
const result = await generateObject({
    model,
    schema: z.object({
        tasks: z.array(
            z.object({
                name: z.string(),
                description: z.string(),
                agentId: z.string(),
                steps: z.array(
                    z.object({
                        name: z.string(),
                        description: z.string(),
                    })
                )
            })
        )
    }),
    prompt: `
        系统中所有的Agent的信息如下:
        ${agents.map((agent) => `名称:${agent.name} 职责:${agent.description} ID:${agent.id}`).join('\n')}

        请生成一份创客大赛管理系统开发计划,包括数据库设计和API接口设计,并将任务分配给合适的Agent,每个任务都必须有多个详细的步骤。
    `,
});

res.send(result);

生成结果如下:

{
    "object": {
        "tasks": [
            {
                "name": "数据库设计",
                "description": "设计创客大赛管理系统的数据库结构",
                "agentId": "system-architect",
                "steps": [
                    {
                        "name": "确定数据表",
                        "description": "确定需要哪些数据表,例如用户表、项目表、比赛表等"
                    },
                    {
                        "name": "设计字段",
                        "description": "为每个数据表设计字段,包括字段名、数据类型、长度等"
                    },
                    {
                        "name": "优化索引",
                        "description": "优化索引设计,提高查询效率"
                    }
                ]
            },
            {
                "name": "API接口设计",
                "description": "设计创客大赛管理系统的API接口",
                "agentId": "system-architect",
                "steps": [
                    {
                        "name": "确定接口类型",
                        "description": "确定需要哪些类型的接口,例如CRUD接口、查询接口等"
                    },
                    {
                        "name": "设计接口参数",
                        "description": "为每个接口设计参数,包括请求参数、返回参数等"
                    },
                    {
                        "name": "编写接口文档",
                        "description": "编写详细的接口文档,方便其他开发者使用"
                    }
                ]
            },
            {
                "name": "Node.js后端开发",
                "description": "使用Node.js实现后端逻辑",
                "agentId": "nodejs-developer",
                "steps": [
                    {
                        "name": "搭建项目",
                        "description": "搭建Node.js项目,安装相关依赖"
                    },
                    {
                        "name": "实现业务逻辑",
                        "description": "实现后端业务逻辑,包括数据处理、业务逻辑处理等"
                    },
                    {
                        "name": "单元测试",
                        "description": "编写单元测试,确保代码质量"
                    }
                ]
            },
            {
                "name": "DevOps部署",
                "description": "使用DevOps工具进行部署",
                "agentId": "devops-engineer",
                "steps": [
                    {
                        "name": "编写Dockerfile",
                        "description": "编写Dockerfile,定义镜像"
                    },
                    {
                        "name": "构建Docker镜像",
                        "description": "使用Dockerfile构建Docker镜像"
                    },
                    {
                        "name": "部署到Kubernetes",
                        "description": "将镜像部署到Kubernetes集群"
                    }
                ]
            }
        ]
    },
    "finishReason": "tool-calls",
    "usage": {
        "promptTokens": 442,
        "completionTokens": 441,
        "totalTokens": 883
    },
    "warnings": [],
    "providerMetadata": {
        "openai": {}
    },
    "experimental_providerMetadata": {
        "openai": {}
    },
    "response": {
        "id": "chatcmpl-6I5z36TfNUwXduuABR5r5HsTKaMF8BCo",
        "timestamp": "2025-02-23T09:17:17.000Z",
        "modelId": "qwen2.5",
        "headers": {
            "content-length": "2354",
            "content-type": "application/json; charset=utf-8",
            "date": "Sun, 23 Feb 2025 09:16:54 GMT",
            "keep-alive": "timeout=15, max=100",
            "server": "uvicorn, llama-box/v0.0.117",
            "x-request-accepted-at": "1559518723735",
            "x-request-id": "1559518723734",
            "x-response-tokens-per-second": "22.589093"
        }
    },
    ...
}

通过结构化输出,系统架构师智能体设计的数据库结构和 API 接口文档可以共享给 Node.js 工程师智能体,以便其进行后端开发。DevOps 工程师智能体可以获取后端代码和 Dockerfile,进行镜像构建和部署。

工具调用

Tool Calling(工具调用)是一种允许 AI 模型与外部工具或 API 进行交互的功能。它使模型能够调用特定的工具来执行任务或检索信息,从而扩展了模型的能力。以下是 Tool Calling 的一些关键特点和工作原理:

image.png

关键特性

  1. 增强功能:Tool Calling 允许模型通过调用外部工具来执行其自身无法完成的任务,例如信息检索、数据处理或执行特定操作。
  2. 灵活性:开发者可以定义各种工具,包括网页搜索、数据库查询、数学计算等,以满足不同的应用需求。
  3. 智能选择:模型可以根据用户的问题或提示词,智能地选择合适的工具进行调用。

编写一个天气查询工具

import { generateObject, generateText, tool } from "ai";
import { z } from 'zod';

const weather = tool({
    description: '获取某地天气',
    parameters: z.object({
        location: z.string().describe('要获取天气的位置'),
    }),
    execute: async ({ location }) => ({
        location,
        temperature: 20 + Math.floor(Math.random() * 21) - 10,
    }),
});

const model = this.agent.createQWenModel();
const result = await generateText({
    model,
    tools: {
        weather
    },
    toolChoice: 'required',
    prompt: "杭州的天气如何?"
});
res.send(result);

toolChoice toolChoice选项支持以下设置:

  • auto (默认):模型可以选择是否以及调用哪些工具。
  • required: 模型必须调用一个工具,它可以自主选择调用哪个工具。
  • none: 模型不得调用工具
  • { type: 'tool', toolName: string }: 模型必须调用指定的工具

执行上面的代码模型将从用户提问中解析出工具需要的参数,然后选择并调用工具获得结果。

{
    "text": "",
    "toolCalls": [
        {
            "type": "tool-call",
            "toolCallId": "call-sWJm07WQzzre50Bm4g3GYUKtxYHHJBpk",
            "toolName": "weather",
            "args": {
                "location": "杭州"
            }
        }
    ],
    "toolResults": [
        {
            "type": "tool-result",
            "toolCallId": "call-sWJm07WQzzre50Bm4g3GYUKtxYHHJBpk",
            "toolName": "weather",
            "args": {
                "location": "杭州"
            },
            "result": {
                "location": "杭州",
                "temperature": 29
            }
        }
    ],
    "finishReason": "tool-calls",
    "usage": {
        "promptTokens": 215,
        "completionTokens": 19,
        "totalTokens": 234
    },
    "warnings": [],
    ...
}

让智能体调用其他智能体

const payload = {
    "name": "welcome-greet",
    "data": { "username": "Roy Lin" }
};

const callAgent = tool({
    description: '调用智能体',
    parameters: z.object({
        name: z.string(),
        data: z.any(),
    }),
    execute: async ({ name, data }) => {
        return await this.inngestService.send({
            name,
            data
        });
    }
});

const model = this.agent.createQWenModel();
const result = await generateText({
    model,
    tools: {
        callAgent
    },
    toolChoice: 'required',
    prompt: `请调用智能体: ${payload.name}, 数据为:${JSON.stringify(payload.data)}`
});
res.send(result);

下面Greeting Agent代码实现

@Agent({
    config: {
        id: 'welcome-greet',
        name: "Greeting Agent",
        description: 'This is an agent used to greet users.'
    },
    trigger: {
        event: 'welcome-greet',
    }
})
async greeting({ event, step }: Context) {
    const model = this.agentService.createQWenModel();

    const { text } = await generateText({
        model,
        prompt: `请用一句话向${event.data.username}问好。`,
    });

    return { message: text };
}

查看Inngest UI可以看到Greeting Agent的执行结果

image.png

总结

通过结构化输出和工具调用,我们可以更好地利用大语言模型的能力,为实际应用带来更高的效率和更好的用户体验。