지난번에 만들었던 gpt 채팅앱을 디벨롭 시키고자
OpenAI에 langchain 을 붙여 웹서비스를 구현해보았다.
gpt를 사용해보면 전문 지식이 필요한 작업에 대한 특정 답변을 제공하는 데 어려움을 겪을 때가 있다.
langchain은 LLM과 애플리케이션의 통합을 간소화하도록 설계된 SDK이다. 쉽게 말하면 언어모델 학습시 사용된 데이터 말고 그 외의 새로운 데이터를 인식할 수 있는데 능동적으로 다른 기능과 연동하여 추가적인 결과를 낸다.
내가 사용한 모듈 중 하나는 Agents, memory 이다.
const tools = [new GoogleCustomSearch()];
채팅 화면부터 살펴보면 UI 는 이러하다.
질문에 대한 답변을 하기 위해
Langchain에서 제공하는 AgentExecutor의 작동 과정을 살펴보자!
[chain/start][1:chain:AgentExecutor]
: 유저의 인풋이 들어오면 체인이 시작된다. 여기서 체인은 일련의 과정들을 의미한다.
[lim/start][1:chain:AgentExecutor > 2:llm:ChatOpenAI]
: 첫 번째로 실행되는 것은 ChatOpenAI라는 도구이다. 이 도구는 OpenAI를 활용하여 유저의 인풋을 해석한다.
AI 배경 컨텍스트를 'You are a helpful smart assistant. knows every area' 로 설정해줬기 때문에 이를 바탕으로 답변을 해줄 것이다.
[lim/end][1:chain:AgentExecutor > 2:llm:ChatOpenAI]
: ChatOpenAI의 작동이 끝난 후 결과를 반환한다.
additional_kwargs / function_call / arguments를 보면 google-custom-search라고 적힌것과 유저 인풋을 영어로 넘겨주는 것을 볼 수 있다. google-custom-search 가 langchain agents tool 로 사용하고자 하는 도구이기 때문에 구글에 검색해서 결과를 도출할 것을 기대할 수 있다.
[agents/actions][1:chain:AgentExecutor]
: 다음으로 AgentExecutor가 다음 작업을 결정한다. google-custom-search라는 도구를 사용하기로 결정한다.
[tool/start] ([1:chain:AgentExecutor > 3:tool:GoogleCustomSearch])
: google-custom-search 도구가 시작된다. 유저의 인풋을 바탕으로 검색을 수행한다
[tool/end][1:chain:AgentExecutor > 3:tool:GoogleCustomSearch]
: 검색 작업이 끝나면 결과를 반환한다. 유저가 검색한 내용을 구글에 검색한 내용들이 보인다.
[lim/start][1:chain:AgentExecutor > 4:llm:ChatOpenAI] : 마지막으로, ChatOpenAI가 다시 한번 작동하여 검색 결과를 유저의 인풋과 함께 처리한다.
8. [lim/end][1:chain:AgentExecutor > 4:llm:ChatOpenAI]
: ChatOpenAI의 작동이 끝나면 최종 결과를 반환한다
9. [chain/end][1:chain:AgentExecutor]
: 모든 과정이 끝나면 체인이 종료되고 최종적으로 유저에게 보여줄 output을 뽑아낸다.
위 과정을 거쳐서 AgentExecutor는 유저의 인풋을 가장 효과적으로 처리하고 결과를 반환한다!
작성한 api 코드를 살펴보자 (자세한 내용은 주석으로 처리했습니다)
// api/chat.ts
export const runtime = 'edge';
//Vercel의 메시지 형식을 Langchain의 메시지 형식으로 변환
const convertVercelMessageToLangChainMessage = (message: VercelChatMessage) => {
if (message.role === 'user') {
//유저 인풋
return new HumanMessage(message.content);
} else if (message.role === 'assistant') {
//AI가 응답한 내용
return new AIMessage(message.content);
} else {
return new ChatMessage(message.content, message.role);
}
};
//AI에게 해야 할 일을 알려주는 배경 컨텍스트
const PREFIX_TEMPLATE = `You are a helpful smart assistant. knows every area.`;
export default async function POST(req: NextRequest) {
try {
const body = await req.json();
//유저와 어시스턴트의 메시지만 필터링
const messages = (body.messages ?? []).filter(
(message: VercelChatMessage) =>
message.role === 'user' || message.role === 'assistant'
);
//중간 결과를 반환
const returnIntermediateSteps = body.show_intermediate_steps;
//이전 메시지와 현재 메시지의 내용을 분리
const previousMessages = messages
.slice(0, -1)
.map(convertVercelMessageToLangChainMessage);
const currentMessageContent = messages[messages.length - 1].content;
//AgentExecutor가 인풋을 처리하는 데 도움을 주는 외부 도구 (GoogleCustomSearch) 작성
const tools = [new GoogleCustomSearch()];
//OpenAI의 GPT-4 모델을 이용하는 ChatOpenAI 객체를 생성
const chat = new ChatOpenAI({
modelName: 'gpt-4',
temperature: 0.5,
});
//사용자의 인풋을 처리하고 적절한 출력을 생성하는 역할
const executor = await initializeAgentExecutorWithOptions(tools, chat, {
agentType: 'openai-functions', //사용할 에이전트의 타입지정
verbose: true, //상세한 로깅을 활성화
returnIntermediateSteps, //중간 결과를 반환할지 여부를 지정
memory: new BufferMemory({ //이전의 채팅 히스토리를 저장
memoryKey: 'chat_history', //메모리의 키를 지정
chatHistory: new ChatMessageHistory(previousMessages),
//이전 메시지를 ChatMessageHistory 객체로 변환하여 저장
returnMessages: true, //메시지 반환 여부를 지정
outputKey: 'output', //출력키 지정
}),
agentArgs: {
//에이전트에 전달할 인수를 설정.
prefix: PREFIX_TEMPLATE, //챗봇의 행동 지시
},
});
//현재 메시지를 처리하고 결과를 반환
const result = await executor.call({
input: `${currentMessageContent}`,
});
//중간 결과를 반환하거나 StreamingTextResponse를 반환
if (returnIntermediateSteps) {
return NextResponse.json(
{ output: result.output, intermediate_steps: result.intermediateSteps },
{ status: 200 }
);
} else {
const textEncoder = new TextEncoder();
const fakeStream = new ReadableStream({
async start(controller) {
for (const character of result.output) {
controller.enqueue(textEncoder.encode(character));
await new Promise((resolve) => setTimeout(resolve, 20));
}
controller.close();
},
});
return new StreamingTextResponse(fakeStream);
}
} catch (e: any) {
return NextResponse.json({ error: e.message }, { status: 500 });
}
}
이렇게 작성한 코드를 바탕으로 챗봇을 구현했다!
내 벨로그에 대한 것도 가져온다구 !!! 넘 신기함
제목생성 기능은 langchain 을 사용해줄 필요성을 느끼지 못해
단순 openAI API 로만 구현하였다.
원하는 주제를 입력하면 그와 관련된 제목을 지어주는 기능이다.
복사도 가능함!
스크립트 생성 기능에서는 챗봇처럼 langchain agents tool로 google custom search 를 붙여주었다. 아무래도 스토리를 지어낼라면 관련 내용에 대해 알아야 한다고 생각했기 때문이다.
이 스크립트를 만들어 주기 위해 이렇게 구글에 검색하는 것을 볼 수 있고요~
일부러 답변 시간을 좀 줄이기 위해 한국어로 번역하도록 설정은 따로 안했다...
요즘 AI를 활용한 서비스들이 많이 나오는데
직접 langchain까지 적용한 서비스를 만들어 보니 감회가 새롭다 ..
너무 재밌는 개발세계..
깃허브에서 자세한 코드 보실 수 있습니다!
https://github.com/waterbinnn/gpt-ai-helper
혹시나 클론해서 사용해보실 분들은 .env 파일에 API 키 입력해서 사용해 주세요!OPENAI_API_KEY = '' GOOGLE_API_KEY = '' GOOGLE_CSE_ID = ''