KG-596 Fix thought signature on tool call with gemini 3.0#1317
KG-596 Fix thought signature on tool call with gemini 3.0#1317kpavlov merged 2 commits intoJetBrains:developfrom
Conversation
ce5398f to
a579dc6
Compare
kpavlov
left a comment
There was a problem hiding this comment.
Thank you, @mltheuser, very useful fix. Brings value
I added a couple of comments on test scenarios, and making it more defensive.
antoniibelyshev
left a comment
There was a problem hiding this comment.
Thank you! Looks almost ready. I saw a couple issues which might need attention.
- The reasoning inside a function call seems to be duplicated: it is added as a separate
Reasoningmessage, and then also as part of the function call. - Probably, it's better to not introduce a new field in the
metaInfo, and just use theResoningmessages as delimiters indicating where a new api response starts.
Not sure about both of the points though. I'm open for a discussion here.
8f282db to
7f9432a
Compare
7f9432a to
58d66c7
Compare
|
Thank you everyone for the in-depth review. I have switched to the pattern where we store thought signatures in reasoning messages as suggested by @antoniibelyshev and I have extended the tests as suggested by @kpavlov (your point about verifying the structural logic (especially for parallel calls) was also spot on) |
antoniibelyshev
left a comment
There was a problem hiding this comment.
Thank you! LGTM
…1317) ## Motivation and Context Related to: [KG-596](https://youtrack.jetbrains.com/projects/KG/issues/KG-596) Gemini 3.0 models enforce stricter validation of [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) for function calls. When the model returns parallel tool calls, only the *first* call in a turn receives a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429). On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors. **The Problem:** The [GoogleLLMClient](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#101-844) wasn't preserving [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject. ## How It's Solved 1. **Preserve [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429):** When processing model responses, we now extract the [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) from `GooglePart.FunctionCall` and store it in `Message.Tool.Call.metaInfo.metadata`. When building subsequent requests, we restore it. 2. **Correctly batch parallel tool calls/results:** The [createGoogleRequest](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#280-477) function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429), it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single [model](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#775-805) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53) and results into a single [user](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53), as the API requires. 3. **Clean, idiomatic implementation:** The buffering logic uses a [when](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable. ## Breaking Changes None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated --------- Co-authored-by: Malte Heuser <malte.heuser@ing.com>
…1317) ## Motivation and Context Related to: [KG-596](https://youtrack.jetbrains.com/projects/KG/issues/KG-596) Gemini 3.0 models enforce stricter validation of [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) for function calls. When the model returns parallel tool calls, only the *first* call in a turn receives a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429). On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors. **The Problem:** The [GoogleLLMClient](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#101-844) wasn't preserving [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject. ## How It's Solved 1. **Preserve [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429):** When processing model responses, we now extract the [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) from `GooglePart.FunctionCall` and store it in `Message.Tool.Call.metaInfo.metadata`. When building subsequent requests, we restore it. 2. **Correctly batch parallel tool calls/results:** The [createGoogleRequest](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#280-477) function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429), it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single [model](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#775-805) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53) and results into a single [user](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53), as the API requires. 3. **Clean, idiomatic implementation:** The buffering logic uses a [when](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable. ## Breaking Changes None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated --------- Co-authored-by: Malte Heuser <malte.heuser@ing.com>
|
Is there somewhere official that the public can track what will be release? We are keen to see this fix being part of the next release as this is stopping us from using gemini 3 models with tool calls. |
## Motivation and Context Related to: [KG-596](https://youtrack.jetbrains.com/projects/KG/issues/KG-596) Gemini 3.0 models enforce stricter validation of [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) for function calls. When the model returns parallel tool calls, only the *first* call in a turn receives a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429). On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors. **The Problem:** The [GoogleLLMClient](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#101-844) wasn't preserving [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject. ## How It's Solved 1. **Preserve [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429):** When processing model responses, we now extract the [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) from `GooglePart.FunctionCall` and store it in `Message.Tool.Call.metaInfo.metadata`. When building subsequent requests, we restore it. 2. **Correctly batch parallel tool calls/results:** The [createGoogleRequest](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#280-477) function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429), it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single [model](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#775-805) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53) and results into a single [user](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53), as the API requires. 3. **Clean, idiomatic implementation:** The buffering logic uses a [when](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable. ## Breaking Changes None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated --------- Co-authored-by: Malte Heuser <malte.heuser@ing.com>
## Motivation and Context Related to: [KG-596](https://youtrack.jetbrains.com/projects/KG/issues/KG-596) Gemini 3.0 models enforce stricter validation of [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) for function calls. When the model returns parallel tool calls, only the *first* call in a turn receives a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429). On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors. **The Problem:** The [GoogleLLMClient](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#101-844) wasn't preserving [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject. ## How It's Solved 1. **Preserve [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429):** When processing model responses, we now extract the [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) from `GooglePart.FunctionCall` and store it in `Message.Tool.Call.metaInfo.metadata`. When building subsequent requests, we restore it. 2. **Correctly batch parallel tool calls/results:** The [createGoogleRequest](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#280-477) function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429), it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single [model](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#775-805) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53) and results into a single [user](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53), as the API requires. 3. **Clean, idiomatic implementation:** The buffering logic uses a [when](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable. ## Breaking Changes None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated --------- <!-- Thank you for opening a pull request! Please add a brief description of the proposed change here. Also, please tick the appropriate points in the checklist below. --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> ## Breaking Changes <!-- Will users need to update their code or configurations? --> --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring - [ ] CI/CD changes - [ ] Dependencies update #### Checklist - [ ] The pull request has a description of the proposed change - [ ] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [ ] The pull request uses **`develop`** as the base branch - [ ] Tests for the changes have been added - [ ] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [ ] An issue describing the proposed change exists - [ ] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated Co-authored-by: Malte Heuser <mltheuser@gmail.com> Co-authored-by: Malte Heuser <malte.heuser@ing.com>
## Motivation and Context Related to: [KG-596](https://youtrack.jetbrains.com/projects/KG/issues/KG-596) Gemini 3.0 models enforce stricter validation of [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) for function calls. When the model returns parallel tool calls, only the *first* call in a turn receives a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429). On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors. **The Problem:** The [GoogleLLMClient](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#101-844) wasn't preserving [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject. ## How It's Solved 1. **Preserve [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429):** When processing model responses, we now extract the [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429) from `GooglePart.FunctionCall` and store it in `Message.Tool.Call.metaInfo.metadata`. When building subsequent requests, we restore it. 2. **Correctly batch parallel tool calls/results:** The [createGoogleRequest](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#280-477) function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a [thoughtSignature](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#401-429), it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single [model](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClient.kt#775-805) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53) and results into a single [user](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) role [GoogleContent](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/commonMain/kotlin/ai/koog/prompt/executor/clients/google/models/GoogleGenerateContent.kt#48-53), as the API requires. 3. **Clean, idiomatic implementation:** The buffering logic uses a [when](file:///Users/ku76uh/Developer/jetbrains/fork/koog/prompt/prompt-executor/prompt-executor-clients/prompt-executor-google-client/src/jvmTest/kotlin/ai/koog/prompt/executor/clients/google/GoogleLLMClientTest.kt#59-74) expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable. ## Breaking Changes None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models. --- #### Type of the changes - [ ] New feature (non-breaking change which adds functionality) - [x] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Tests improvement - [ ] Refactoring #### Checklist - [x] The pull request has a description of the proposed change - [x] I read the [Contributing Guidelines](https://github.com/JetBrains/koog/blob/main/CONTRIBUTING.md) before opening the pull request - [x] The pull request uses **`develop`** as the base branch - [x] Tests for the changes have been added - [x] All new and existing tests passed ##### Additional steps for pull requests adding a new feature - [x] An issue describing the proposed change exists - [x] The pull request includes a link to the issue - [ ] The change was discussed and approved in the issue - [ ] Docs have been added / updated --------- Co-authored-by: Malte Heuser <malte.heuser@ing.com>
Motivation and Context
Related to: KG-596
Gemini 3.0 models enforce stricter validation of thoughtSignature for function calls. When the model returns parallel tool calls, only the first call in a turn receives a thoughtSignature. On subsequent turns, the API expects this signature to be echoed back exactly. Without proper handling, multi-turn agentic conversations with parallel tools fail with cryptic API errors.
The Problem: The GoogleLLMClient wasn't preserving thoughtSignature across turns, and wasn't correctly re-grouping parallel tool calls/results when constructing requests—leading to malformed conversation structures that newer Gemini models reject.
How It's Solved
Preserve thoughtSignature: When processing model responses, we now extract the thoughtSignature from
GooglePart.FunctionCalland store it inMessage.Tool.Call.metaInfo.metadata. When building subsequent requests, we restore it.Correctly batch parallel tool calls/results: The createGoogleRequest function now uses a buffering strategy to re-group interleaved messages. The key insight: if a tool call has a thoughtSignature, it starts a new turn; if it doesn't, it's a parallel call in the same turn. This lets us batch calls into a single model role GoogleContent and results into a single user role GoogleContent, as the API requires.
Clean, idiomatic implementation: The buffering logic uses a when expression to make the three states explicit (new turn, starting fresh, parallel call), keeping the code readable and maintainable.
Breaking Changes
None. This is a backward-compatible fix that enables correct behavior with Gemini 3.0+ while remaining compatible with earlier models.
Type of the changes
Checklist
developas the base branchAdditional steps for pull requests adding a new feature