Add koog module: a tool-calling agent, a strategy graph with a Java node and the Spring Boot starter on Koog 1.3.0, tested against a scripted OpenAI-compatible endpoint
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6FHj1ptP5Fej2eFDo
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
agent.run("Is checkout healthy?") -> checkout is DEGRADED
|
||||
|
||||
HTTP calls the model endpoint received: 2
|
||||
POST /v1/chat/completions
|
||||
POST /v1/chat/completions
|
||||
|
||||
Request 1 - messages:
|
||||
system: You are an on-call assistant. Use the tools to answer; never guess a status.
|
||||
user: Is checkout healthy?
|
||||
Request 1 - tools Koog derived from ServiceTools:
|
||||
{"name":"getServiceStatus","description":"Returns the current status of one service: HEALTHY, DEGRADED or DOWN","parameters":{"type":"object","properties":{"service":{"description":"Service name, for example checkout","type":"string"}},"required":["service"]}}
|
||||
{"name":"error_rate","description":"Returns the percentage of failed requests for one service over the last N minutes","parameters":{"type":"object","properties":{"service":{"description":"Service name, for example checkout","type":"string"},"minutes":{"description":"Window in minutes","type":"integer"}},"required":["service","minutes"]}}
|
||||
|
||||
Request 2 - messages (the tool call and its result are now in the history):
|
||||
{"role":"system","content":"You are an on-call assistant. Use the tools to answer; never guess a status."}
|
||||
{"role":"user","content":"Is checkout healthy?"}
|
||||
{"role":"assistant","tool_calls":[{"id":"call_1","function":{"name":"getServiceStatus","arguments":"{\"service\":\"checkout\"}"},"type":"function"}]}
|
||||
{"role":"tool","content":"\"DEGRADED\"","tool_call_id":"call_1"}
|
||||
@@ -0,0 +1,23 @@
|
||||
Graph, as MermaidDiagramGenerator prints it:
|
||||
|
||||
---
|
||||
title: redacting_single_run
|
||||
---
|
||||
stateDiagram
|
||||
state "redact" as redact
|
||||
state "callLLM" as callLLM
|
||||
state "executeTool" as executeTool
|
||||
state "sendToolResult" as sendToolResult
|
||||
|
||||
[*] --> redact
|
||||
redact --> callLLM
|
||||
callLLM --> executeTool : transformed
|
||||
callLLM --> [*] : transformed
|
||||
executeTool --> sendToolResult
|
||||
sendToolResult --> executeTool : transformed
|
||||
sendToolResult --> [*] : transformed
|
||||
|
||||
The user typed: My key is sk-ABCDEFGH12345678. Is checkout healthy?
|
||||
The model got: My key is [REDACTED]. Is checkout healthy?
|
||||
Answer: checkout is DEGRADED
|
||||
Secret present in any request body: false
|
||||
@@ -0,0 +1,17 @@
|
||||
1. The tool throws IllegalArgumentException("Unknown service: nope")
|
||||
agent.run returned: There is no such service.
|
||||
LLM calls: 2
|
||||
what the model was told: tool: Tool with name 'getServiceStatus' failed to execute due to the error: Unknown service: nope!
|
||||
|
||||
2. The model sends {"minutes":"soon"} for an int parameter
|
||||
what the model was told: Tool with name 'error_rate' failed to parse arguments due to the error: Failed to parse literal '"soon"' as an int value at element: $.primitive | JSON input: "soon"
|
||||
|
||||
3. The model calls restartService, which is not registered
|
||||
the history sent back, last two messages:
|
||||
{"role":"assistant","content":"{\"tool_call_id\":\"c1\",\"tool_name\":\"restartService\",\"tool_args\":{\"service\":\"checkout\"}}"}
|
||||
{"role":"user","content":"{\"tool_call_id\":\"c1\",\"tool_name\":\"restartService\",\"tool_result\":\"Tool with name 'restartService' not found in the tool registry. Use one of the available tools.\"}"}
|
||||
|
||||
4. The model answers every request with another tool call, maxIterations(6)
|
||||
AIAgentMaxNumberOfIterationsReachedException
|
||||
LLM calls made before it stopped: 3
|
||||
default maxIterations of an agent you do not configure: 50
|
||||
@@ -0,0 +1,4 @@
|
||||
run 1: first
|
||||
run 2: second
|
||||
messages sent on run 2: 1
|
||||
{"role":"user","content":"hello again"}
|
||||
@@ -0,0 +1,11 @@
|
||||
PromptExecutor beans the starter registered (only OpenAI is configured):
|
||||
multiLLMPromptExecutor
|
||||
openAIExecutor
|
||||
|
||||
GET /ask?q=How bad are checkout errors?
|
||||
-> checkout fails 4.2% of requests over 15 minutes
|
||||
Model endpoint saw: [POST /v1/chat/completions, POST /v1/chat/completions]
|
||||
Tool argument the model sent: minutes=15 (an int, parsed by Koog)
|
||||
|
||||
context.getBean(PromptExecutor.class) with one provider configured:
|
||||
NoUniqueBeanDefinitionException: expected single matching bean but found 2: multiLLMPromptExecutor,openAIExecutor
|
||||
@@ -0,0 +1,4 @@
|
||||
ai.koog.openai.api-key is empty, ai.koog.openai.enabled=true is set:
|
||||
startup failed: true
|
||||
cause: NoSuchBeanDefinitionException
|
||||
message: No qualifying bean of type 'ai.koog.prompt.executor.model.PromptExecutor' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("openAIExecutor")}
|
||||
@@ -0,0 +1,11 @@
|
||||
kotlin-stdlib on the classpath (correct, Kotlin BOM imported first):
|
||||
kotlin-stdlib-2.3.10.jar
|
||||
|
||||
Same classpath with kotlin-stdlib 1.9.25, which is what the Spring Boot 3.5.16 BOM picks when it comes first:
|
||||
kotlin-stdlib-1.9.25.jar
|
||||
|
||||
$ java -cp <that classpath> com.ankurm.koog.TrapMain
|
||||
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/time/Instant
|
||||
at ai.koog.utils.time.KoogClock.<clinit>(KoogClock.kt:27)
|
||||
at ai.koog.prompt.executor.model.PromptExecutorBuilder.openAI$default(PromptExecutorBuilder.kt:85)
|
||||
at ai.koog.prompt.executor.model.PromptExecutorBuilder.openAI(PromptExecutorBuilder.kt)
|
||||
Reference in New Issue
Block a user