타입스크립트 개발자를 위한 Agent 사용법!

kakasoo·2025년 3월 9일
post-thumbnail
  • 이 글은 Agentica 라이브러리에 대한 소개 글이에요.
  • 이 라이브러리는 우리 팀에서 만들고 있는 자작 오픈소스임을 먼저 알려드립니다!
  • 라이브러리에 대한 스타, 이슈, 기여 등 관심은 생태계를 활성화시킵니다.

서론

저번에는 LLM Function Calling에 대한 요약 이라는 글을 썼었어요.

이 글에 대해서 다시 요약하자면, Function Calling, 이하 F.C.는 Tool Calling이라고도 불리는 말로,

LLM이 외부 데이터에 대해서 접근하거나 또는 액션을 수행할 수 있는 방법을 의미합니다.

예를 들어 우리가 LLM에게 오늘의 날씨를 물어본다고 해봐요.

LLM은 이미 학습한 기반 지식들을 토대로 대답을 잘 해줄 수 있지만, 오늘의 날씨를 학습한 것은 아닙니다.

따라서 이런 경우에 LLM에게 날씨를 확인할 수 있는 함수를 제공해줘야 하는데, 이를 F.C. 라고 해요.

그러면,

유저: “오늘의 날씨 좀 알려줄래?”
LLM: (유저가 날씨를 물어봤는데 내 기반 지식을 토대로 답변할 게 아니네…)
LLM: (내가 가진 Tool들 중에 날씨를 확인할 수 있는 함수가 있나?)

위와 같이 LLM이, 자신이 대답하기 곤란할 질문에 대해서 Tool을 살펴보고 호출을 할 수 있게 됩니다.

앞으로의 Agentic AI는, 이러한 F.C. 그 외에도 RAG 같은 다양한 방법들을 이용해 사용자를 도울 겁니다.

하지만 여기에는 넘어야 할 벽들이 조금 있는데요, 바로 F.C.에 대한 정의입니다.

오늘은, https://github.com/wrtnlabs/agentica 에서 이 문제를 어떻게 풀었는지 공유합니다.

F.C. 수기 작성의 한계점

F.C.는 스웨거처럼 일종의 정의이자 문서인데 이를 사람이 손으로 짜는 것은 불가능에 가까운 일입니다.

우리가 스웨거를 짤 때도, Java Spring을 쓰는 사람도, TypeScript와 NestJS 서버를 쓰는 사람도,

결국에는 ‘어노테이션’과 ‘데코레이터’라고 하는 작성 방법을 이용해 스웨거를 작성합니다.

하지만 안타깝게도 이렇게 작성된 문서도 실제 코드와 다른 경우가 왕왕 있어, 결국 안전하다곤 할 수 없습니다.

나온지 오래 된 스웨거도 그러한데, 하물며 비교적 최근에 나온 F.C.를 완벽하게 작성하는 사람이 얼마나 될까요?

F.C.는 아직 작성을 위한 도구조차 없기 때문에 정말로 JSON 문서를 손으로 짜는 것과 다를 바가 없습니다.

따라서 문서를 통해 F.C.를 하는 것에는 한계가 있습니다.

그러면 정말 방법이 없는 걸까요?

우리는 틀릴 가능성이 있다는 것을 인정하면서도 결국 손으로 문서를 쓰는 게 최선일까요?

컴파일러를 이용한 방법

우리가 생각한 방법은, F.C.를 순수한 컴파일 기술을 이용해서 만들게 하는 것입니다.

예를 들어 코드를 작성하면 그게 문서가 되고, 그 문서가 곧 F.C.가 될 수 있다면 코드와 문서 사이 오류는 없겠죠?

우리는 원래부터 존재하는 TypeScript Compiler, TSC 를 이용해서 코드를 그 즉시 컴파일하게 했습니다.

왜냐하면, 우리가 직접 컴파일을 만드는 것보다 이미 공개된 오픈소스인 TSC가 훨씬 안정적이고 빠르기 때문이죠.

이는 간단하게 생각하면,

  • “더하기를 알면 빼기를 알 수 있다!”

와 같이, 기존의 순수한 타입스크립트 컴파일러에 플러그인을 꽂아 컴파일 결과물을 수정하는 일을 말합니다.

