基于Vercel AI与OpenAI Functions,实现Next.js客户端与服务端互通的最简方案
Next.js API中Vercel AI与OpenAI Functions桥接客户端函数的最简方案
核心思路
通过Vercel AI的流式响应机制,在服务端拦截OpenAI返回的函数调用指令,将指令传递给客户端执行;客户端完成函数调用后,把结果回传给服务端,服务端再将结果注入对话历史,继续与OpenAI交互。
实现步骤
1. 服务端API路由(app/api/chat/route.ts)
处理对话请求,拦截OpenAI的函数调用指令并转发给客户端:
import OpenAI from 'openai'; import { OpenAIStream, StreamingTextResponse } from 'ai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); export const runtime = 'edge'; // 定义客户端可调用的函数描述 const functions = [ { name: 'get_user_location', description: '获取当前用户的地理位置', parameters: { type: 'object', properties: {}, required: [], }, }, ]; export async function POST(req: Request) { const { messages } = await req.json(); const response = await openai.chat.completions.create({ model: 'gpt-3.5-turbo-0613', stream: true, messages, functions, function_call: 'auto', }); // 拦截函数调用指令,转为JSON格式发送给客户端 const stream = OpenAIStream(response, { async onFunctionCall({ name, arguments: args }) { return JSON.stringify({ type: 'function_call', name, args, }); }, }); return new StreamingTextResponse(stream); }
2. 客户端组件(app/page.tsx)
监听服务端流,解析函数调用指令并执行客户端函数,将结果回传:
'use client'; import { useChat } from 'ai/react'; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit, addMessage } = useChat({ api: '/api/chat', onResponse: async (response) => { const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); // 尝试解析是否为函数调用指令 try { const data = JSON.parse(chunk); if (data.type === 'function_call') { let result; // 匹配并执行对应客户端函数 if (data.name === 'get_user_location') { result = await getCurrentLocation(); } // 将函数结果以function角色消息回传给服务端 addMessage({ role: 'function', name: data.name, content: JSON.stringify(result), }); } } catch (e) { // 非函数调用指令,按普通文本处理 } } }, }); // 客户端本地函数:获取地理位置 const getCurrentLocation = async (): Promise<{ latitude: number; longitude: number }> => { return new Promise((resolve, reject) => { if (!navigator.geolocation) { reject(new Error('浏览器不支持地理定位')); return; } navigator.geolocation.getCurrentPosition( (position) => { resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, }); }, (error) => reject(error) ); }); }; return ( <div className="max-w-md mx-auto p-4"> <div className="space-y-2"> {messages.map((m) => ( <div key={m.id} className={`p-2 rounded ${m.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'}`}> {m.role === 'function' ? ( <div> <strong>{m.name} 结果:</strong> {m.content} </div> ) : ( m.content )} </div> ))} </div> <form onSubmit={handleSubmit} className="mt-4"> <input type="text" value={input} onChange={handleInputChange} placeholder="问我需要获取你位置的问题..." className="w-full p-2 border rounded" /> <button type="submit" className="mt-2 w-full p-2 bg-blue-500 text-white rounded"> 发送 </button> </form> </div> ); }
关键桥接点说明
- 服务端通过
onFunctionCall回调捕获OpenAI的函数调用请求,将其序列化为特定格式的JSON字符串插入流式响应 - 客户端通过
onResponse监听流式返回,解析出函数调用指令后执行本地函数 - 客户端完成函数执行后,通过
addMessage将结果以function角色的消息发送回服务端,服务端会自动将该消息加入对话历史,继续与OpenAI完成后续交互
内容的提问来源于stack exchange,提问作者CoolkcaH
相关产品推荐
相关产品推荐

