
우리 프로젝트에서 OpenAI의 gpt를 사용하면서 프론트엔드에서 더욱 효과적으로 사용하고자 하였고, 이에 대해 배우면서 나도 안까먹고자 정리한다.
ai sdk, open ai 적용하기 npm i ai openai
OPEN_API_KEY = xxxxxxxx
app/api/chat/route.ts)import OpenAI from 'openai'
import { OpenAIStream, StreamingTextResponse } from 'ai'
const openai = new OpenAI({
apiKey: process.env.OEPN_API_KEY
});
// .env에 들어있는 OPEN AI의 api key 등록하기
export const dynamic = 'force-dynamic';
// Next.js application에서 동적 라우팅 구현시에 사용
export async function POST((req: Request) {
const { messages } = await req.json();
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages,
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(steam);
app/page.tsx)use client';
import { useChat } from 'ai/react';
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(m => (
<div key={m.id} className="whitespace-pre-wrap">
{m.role === 'user' ? 'User: ' : 'AI: '}
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
원래 OpenAI에서 볼 수 있었던 function calling 부분은 Deprecated 곧 더이상 사용되지 않는다고 API reference에 나와있다. 그렇지만 해당 파트가 없어져야하는 것은 아니다. tools를 사용하여 OpenAI 에서의 OpenAI의 function callling처럼 보여질 수 있다.
function calling은 함수를 호출하는 것

1. Automatic Function Execution 과 유사하나 function call을 보내면서 사용자에게 파악한 의도를 알려주고, 함수 실행간 진행상황을 사용자에게 보고한다.
2. Automatic Function Execution with Intent & Progress 와 거의 유사하지만 차이점은 function_call 과정시에 사용자의 의도를 기반으로 함수 선택 및 확인 후에 서버에서 함수를 실행하게 되는 차이점이 있다.다음처럼 3가지 UX Flow를 따르면서 사용자 경험이 확대된다. 이는 프롬프트를 어떻게 작성하느냐에 따라 UX Flow를 선택할 수 있을 것이다. 가령 예시는 다음과 같다.
messages: [
{
role: 'system',
content:"의도작성하기"
}];
1번흐름 : "사용자가 요청했을 때 사용자의 의도를 예상하여 함수를 실행하고 결과를 종합하여 응답해라",
2번흐름 : "사용자가 요청했을 때 사용자의 의도를 예상해서 사용자에게 의도와 함수 실행 과정을 전달하고 결과를 종합하여 응답해라"
3번흐름 : "사용자가 요청했을 때 의도를 예상하여 실행하지말고 예상한 의도를 사용자에게 확인받은 후 함수를 실행하며 진행과정을 전달한다. 그리고 함수 실행 결과값으로 결과를 종합하여 응답해라"
❗ 프롬프트 엔지니어링 구현시 이 방법은 정답이아닙니다,,, 실험하는 과정을 꼭 진행해야합니다.import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
//2024.05.02 기준으로 function call은 레거시가 되고 대신 Tools의 이름으로 등록이 되어있습니다. 주의바랍니다.
import type { ChatCompletionTool } from 'openai/resources/index.mjs';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const functions: Array<ChatCompetionTool> = [
{
type: 'function',
function: {
name: 'get_current_weather',
description: 'Get the current weather in a given location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string'
description: 'The city and state, e.g. San Francisco, CA'
},
},
required: ['location'],
},
},
}. //...
export async function POST(req: Request) {
const { messages } = await. req.json()
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: [
{
role: 'system',
content: "Do not assume what values to use for functions. If the user's request is unclear, ask for clarification"
},
...messages,
],
tools: functions,
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}
experimental_onFunctionCall 콜백이 된다. 이 콜백은 OpenAIStream에 전달된다.createFunctionCallMessages를 사용하여 중첩된 OpenAI 호출에서 메시지 컨텍스트를 구성하고 함수 호출 메시지를 생성할 수 다.const stream = OpenAIStream(response, {
experimental_onFunctionCall: async (
{ name, arguments: args },
createFunctionCallMessages,
) => {
if (name === 'get_current_weather') {
const weatherData = {
temperature: 20,
unit: args.format === 'celsius' ? 'C' : 'F',
};
const newMessages = createFunctionCallMessages(weatherData);
return openai.chat.completions.create({
messages: [...messages, ...newMessages],
stream: true,
model: 'gpt-3.5-turbo-0613',
functions,
});
}
},
});
useCompletion과 useChat 훅에 experimental_onFunctionCall 핸들러를 전달할 수 있다. 이 콜백은 서버에서 함수 호출을 처리하지 않고 클라이언트로 스트리밍할 때 호출된다.const functionCallHandler: FunctionCallHandler = async (
chatMessages,
functionCall,
) => {
if (functionCall.name === 'get_current_weather') {
if (functionCall.arguments) {
const parsedFunctionCallArguments = JSON.parse(functionCall.arguments);
console.log(parsedFunctionCallArguments);
}
const temperature = Math.floor(Math.random() * (100 - 30 + 1) + 30);
const weather = ['sunny', 'cloudy', 'rainy', 'snowy'][
Math.floor(Math.random() * 4)
];
const functionResponse: ChatRequest = {
messages: [
...chatMessages,
{
id: generateId(),
name: 'get_current_weather',
role: 'function' as const,
content: JSON.stringify({
temperature,
weather,
info: 'This data is randomly generated and came from a fake weather API!',
}),
},
],
};
return functionResponse;
}
};
const { messages, input, handleInputChange, handleSubmit } = useChat({
experimental_onFunctionCall: functionCallHandler,
});
get_current_weather 함수를 호출하면, OpenAI API는 호출할 함수의 이름과 인자를 포함한 특별한 형식의 메시지를 반환된다. 클라이언트에서는 이 핸들러를 통해 함수 호출을 처리하고 채팅을 적절히 조작할 수 있다.