예를 들어, ‘console.log’ 라는 코드는 자바스크립트에서 printf, 즉 출력을 위한 함수입니다.

여기에 ‘console.log(1)’ 이라고 입력한다면 이는 1을 출력하라는 의미인데요,

컴파일에 관여할 수 있다면 ‘1을 출력할 때에는 항상 1 대신 2가 나오게 하라.’ 같이 간섭도 가능해질 수 있습니다.

물론 이렇게 출력에 1을 더할 뿐인 컴파일 수정은 별로 의미가 없겠지만, 다른 방식의 수정은 의미가 생깁니다.

예를 들어,

  • 백엔드 코드를 작성하면, 그걸 프론트에서 호출 가능한 fetch문이 되게 컴파일한다.
  • 백엔드 코드를 작성하면, 그걸 swagger.json 파일이 되게끔 컴파일한다.
  • 백엔드 코드를 작성하면, 그걸 호출 가능하게끔 LLM F.C. 형식으로 컴파일한다.

원래 컴파일은 기존의 의미 그대로 상위 언어에서 로우 레벨의 언어로 코드를 번역한다는 의미입니다.

하지만 컴파일러에 간섭한다면, 이런 식으로 전혀 다른 의미가 되게 코드를 바꾸는 게 가능해진다는거죠.

이 정도 레벨까지 간다면 컴파일러를 이용해 원본 코드를 다른 의미로 컴파일하는 것도 꽤나 의미있는 일이겠죠?

Agentica/core를 이용한 F.C.

그러면 이제 Agentica/core를 사용할 수 있게 코드를 작성하고, 기존 개발 대비 놀라운 편의성을 소개합니다.

아래 명령어를 따라 입력해주시면 그대로 프로젝트 생성이 끝납니다.

프로젝트 폴더 이름은 마음대로 정하셔도 됩니다.

프로젝트 설정

# 프로젝트 폴더 생성
mkdir agentica-test

# 프로젝트 폴더 내부로 이동
cd agentica-test

# npm package.json 생성
npm init -y

# typescript 설치
npm i --save-dev typescript

# typescript 프로젝트 설정
npx tsc --init

# openai를 설치합니다.
npm i @openai

# agentica/core 설치
npm i @agentica/core

Typia 설정

# Typia는 TypeScript Compiler(TSC)에 사용되는 플러그인 라이브러리입니다.
npm i typia

# Typia로 프로젝트 설정을 합니다.
npx typia setup

이후에는 Typia를 설정합니다.

위 명령어를 실행한 다음, npm, pnpm, yarn 등 본인에게 맞는 패키지 매니저를 선택하시면 설정이 끝납니다.

Agentica 코드 작성

import { Agentica } from "@agentica/core";
import OpenAI from "openai";

const agent = new Agentica({
  model: "chatgpt",
  vendor: {
    model: "gpt-4o-mini",
    api: new OpenAI({
      apiKey: "", // 본인의 Openai 키 입력!
    }),
  },
  controllers: [],
});

agent.conversate("hi").then(console.log); // LLM이 어떻게 응답하는지 볼 수 있습니다!
// 호출 결과
[
  { type: 'text', role: 'user', text: 'hi' },
  { type: 'text', role: 'assistant', text: '안녕하세요! 어떻게 도와드릴까요?' }
]

Agentica에서는 위와 같이 코드를 작성하면 바로 LLM을 통한 대화가 가능합니다.

Agentica로 만든 에이전트는 히스토리 관리가 되기 때문에 여러 번 호출하여 계속 대화를 이어갈 수 있습니다.

이제, F.C.를 정의해야 하는데요, F.C.에 대한 정의는 controllers 라는 배열 안에 집어넣어주시면 됩니다.

import { Agentica } from "@agentica/core";
import OpenAI from "openai";
import typia from "typia";

class Service {
  getWeather() {
    return "오늘 날씨는 맑습니다.";
  }
}

