Functional Agents
Koog supports defining agent logic as plain functions (lambdas). This is the simplest approach when you need custom control flow without the overhead of graph definitions.
When to Use Functional Agents
| Use Functional Agents | Use Graph-Based Agents |
|---|---|
| Simple, linear workflows | Complex branching logic |
| Quick prototyping | Reusable, named strategies |
| One-off scripts | Visual workflow representation |
| Full Kotlin/Java control flow | Standardized ReAct loops |
Kotlin Functional Agent
import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.core.tools.ToolRegistry
import ai.koog.agents.ext.simple.simpleOpenAIExecutor
import ai.koog.agents.ext.llm.OpenAIModels
suspend fun main() {
val apiKey = System.getenv("OPENAI_API_KEY")
val toolRegistry = ToolRegistry {
tool(searchWeb)
tool(sendEmail)
}
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(apiKey),
llmModel = OpenAIModels.Chat.GPT4o,
systemPrompt = "You are a helpful research assistant.",
toolRegistry = toolRegistry
)
// Run with custom logic
val result = agent.run { context ->
// Access the LLM directly
val initialResponse = context.llm.chat("Find recent articles about Kotlin Multiplatform")
// Process the response
if (initialResponse.contains("search")) {
// Manually invoke a tool
val searchResult = context.tools.execute("searchWeb", mapOf("query" to "Kotlin Multiplatform 2024"))
context.llm.chat("Summarize these results: $searchResult")
} else {
initialResponse
}
}
println(result)
}
Lambda-Based Approach
Define the agent's behavior as a suspend lambda:
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(apiKey),
llmModel = OpenAIModels.Chat.GPT4o,
toolRegistry = toolRegistry
)
// Simple lambda
val result = agent.run("What is 2 + 2?")
// Lambda with context
val result = agent.runWithContext { ctx ->
val response1 = ctx.chat("What are the top 3 programming languages?")
val response2 = ctx.chat("Now compare their concurrency models")
"Summary: $response1\n\nComparison: $response2"
}
Java Functional Agent
In Java, use the builder pattern with a lambda:
import ai.koog.agents.core.agent.AIAgent;
import ai.koog.agents.core.tools.ToolRegistry;
import ai.koog.agents.ext.simple.SimpleOpenAIExecutorKt;
import ai.koog.agents.ext.llm.OpenAIModels;
public class FunctionalAgent {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("OPENAI_API_KEY");
var toolRegistry = ToolRegistry.builder()
.tool(new SearchTool())
.tool(new SummaryTool())
.build();
var agent = AIAgent.builder(
SimpleOpenAIExecutorKt.simpleOpenAIExecutor(apiKey),
OpenAIModels.Chat.GPT4o
)
.systemPrompt("You are a research assistant.")
.toolRegistry(toolRegistry)
.build();
// Simple run
String result = agent.run("Search for articles about AI agents");
System.out.println(result);
// Functional run with context
String result2 = agent.runWithContext(ctx -> {
String search = ctx.chat("Find articles about Koog framework");
String summary = ctx.chat("Summarize the key points");
return "Search: " + search + "\n\nSummary: " + summary;
});
System.out.println(result2);
}
}
Multi-Step Functional Workflow
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(apiKey),
llmModel = OpenAIModels.Chat.GPT4o,
toolRegistry = toolRegistry,
systemPrompt = "You are a data analyst."
)
val result = agent.runWithContext { ctx ->
// Step 1: Gather data
val rawData = ctx.tools.execute("fetchData", mapOf(
"source" to "database",
"query" to "SELECT * FROM sales WHERE date > '2024-01-01'"
))
// Step 2: Analyze with LLM
val analysis = ctx.chat("""
Analyze this sales data and identify trends:
$rawData
""")
// Step 3: Generate visualization config
val chartConfig = ctx.chat("""
Based on this analysis, suggest a chart configuration:
$analysis
""")
// Step 4: Final summary
ctx.chat("""
Create a executive summary combining:
- Analysis: $analysis
- Visualization: $chartConfig
""")
}
Error Handling in Functional Agents
val result = agent.runWithContext { ctx ->
try {
val response = ctx.chat("Process this data")
response
} catch (e: Exception) {
ctx.chat("The previous step failed with: ${e.message}. Please provide a fallback response.")
}
}
Combining with Strategies
Functional agents can use predefined strategies as a fallback:
import ai.koog.agents.core.strategy.strategy
import ai.koog.agents.core.strategy.nodeLLMRequest
import ai.koog.agents.core.strategy.nodeExecuteTool
import ai.koog.agents.core.strategy.nodeFinish
// Define a fallback strategy
val fallbackStrategy = strategy("fallback") {
val llm by nodeLLMRequest()
val tool by nodeExecuteTool()
edge(nodeStart forwardTo llm)
edge(llm forwardTo tool onToolCall { true })
edge(llm forwardTo nodeFinish onAssistantMessage { true })
edge(tool forwardTo llm)
}
val agent = AIAgent(
promptExecutor = simpleOpenAIExecutor(apiKey),
llmModel = OpenAIModels.Chat.GPT4o,
toolRegistry = toolRegistry,
strategy = fallbackStrategy
)
// Use functional approach for simple cases
val simpleResult = agent.run("Hello!")
// Strategy handles complex tool-using cases automatically
val complexResult = agent.run("Search for recent Kotlin news and summarize the top 3 articles")
Comparison: Functional vs Graph-Based
// Functional: Full Kotlin control flow
val result = agent.runWithContext { ctx ->
val answer = ctx.chat("Is this task complex? Answer yes or no.")
if (answer.contains("yes")) {
val details = ctx.chat("Break down the task into steps")
ctx.chat("Execute step by step: $details")
} else {
ctx.chat("Just do it directly")
}
}
// Graph-based: Declarative routing
val strategy = strategy("adaptive") {
val classify by nodeLLMRequest()
val complexPath by nodeExecuteTool()
val simplePath by nodeLLMRequest()
edge(nodeStart forwardTo classify)
edge(classify forwardTo complexPath onToolCall { true })
edge(classify forwardTo simplePath onAssistantMessage { msg ->
!msg.content.contains("complex")
})
edge(complexPath forwardTo simplePath)
edge(simplePath forwardTo nodeFinish)
}
Best Practices
- Start simple — Use
agent.run()for single-turn interactions - Use
runWithContextfor multi-step workflows that need custom logic - Prefer graph-based when the workflow is reusable or needs visual representation
- Handle errors — Wrap tool calls in try/catch for resilience
- Limit iterations — Set
maxIterationsto prevent runaway loops