FunctionalAIAgent: 단계별로 싱글 런(single-run) 에이전트 구축하기
FunctionalAIAgent는 간단한 루프로 제어할 수 있는 가벼운 비그래프형(non‑graph) 에이전트입니다. 다음과 같은 경우에 사용하세요:
- 커스텀 루프 내에서 LLM을 한 번 또는 몇 번 호출하고 싶을 때
- LLM 호출 사이에 선택적으로 도구(tool)를 호출하고 싶을 때
- 전체 전략 그래프를 구축하지 않고 최종 값(문자열, 데이터 클래스 등)을 반환하고 싶을 때
이 가이드에서 다룰 내용:
- "Hello, World" FunctionalAIAgent 만들기
- 도구를 추가하고 에이전트가 호출하도록 설정하기
- 동작 관찰을 위한 기능(이벤트 핸들러) 추가하기
- 이력 압축(history compression)으로 컨텍스트 제어하기
- 일반적인 레시피, 주의 사항 및 FAQ 학습하기
1) 전제 조건
PromptExecutor(LLM과 실제로 통신하는 객체)가 필요합니다. 로컬 테스트를 위해 Ollama 실행기를 사용할 수 있습니다:
kotlin
val exec = simpleOllamaAIExecutor()또한 사용할 모델을 선택해야 합니다. 예:
kotlin
val model = OllamaModels.Meta.LLAMA_3_2준비가 끝났습니다. 이제 이 두 가지를 에이전트 팩토리에 주입해 보겠습니다.
2) 첫 번째 에이전트 (Hello, World)
목표: 사용자의 텍스트를 LLM에 보내고 단일 어시스턴트 메시지를 문자열로 반환합니다.
kotlin
val agent = functionalAIAgent<String, String>(
prompt = "You are a helpful assistant.",
promptExecutor = exec,
model = model
) { input ->
val responses = requestLLMMultiple(input)
responses.single().asAssistantMessage().content
}
val result = agent.run("Say hi in one sentence")
println(result)어떤 일이 일어나나요?
requestLLMMultiple(input)은 사용자 입력을 보내고 하나 이상의 어시스턴트 메시지를 받습니다.- 유일한 메시지의 내용(콘텐츠)을 반환합니다 (일반적인 원샷(one-shot) 흐름).
팁: 구조화된 데이터를 반환하려면 콘텐츠를 파싱하거나 Structured Data API를 사용하세요.
3) 도구 추가 (에이전트가 함수를 호출하는 방법)
목표: 모델이 도구를 통해 작은 장치를 조작할 수 있도록 합니다.
kotlin
class Switch {
private var on = false
fun on() { on = true }
fun off() { on = false }
fun isOn() = on
}
class SwitchTools(private val sw: Switch) {
fun turn_on() = run { sw.on(); "ok" }
fun turn_off() = run { sw.off(); "ok" }
fun state() = if (sw.isOn()) "on" else "off"
}
val sw = Switch()
val tools = ToolRegistry { tools(SwitchTools(sw).asTools()) }
val toolAgent = functionalAIAgent<String, String>(
prompt = "You're responsible for running a Switch device and perform operations on it by request.",
promptExecutor = exec,
model = model,
toolRegistry = tools
) { input ->
var responses = requestLLMMultiple(input)
while (responses.containsToolCalls()) {
val pending = extractToolCalls(responses)
val results = executeMultipleTools(pending)
responses = sendMultipleToolResults(results)
}
responses.single().asAssistantMessage().content
}
val out = toolAgent.run("Turn switch on")
println(out)
println("Switch is ${if (sw.isOn()) "on" else "off"}")작동 원리
containsToolCalls()는 LLM으로부터 도구 호출 메시지가 있는지 감지합니다.extractToolCalls(...)는 어떤 도구를 어떤 인자로 실행할지 읽어옵니다.executeMultipleTools(...)는ToolRegistry를 대상으로 도구들을 실행합니다.sendMultipleToolResults(...)는 결과를 다시 LLM에 보내고 다음 응답을 받습니다.
4) 기능(EventHandler)을 통한 동작 관찰
목표: 모든 도구 호출을 콘솔에 출력합니다.
kotlin
val observed = functionalAIAgent<String, String>(
prompt = "...",
promptExecutor = exec,
model = model,
toolRegistry = tools,
featureContext = {
install(EventHandler) {
onToolCallStarting { e -> println("Tool called: ${'
```}{e.tool.name}, args: ${'
```}{e.toolArgs}") }
}
}
) { input ->
var responses = requestLLMMultiple(input)
while (responses.containsToolCalls()) {
val pending = extractToolCalls(responses)
val results = executeMultipleTools(pending)
responses = sendMultipleToolResults(results)
}
responses.single().asAssistantMessage().content
}이러한 방식으로 설치할 수 있는 다른 기능으로는 스트리밍 토큰(streaming tokens) 및 트레이싱(tracing)이 있습니다. 사이드바의 관련 문서를 참조하세요.
5) 컨텍스트 제어 (이력 압축)
대화가 길어지면 모델의 컨텍스트 창을 초과할 수 있습니다. 토큰 사용량을 확인하여 이력(history)을 압축할 시점을 결정하세요:
kotlin
var responses = requestLLMMultiple(input)
while (responses.containsToolCalls()) {
if (latestTokenUsage() > 100_000) {
compressHistory()
}
val pending = extractToolCalls(responses)
val results = executeMultipleTools(pending)
responses = sendMultipleToolResults(results)
}모델과 프롬프트 크기에 적절한 임계값을 사용하세요.
일반적인 레시피
- 구조화된 출력(Structured output) 반환
- LLM에게 JSON 형식을 요청하고 이를 파싱하거나, Structured Data API를 사용하세요.
- 도구 입력 검증
- 도구 함수 내에서 검증을 수행하고 명확한 에러 메시지를 반환하세요.
- 요청당 하나의 에이전트 인스턴스
- 각 에이전트 인스턴스는 한 번에 하나의 실행만 처리합니다. 동시성이 필요한 경우 새 인스턴스를 생성하세요.
- 커스텀 출력(Output) 타입
functionalAIAgent<String, MyResult>와 같이 타입을 변경하고 루프에서 데이터 클래스를 반환하세요.
문제 해결 및 주의 사항
- “Agent is already running”
FunctionalAIAgent는 동일한 인스턴스에서 동시 실행을 방지합니다. 여러 코루틴에서 하나의 인스턴스를 공유하지 마세요. 실행마다 새로운 에이전트를 생성하거나 완료될 때까지 기다리세요.
- 비어 있거나 예상치 못한 모델 출력
- 시스템 프롬프트를 확인하세요. 중간 응답을 출력해 보세요. 퓨샷(few-shot) 예제를 추가하는 것을 고려해 보세요.
- 루프가 끝나지 않음
- 도구 호출이 없을 때 루프를 빠져나가는지 확인하세요. 안전을 위해 가드(guard)나 타임아웃을 추가하세요.
- 컨텍스트 오버플로
latestTokenUsage()를 모니터링하고compressHistory()를 호출하세요.
레퍼런스 (요약)
생성자
kotlin
fun <Input, Output> functionalAIAgent(
promptExecutor: PromptExecutor,
agentConfig: AIAgentConfig,
toolRegistry: ToolRegistry = ToolRegistry.EMPTY,
loop: suspend AIAgentFunctionalContext.(input: Input) -> Output
): AIAgent<Input, Output>
fun <Input, Output> functionalAIAgent(
promptExecutor: PromptExecutor,
toolRegistry: ToolRegistry = ToolRegistry.EMPTY,
prompt: String = "",
model: LLModel = OpenAIModels.Chat.GPT4o,
featureContext: FeatureContext.() -> Unit = {},
func: suspend AIAgentFunctionalContext.(input: Input) -> Output,
): AIAgent<Input, Output>주요 타입
FunctionalAIAgent<Input, Output>AIAgentFunctionalContextAIAgentConfig/AIAgentConfigBasePromptExecutorToolRegistryFeatureContext및 기능 인터페이스
소스 코드 참조: agents/agents-core/src/commonMain/kotlin/ai/koog/agents/core/agent/FunctionalAIAgent.kt
