随着人工智能技术的不断发展,OpenAI的API当中又除了很多有意思的功能。这些 API 不仅可以生成自然语言文本,还能解析结构化数据、进行实时交互和自动化函数调用。在本文中,我们将深入探讨 OpenAI API 的结构化输出解析助手和流式响应功能,帮助开发者更高效地利用这些工具。
结构化输出
OpenAI API 支持通过 response_format
请求参数从模型中提取 JSON 响应。为了更方便地处理这些响应,OpenAI SDK 提供了一个 client.beta.chat.completions.parse()
方法。这是一个包装在 client.chat.completions.create()
之上的方法,返回一个 ParsedChatCompletion
对象。
使用 Zod 模式自动解析响应内容
借助 Zod 模式,可以自动解析和验证 JSON 响应内容。以下是一个示例:
import { zodResponseFormat } from 'openai/helpers/zod'; import OpenAI from 'openai/index'; import { z } from 'zod'; const Step = z.object({ explanation: z.string(), output: z.string(), }); const MathResponse = z.object({ steps: z.array(Step), final_answer: z.string(), }); const client = new OpenAI(); const completion = await client.beta.chat.completions.parse({ model: 'gpt-4o-2024-08-06', messages: [ { role: 'system', content: '你是一个有帮助的数学老师。' }, { role: 'user', content: '求解 8x + 31 = 2' }, ], response_format: zodResponseFormat(MathResponse, 'math_response'), }); const message = completion.choices[0]?.message; if (message?.parsed) { console.log(message.parsed.steps); console.log(`答案: ${message.parsed.final_answer}`); }
在这个示例中,我们定义了一个数学问题的响应格式,并利用 Zod 模式进行解析和验证。
自动解析函数工具调用
如果你使用 zodFunctionTool()
辅助方法,并将工具模式标记为 "strict": true,.parse()
方法将自动解析函数工具调用。以下是一个示例:
import { zodFunction } from 'openai/helpers/zod'; import OpenAI from 'openai/index'; import { z } from 'zod'; const Table = z.enum(['orders', 'customers', 'products']); const Column = z.enum(['id', 'status', 'expected_delivery_date', 'delivered_at', 'shipped_at', 'ordered_at', 'canceled_at']); const Operator = z.enum(['=', '>', '<', '<=', '>=', '!=']); const OrderBy = z.enum(['asc', 'desc']); const DynamicValue = z.object({ column_name: z.string() }); const Condition = z.object({ column: z.string(), operator: Operator, value: z.union([z.string(), z.number(), DynamicValue]), }); const Query = z.object({ table_name: Table, columns: z.array(Column), conditions: z.array(Condition), order_by: OrderBy, }); const client = new OpenAI(); const completion = await client.beta.chat.completions.parse({ model: 'gpt-4o-2024-08-06', messages: [ { role: 'system', content: '你是一个有帮助的助手。今天是2024年8月6日。你帮助用户查询他们需要的数据,通过调用查询函数。', }, { role: 'user', content: '查找我去年11月所有已完成但未按时交付的订单', }, ], tools: [zodFunction({ name: 'query', parameters: Query })], }); const toolCall = completion.choices[0]?.message.tool_calls?.[0]; if (toolCall) { const args = toolCall.function.parsed_arguments as z.infer<typeof Query>; console.log(args); console.log(args.table_name); }
在这个示例中,我们定义了一个查询函数的参数格式,并利用 Zod 模式进行解析和验证。
流式输出
OpenAI 支持在与 Chat 或 Assistant API 交互时流式响应,提供了更高效的实时交互体验。
Assistant 流式 API
Assistant 流式 API 允许开发者订阅事件类型并接收累积的响应。以下是一个示例:
const run = openai.beta.threads.runs .stream(thread.id, { assistant_id: assistant.id, }) .on('textCreated', (text) => process.stdout.write('\n助手 > ')) .on('textDelta', (textDelta, snapshot) => process.stdout.write(textDelta.value)) .on('toolCallCreated', (toolCall) => process.stdout.write(`\n助手 > ${toolCall.type}\n\n`)) .on('toolCallDelta', (toolCallDelta, snapshot) => { if (toolCallDelta.type === 'code_interpreter') { if (toolCallDelta.code_interpreter.input) { process.stdout.write(toolCallDelta.code_interpreter.input); } if (toolCallDelta.code_interpreter.outputs) { process.stdout.write('\n输出 >\n'); toolCallDelta.code_interpreter.outputs.forEach((output) => { if (output.type === 'logs') { process.stdout.write(`\n${output.logs}\n`); } }); } } });
通过这个示例,我们可以看到如何创建一个运行并订阅多个事件,以便实时处理来自 Assistant 的响应。
开始流
有三种创建流的辅助方法:
openai.beta.threads.runs.stream();
openai.beta.threads.createAndRunStream();
openai.beta.threads.runs.submitToolOutputsStream();
这些方法分别用于启动并流式响应已填充消息的现有运行、向线程添加消息并启动运行、以及向等待输出的运行提交工具输出并启动流。
自动化函数调用
openai.chat.completions.runTools()
方法返回一个 Runner,用于使用聊天完成自动化函数调用。以下是一个示例:
client.chat.completions.runTools({ model: 'gpt-3.5-turbo', messages: [{ role: 'user', content: '这周的天气如何?' }], tools: [ { type: 'function', function: { function: getWeather as (args: { location: string; time: Date }) => any, parse: parseFunction as (args: strings) => { location: string; time: Date }, parameters: { type: 'object', properties: { location: { type: 'string' }, time: { type: 'string', format: 'date-time' }, }, }, }, }, ], });
在这个示例中,我们定义了一个获取天气的函数,并通过 runTools
方法自动化函数调用。
综合示例
代码
import { zodResponseFormat, zodFunction } from 'openai/helpers/zod'; import OpenAI from 'openai/index'; import { z } from 'zod'; // 定义 OpenAIHelper 类,用于封装 OpenAI API 的各种功能 class OpenAIHelper { private client: OpenAI; constructor(apiKey: string) { // 初始化 OpenAI 客户端 this.client = new OpenAI(apiKey); } /** * 解析结构化响应 * @param messages - 与模型交互的消息数组 * @param responseSchema - 用于验证响应的 Zod 模式 * @returns 解析后的响应数据 */ async parseStructuredResponse(messages: any[], responseSchema: z.ZodType<any>) { const completion = await this.client.beta.chat.completions.parse({ model: 'gpt-4o-2024-08-06', messages, response_format: zodResponseFormat(responseSchema, 'structured_response'), }); const message = completion.choices[0]?.message; if (message?.parsed) { return message.parsed; } return null; } /** * 解析数学问题的响应 * @param messages - 与模型交互的消息数组 * @returns 解析后的数学问题响应数据 */ async parseMathResponse(messages: any[]) { // 定义每一步的结构 const Step = z.object({ explanation: z.string(), // 每一步的解释 output: z.string(), // 每一步的输出 }); // 定义数学问题响应的结构 const MathResponse = z.object({ steps: z.array(Step), // 包含多个步骤 final_answer: z.string(), // 最终答案 }); // 解析结构化响应 return this.parseStructuredResponse(messages, MathResponse); } /** * 解析查询函数的响应 * @param messages - 与模型交互的消息数组 * @returns 解析后的查询函数响应数据 */ async parseQueryFunction(messages: any[]) { // 定义查询相关的枚举和对象 const Table = z.enum(['orders', 'customers', 'products']); const Column = z.enum(['id', 'status', 'expected_delivery_date', 'delivered_at', 'shipped_at', 'ordered_at', 'canceled_at']); const Operator = z.enum(['=', '>', '<', '<=', '>=', '!=']); const OrderBy = z.enum(['asc', 'desc']); const DynamicValue = z.object({ column_name: z.string() }); const Condition = z.object({ column: z.string(), operator: Operator, value: z.union([z.string(), z.number(), DynamicValue]), }); const Query = z.object({ table_name: Table, columns: z.array(Column), conditions: z.array(Condition), order_by: OrderBy, }); // 解析查询函数的响应 const completion = await this.client.beta.chat.completions.parse({ model: 'gpt-4o-2024-08-06', messages, tools: [zodFunction({ name: 'query', parameters: Query })], }); const toolCall = completion.choices[0]?.message.tool_calls?.[0]; if (toolCall) { const args = toolCall.function.parsed_arguments as z.infer<typeof Query>; return args; } return null; } /** * 流式助手响应 * @param threadId - 线程 ID * @param assistantId - 助手 ID * @param eventHandlers - 事件处理程序 * @returns 运行实例 */ streamAssistantResponses(threadId: string, assistantId: string, eventHandlers: any) { const run = this.client.beta.threads.runs .stream(threadId, { assistant_id: assistantId, }) .on('textCreated', eventHandlers.onTextCreated) // 当新文本创建时触发 .on('textDelta', eventHandlers.onTextDelta) // 当文本有增量更新时触发 .on('toolCallCreated', eventHandlers.onToolCallCreated) // 当工具调用创建时触发 .on('toolCallDelta', eventHandlers.onToolCallDelta); // 当工具调用有增量更新时触发 return run; } /** * 自动化函数调用 * @param model - 模型名称 * @param messages - 与模型交互的消息数组 * @param tools - 要运行的工具数组 * @returns 函数调用结果 */ runTools(model: string, messages: any[], tools: any[]) { return this.client.chat.completions.runTools({ model, messages, tools, }); } }
使用
// 使用示例 (async () => { const helper = new OpenAIHelper('your-api-key'); // 解析数学问题响应 const mathMessages = [ { role: 'system', content: '你是一个有帮助的数学老师。' }, { role: 'user', content: '求解 8x + 31 = 2' }, ]; const mathResponse = await helper.parseMathResponse(mathMessages); console.log('Math Response:', mathResponse); // 解析查询函数 const queryMessages = [ { role: 'system', content: '你是一个有帮助的助手。今天是2024年8月6日。你帮助用户查询他们需要的数据,通过调用查询函数。', }, { role: 'user', content: '查找我去年11月所有已完成但未按时交付的订单', }, ]; const queryResponse = await helper.parseQueryFunction(queryMessages); console.log('Query Response:', queryResponse); // 流式助手响应 const eventHandlers = { onTextCreated: (text: string) => process.stdout.write('\n助手 > '), onTextDelta: (textDelta: any, snapshot: any) => process.stdout.write(textDelta.value), onToolCallCreated: (toolCall: any) => process.stdout.write(`\n助手 > ${toolCall.type}\n\n`), onToolCallDelta: (toolCallDelta: any, snapshot: any) => { if (toolCallDelta.type === 'code_interpreter') { if (toolCallDelta.code_interpreter.input) { process.stdout.write(toolCallDelta.code_interpreter.input); } if (toolCallDelta.code_interpreter.outputs) { process.stdout.write('\n输出 >\n'); toolCallDelta.code_interpreter.outputs.forEach((output: any) => { if (output.type === 'logs') { process.stdout.write(`\n${output.logs}\n`); } }); } } }, }; const threadId = 'your-thread-id'; const assistantId = 'your-assistant-id'; helper.streamAssistantResponses(threadId, assistantId, eventHandlers); // 自动化函数调用 const tools = [ { type: 'function', function: { function: (args: { location: string; time: Date }) => { // 获取天气的实际实现 return getWeather(args); }, parse: (args: string) => { // 解析函数参数的实际实现 return parseFunction(args); }, parameters: { type: 'object', properties: { location: { type: 'string' }, time: { type: 'string', format: 'date-time' }, }, }, }, }, ]; const toolRunner = helper.runTools('gpt-3.5-turbo', [{ role: 'user', content: '这周的天气如何?' }], tools); console.log('Tool Runner:', toolRunner); })(); /** * 假设的获取天气的函数 * @param args - 包含位置和时间的参数 * @returns 天气信息 */ function getWeather(args: { location: string; time: Date }) { // 这里是获取天气的逻辑 return `The weather in ${args.location} at ${args.time} is sunny.`; } /** * 假设的解析函数参数的函数 * @param args - 字符串形式的参数 * @returns 解析后的参数对象 */ function parseFunction(args: string) { // 这里是解析函数参数的逻辑 const parsedArgs = JSON.parse(args); return { location: parsedArgs.location, time: new Date(parsedArgs.time), }; }
结论
本文介绍了如何利用 OpenAI API 的结构化输出解析助手和流式响应功能,通过使用 Zod 模式自动解析和验证 JSON 响应,展示了数学问题和查询函数的解析示例。此外,还讲解了如何实现流式响应,实时处理来自 Assistant 的响应,并通过 runTools
方法自动化函数调用,提供了一个综合示例,帮助开发者更高效地使用 OpenAI API。