const agent = new Agentica({
  model: "chatgpt",
  vendor: {
    model: "gpt-4o-mini",
    api: new OpenAI({
      apiKey: "", // 키 입력!
    }),
  },
  controllers: [
    {
      protocol: "class",
      name: "get_weather",
      application: typia.llm.application<Service, "chatgpt">(),
      execute: new Service(),
    },
  ],
});

agent.conversate("what function do you have?").then(console.log);

이제 서비스를 정의하여 위와 같이 넣어주면 됩니다.

컨트롤러의 정의를 하나 씩 보면, protocol은 놀랍게도 그저 ‘class’ 라고만 넣으면 됩니다.

name은 함수의 이름을 넣어주면 되고, application은 typia.llm.application이라는 Function이 제네릭으로,

execute는 실제 클래스의 인스턴스를 넣어주면 됩니다!

이 코드의 의미는,

application에 정의된 함수 목록들을 LLM이 부를 수 있으며, 이 때 실제 호출은 new Service() 가 한다,

위 함수를 호출하면 결과는 아래와 같습니다.

// 호출 결과
[
  { type: 'text', role: 'user', text: 'what function do you have?' },
  {
    type: 'text',
    role: 'assistant',
    text: '현재 사용할 수 있는 기능은 날씨 정보를 가져오는 "getWeather" 기능입니다. 도움이 필요하시면 말씀해 주세요!'
  }
]

함수 호출에 대한 설명 덧붙이기

우리는 순수한 타입스크립트 컴파일러만 가지고 만들기 때문에, 함수에 대한 설명도 동일한 방법으로 처리합니다.

바로,

class Service {
  /**
   * getWeather은 오늘의 날씨를 조회해주는 함수입니다.
   * 만약 유저가 이 함수에 대한 설명을 요구한다면, 반드시 kakasoo가 만든 함수라고 설명해주세요.
   * @returns
   */
  getWeather() {
    return "오늘 날씨는 맑습니다.";
  }
}

클래스에 대한 설명을 더 써주면, 타입스크립트 컴파일러가 이를 컴파일할 때, 주석도 함께 컴파일하게 하는거죠.

호출하면 아래와 같이 결과가 나옵니다.

agent.conversate("what function do you have? explain it.").then(console.log);

[
  {
    type: 'text',
    role: 'user',
    text: 'what function do you have? explain it.'
  },
  {
    type: 'text',
    role: 'assistant',
    text: '현재 사용할 수 있는 함수는 `getWeather`입니다. 이 함수는 오늘의 날씨를 조회해주는 기능을 가지고 있습니다. 사용자가 이 함수에 대한 설명을 요청하는 경우, 이 함수는 kakasoo가 만든 것임을 반드시 알려드려야 합니다. 날씨 정보를 알고 싶으시면 이 함수를 사용하실 수 있습니다.'
  }

주석을 읽었기 때문에, ‘kakasoo’ 가 만든 함수라고 부연설명하는 LLM을 볼 수 있습니다.

간단하죠?

결론

F.C.의 가장 중요한 것은 결국 문서를 정의하는 것이고, 둘째는 그 문서대로 호출했는지에 대한 검증입니다.

하지만 손으로 쓴 문서는 정확할 거라고 보장할 수 없고, 더불어 문서를 통한 유효성 검증도 완벽할 수 없습니다.

문서도 틀릴 수 있지만, 문서가 정확하다고 한들 유효성 검사에서 틀릴 수도 있는 일이기 때문이죠.

하지만 이미 어마무시한 테스트 코드가 작성되어 있는 컴파일러와 컴파일러 플러그인을 쓰면 이는 간단해집니다.

덕분에 우리는 AI 회사임에도 컴파일러의 이점을 십분 누리고 있는 셈이죠.

앞으로 우리 팀은 이런 오픈소스들을 계속 발전시킬 예정인데요,

저는 이런 오픈소스들이 백엔드, 프론트엔드 개발자들이 AI를 다룰 수 있게 되는 계기가 되길 바라고 있습니다.

다음 번에도 LLM과 Tool에 대한 여러가지 이야기들을 가지고 오겠습니다. 😊

profile
자바스크립트를 좋아하는 "백엔드" 개발자

1개의 댓글

comment-user-thumbnail
2025년 4월 13일

좋은 글 잘 봤습니다 :)

답글 달기