Skip to content

Commit 172c76d

Browse files
lesebclaudefranciscojavierarceo
authored
feat(interactions): add previous_interaction_id for multi-turn conversations (#5669)
# What does this PR do? Adds `previous_interaction_id` support to the Google Interactions API, enabling server-side conversation state for multi-turn interactions. Without this, every request starts fresh and ADK agents cannot maintain context across turns. When a request includes `previous_interaction_id`, the server fetches the stored conversation history from the SQL backend, prepends it (including the model's prior response) to the new input, and calls inference with the full context. Both streaming and non-streaming responses are persisted for future chaining. Key additions: - `InteractionsStore` class (`src/ogx/providers/utils/interactions/`) following the `ResponsesStore` pattern with `AuthorizedSqlStore` and access control policy - `InteractionsConfig` with `SqlStoreReference` for configurable SQL backend (SQLite/Postgres) - `_build_messages()` method for conversation history reconstruction, supporting multi-hop chaining - Updated Google Interactions API conformance score (39.0% → 40.2%) ## Test Plan 55 unit tests pass (37 existing + 5 new `previous_interaction_id` + 13 shape/passthrough tests). All 39 pre-commit hooks pass. ```bash # Unit tests uv run pytest tests/unit/providers/inline/interactions/ -x --tb=short -q # End-to-end validation with Google GenAI SDK OLLAMA_URL=http://localhost:11434/v1 uv run --extra starter ogx stack run starter --port 8321 uv run scripts/test_interactions_api.py --base-url http://localhost:8321 --model ollama/llama3.2:3b ``` Test 5 validates conversation chaining: creates a first interaction establishing context ("My name is Alice"), then chains from it with `previous_interaction_id` and verifies the model remembers the name. --------- Signed-off-by: Sébastien Han <seb@redhat.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
1 parent 267b1e9 commit 172c76d

21 files changed

Lines changed: 486 additions & 77 deletions

File tree

client-sdks/stainless/openapi.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11847,6 +11847,11 @@ components:
1184711847
type: array
1184811848
- type: 'null'
1184911849
description: Tools (function declarations) available to the model.
11850+
previous_interaction_id:
11851+
anyOf:
11852+
- type: string
11853+
- type: 'null'
11854+
description: ID of a previous interaction to continue the conversation from.
1185011855
stream:
1185111856
anyOf:
1185211857
- type: boolean

docs/docs/api-google-interactions/conformance.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ This documentation is auto-generated from the Google Interactions API specificat
2121

2222
| Metric | Value |
2323
|--------|-------|
24-
| **Overall Coverage Score** | 39.0% |
25-
| **Total Items Implemented** | 32/82 |
26-
| **Total Missing** | 50 |
24+
| **Overall Coverage Score** | 40.2% |
25+
| **Total Items Implemented** | 33/82 |
26+
| **Total Missing** | 49 |
2727

2828
## Section Scores
2929

@@ -36,7 +36,7 @@ Sections are sorted by coverage score (lowest first, needing most attention).
3636
| Endpoints | 25.0% | 1 | 4 | 3 |
3737
| GenerationConfig | 30.0% | 3 | 10 | 7 |
3838
| Usage | 30.0% | 3 | 10 | 7 |
39-
| Request Properties | 53.8% | 7 | 13 | 6 |
39+
| Request Properties | 61.5% | 8 | 13 | 5 |
4040
| Streaming Events | 71.4% | 5 | 7 | 2 |
4141
| Response Properties | 88.9% | 8 | 9 | 1 |
4242

@@ -188,14 +188,15 @@ These properties are in the OGX implementation but not in the Google spec:
188188

189189
### Request Properties
190190

191-
**Score:** 53.8% · **Implemented:** 7/13
191+
**Score:** 61.5% · **Implemented:** 8/13
192192

193193
<details>
194-
<summary>Implemented (7)</summary>
194+
<summary>Implemented (8)</summary>
195195

196196
- `generation_config`
197197
- `input`
198198
- `model`
199+
- `previous_interaction_id`
199200
- `response_modalities`
200201
- `stream`
201202
- `system_instruction`
@@ -204,10 +205,9 @@ These properties are in the OGX implementation but not in the Google spec:
204205
</details>
205206

206207
<details>
207-
<summary>Missing (6)</summary>
208+
<summary>Missing (5)</summary>
208209

209210
- `background`
210-
- `previous_interaction_id`
211211
- `response_format`
212212
- `response_mime_type`
213213
- `service_tier`

docs/docs/providers/interactions/inline_builtin.mdx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,18 @@ title: inline::builtin
1010

1111
Serves the Google Interactions API (POST /v1alpha/interactions) so that Google GenAI SDK and ADK clients can call OGX without code changes. Requests are translated to OpenAI Chat Completions and routed to whichever inference provider is configured (vLLM, Ollama, OpenAI, Bedrock, etc.). When the provider is Gemini, non-streaming requests are forwarded directly to the native /v1beta/interactions endpoint, avoiding double translation.
1212

13+
## Configuration
14+
15+
| Field | Type | Required | Default | Description |
16+
|-------|------|----------|---------|-------------|
17+
| `store` | `SqlStoreReference` | No | table_name='interactions' backend='sql_default' | SQL store for persisting interaction state (conversation chaining). |
18+
| `store.table_name` | `str` | No | | Name of the table to use for the SqlStore |
19+
| `store.backend` | `str` | No | | Name of backend from storage.backends |
20+
1321
## Sample Configuration
1422

1523
```yaml
16-
{}
24+
store:
25+
table_name: interactions
26+
backend: sql_default
1727
```

docs/static/experimental-ogx-spec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7453,6 +7453,11 @@ components:
74537453
type: array
74547454
- type: 'null'
74557455
description: Tools (function declarations) available to the model.
7456+
previous_interaction_id:
7457+
anyOf:
7458+
- type: string
7459+
- type: 'null'
7460+
description: ID of a previous interaction to continue the conversation from.
74567461
stream:
74577462
anyOf:
74587463
- type: boolean

docs/static/google-interactions-coverage.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"google_spec": "docs/static/google-interactions-spec.json",
33
"spec_version": "v1beta",
44
"summary": {
5-
"overall_score": 39.0,
5+
"overall_score": 40.2,
66
"total_google_items": 82,
7-
"total_implemented": 32,
8-
"total_missing": 50
7+
"total_implemented": 33,
8+
"total_missing": 49
99
},
1010
"sections": [
1111
{
@@ -26,21 +26,21 @@
2626
{
2727
"section": "Request Properties",
2828
"google_total": 13,
29-
"implemented": 7,
30-
"missing_count": 6,
31-
"score": 53.8,
29+
"implemented": 8,
30+
"missing_count": 5,
31+
"score": 61.5,
3232
"supported": [
3333
"generation_config",
3434
"input",
3535
"model",
36+
"previous_interaction_id",
3637
"response_modalities",
3738
"stream",
3839
"system_instruction",
3940
"tools"
4041
],
4142
"missing": [
4243
"background",
43-
"previous_interaction_id",
4444
"response_format",
4545
"response_mime_type",
4646
"service_tier",

docs/static/stainless-ogx-spec.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11847,6 +11847,11 @@ components:
1184711847
type: array
1184811848
- type: 'null'
1184911849
description: Tools (function declarations) available to the model.
11850+
previous_interaction_id:
11851+
anyOf:
11852+
- type: string
11853+
- type: 'null'
11854+
description: ID of a previous interaction to continue the conversation from.
1185011855
stream:
1185111856
anyOf:
1185211857
- type: boolean

scripts/google_interactions_coverage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def _collect_streaming_events(spec: dict[str, Any]) -> list[str]:
132132
"system_instruction",
133133
"generation_config",
134134
"tools",
135+
"previous_interaction_id",
135136
"stream",
136137
"response_modalities",
137138
}

scripts/test_interactions_api.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def run_non_streaming_basic(client: genai.Client, model: str) -> None:
5555
input="What is 2+2? Reply with just the number.",
5656
)
5757

58-
assert len(interaction.id) > 0, f"ID should not be empty, got: {interaction.id}"
58+
assert interaction.id.startswith("interaction-"), f"ID should start with 'interaction-', got: {interaction.id}"
5959
assert interaction.status == "completed", f"Status should be 'completed', got: {interaction.status}"
6060
assert len(interaction.outputs) > 0, "Expected at least one output"
6161
assert interaction.outputs[0].type == "text", f"Output type should be 'text', got: {interaction.outputs[0].type}"
@@ -234,9 +234,36 @@ def run_tool_calling(client: genai.Client, model: str) -> None:
234234
print(" PASSED")
235235

236236

237+
def run_previous_interaction_id(client: genai.Client, model: str) -> None:
238+
"""Test 6: Conversation chaining via previous_interaction_id."""
239+
print("Test 6: Conversation chaining (previous_interaction_id)...")
240+
241+
# First interaction: establish context
242+
first = client.interactions.create(
243+
model=model,
244+
input="My name is Alice. Remember it.",
245+
)
246+
assert first.status == "completed"
247+
assert first.id.startswith("interaction-")
248+
print(f" First response: {first.outputs[0].text[:80]}")
249+
250+
# Second interaction: chain from first, ask about the context
251+
second = client.interactions.create(
252+
model=model,
253+
input="What is my name?",
254+
previous_interaction_id=first.id,
255+
)
256+
assert second.status == "completed"
257+
text = second.outputs[0].text.lower()
258+
assert "alice" in text, f"Expected 'alice' in chained response, got: {second.outputs[0].text}"
259+
260+
print(f" Chained response: {second.outputs[0].text[:80]}")
261+
print(" PASSED")
262+
263+
237264
def run_streaming_basic(client: genai.Client, model: str) -> None:
238-
"""Test 6: Streaming interaction with SSE events."""
239-
print("Test 6: Streaming basic interaction...")
265+
"""Test 7: Streaming interaction with SSE events."""
266+
print("Test 7: Streaming basic interaction...")
240267

241268
stream = client.interactions.create(
242269
model=model,
@@ -269,7 +296,7 @@ def run_streaming_basic(client: genai.Client, model: str) -> None:
269296
full_text = "".join(text_parts)
270297
assert len(full_text) > 0, "Streaming should produce text"
271298
assert interaction_id is not None, "Should have received an interaction ID"
272-
assert len(interaction_id) > 0, f"ID should not be empty, got: {interaction_id}"
299+
assert interaction_id.startswith("interaction-"), f"ID should start with 'interaction-', got: {interaction_id}"
273300

274301
print(f" Events: {event_types}")
275302
print(f" Full text: {full_text[:80]}")
@@ -316,6 +343,7 @@ def main():
316343
run_non_streaming_multi_turn,
317344
run_non_streaming_generation_config,
318345
run_tool_calling,
346+
run_previous_interaction_id,
319347
run_streaming_basic,
320348
]
321349

src/ogx/distributions/ci-tests/config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ providers:
203203
interactions:
204204
- provider_id: builtin
205205
provider_type: inline::builtin
206+
config:
207+
store:
208+
table_name: interactions
209+
backend: sql_default
206210
messages:
207211
- provider_id: builtin
208212
provider_type: inline::builtin

src/ogx/distributions/ci-tests/run-with-postgres-store.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ providers:
203203
interactions:
204204
- provider_id: builtin
205205
provider_type: inline::builtin
206+
config:
207+
store:
208+
table_name: interactions
209+
backend: sql_default
206210
messages:
207211
- provider_id: builtin
208212
provider_type: inline::builtin

0 commit comments

Comments
 (0)