Next.js: Open AI와 Langchain 활용해서 AI Helper 만들기

숩딩·2023년 12월 21일
0

AI Helper 만들기

지난번에 만들었던 gpt 채팅앱을 디벨롭 시키고자
OpenAI에 langchain 을 붙여 웹서비스를 구현해보았다.

기능

  • 채팅 : 일상적인 대화, 구글 검색 후 답변 도출
  • 제목 생성 : 블로그 게시물 또는 기타 콘텐츠에 대해 창의적이고 관련성 있는 제목을 생성
  • 스크립트 생성 : 프레젠테이션, 비디오 등에 대한 스크립팅 가능 , 구글 검색 후 결과 도출

Langchain 이란

Langchain 공식문서

gpt를 사용해보면 전문 지식이 필요한 작업에 대한 특정 답변을 제공하는 데 어려움을 겪을 때가 있다.
langchain은 LLM과 애플리케이션의 통합을 간소화하도록 설계된 SDK이다. 쉽게 말하면 언어모델 학습시 사용된 데이터 말고 그 외의 새로운 데이터를 인식할 수 있는데 능동적으로 다른 기능과 연동하여 추가적인 결과를 낸다.

내가 사용한 모듈 중 하나는 Agents, memory 이다.

  • Agents
    : 도구를 선택해서 특정 동작을 하도록 지원하는 모듈이다.
    이걸 tool이라는 것을 이용해서 작성하는데
    GoogleCustomSearch를 툴로 사용하였다.(사용하기 위해서는 GOOGLE_API_KEY와 GOOGLE_CSE_ID 키가 있어야 함)
const tools = [new GoogleCustomSearch()];
  • memory
    : 체인 실행 사이에 이전 상황을 메모리화

Langchain AgentExecutor의 작동 과정

채팅 화면부터 살펴보면 UI 는 이러하다.

질문에 대한 답변을 하기 위해
Langchain에서 제공하는 AgentExecutor의 작동 과정을 살펴보자!

  1. [chain/start][1:chain:AgentExecutor]
    : 유저의 인풋이 들어오면 체인이 시작된다. 여기서 체인은 일련의 과정들을 의미한다.

  2. [lim/start][1:chain:AgentExecutor > 2:llm:ChatOpenAI]
    : 첫 번째로 실행되는 것은 ChatOpenAI라는 도구이다. 이 도구는 OpenAI를 활용하여 유저의 인풋을 해석한다.
    AI 배경 컨텍스트를 'You are a helpful smart assistant. knows every area' 로 설정해줬기 때문에 이를 바탕으로 답변을 해줄 것이다.

  3. [lim/end][1:chain:AgentExecutor > 2:llm:ChatOpenAI]
    : ChatOpenAI의 작동이 끝난 후 결과를 반환한다.
    additional_kwargs / function_call / arguments를 보면 google-custom-search라고 적힌것과 유저 인풋을 영어로 넘겨주는 것을 볼 수 있다. google-custom-search 가 langchain agents tool 로 사용하고자 하는 도구이기 때문에 구글에 검색해서 결과를 도출할 것을 기대할 수 있다.

  4. [agents/actions][1:chain:AgentExecutor]
    : 다음으로 AgentExecutor가 다음 작업을 결정한다. google-custom-search라는 도구를 사용하기로 결정한다.

  5. [tool/start] ([1:chain:AgentExecutor > 3:tool:GoogleCustomSearch])
    : google-custom-search 도구가 시작된다. 유저의 인풋을 바탕으로 검색을 수행한다

  6. [tool/end][1:chain:AgentExecutor > 3:tool:GoogleCustomSearch]
    : 검색 작업이 끝나면 결과를 반환한다. 유저가 검색한 내용을 구글에 검색한 내용들이 보인다.

  7. [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/chat.ts 코드

작성한 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 });
  }
}

이렇게 작성한 코드를 바탕으로 챗봇을 구현했다!
내 벨로그에 대한 것도 가져온다구 !!! 넘 신기함


AI 제목, 스크립트 생성 기능

제목생성 기능은 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 = ''
profile
Front-End Developer ✨

0개의 댓글