Skip to content

Commit 02f2956

Browse files
feat: Enable configurable model streaming support (#1202)
Original PR: #1161, close #1099 https://github.com/user-attachments/assets/95847d85-5062-4755-93b8-0bb07fae9602 - Extends Kagent's ADK OpenAI provider to properly handle streaming with `LmResponse` partial field, aggregating results, and function calling. I tested that this works with other model providers as well like Gemini. - Stream partial events from ADK to the event queue for frontend updates on LLM response chunks, but also propagate `Event.partial` at the A2A `Message` level so that the task store will filter them out and avoid persisting partial chunks (only save `TaskStatusUpdateEvent` that contain complete chunks). - An **alternative** would be to mark the partial field at the metadata at the `Task` level in `_get_context_metadata` and reconstruct the event in the client side, but currently we do it at the Message level since it makes more sense. There is a discussion from ADK Python on this regarding their A2A-ADK integration. - Update the UI to include a toggle to select whether to use model streaming. Note that model streaming is different from A2A streaming, the former streams "chunks" of text, the latter streams tasks. - Added e2e test that uses `stream: true` supported by the new MockLLM --------- Signed-off-by: Jet Chiang <pokyuen.jetchiang-ext@solo.io> Co-authored-by: Azeez Syed <syedazeez337@gmail.com>
1 parent a535857 commit 02f2956

38 files changed

Lines changed: 726 additions & 81 deletions

go/api/v1alpha2/agent_types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ type DeclarativeAgentSpec struct {
8686
// +optional
8787
ModelConfig string `json:"modelConfig,omitempty"`
8888
// Whether to stream the response from the model.
89-
// If not specified, the default value is true.
89+
// If not specified, the default value is false.
9090
// +optional
91-
Stream *bool `json:"stream,omitempty"`
91+
Stream bool `json:"stream,omitempty"`
9292
// +kubebuilder:validation:MaxItems=20
9393
Tools []*Tool `json:"tools,omitempty"`
9494
// A2AConfig instantiates an A2A server for this agent,

go/api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 0 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/config/crd/bases/kagent.dev_agents.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9691,7 +9691,7 @@ spec:
96919691
stream:
96929692
description: |-
96939693
Whether to stream the response from the model.
9694-
If not specified, the default value is true.
9694+
If not specified, the default value is false.
96959695
type: boolean
96969696
systemMessage:
96979697
description: SystemMessage is a string specifying the system message

go/internal/adk/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ type AgentConfig struct {
253253
SseTools []SseMcpServerConfig `json:"sse_tools"`
254254
RemoteAgents []RemoteAgentConfig `json:"remote_agents"`
255255
ExecuteCode bool `json:"execute_code,omitempty"`
256+
Stream bool `json:"stream"`
256257
}
257258

258259
func (a *AgentConfig) UnmarshalJSON(data []byte) error {

go/internal/controller/translator/agent/adk_api_translator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent *v1al
531531
Instruction: systemMessage,
532532
Model: model,
533533
ExecuteCode: false && ptr.Deref(agent.Spec.Declarative.ExecuteCodeBlocks, false), //ignored due to this issue https://github.com/google/adk-python/issues/3921.
534+
Stream: agent.Spec.Declarative.Stream,
534535
}
535536

536537
for _, tool := range agent.Spec.Declarative.Tools {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
operation: translateAgent
2+
targetObject: basic-agent
3+
namespace: test
4+
objects:
5+
- apiVersion: v1
6+
kind: Secret
7+
metadata:
8+
name: openai-secret
9+
namespace: test
10+
data:
11+
api-key: c2stdGVzdC1hcGkta2V5 # base64 encoded "sk-test-api-key"
12+
- apiVersion: kagent.dev/v1alpha2
13+
kind: ModelConfig
14+
metadata:
15+
name: basic-model
16+
namespace: test
17+
spec:
18+
provider: OpenAI
19+
model: gpt-4o
20+
apiKeySecret: openai-secret
21+
apiKeySecretKey: api-key
22+
openAI:
23+
temperature: "0.7"
24+
maxTokens: 1024
25+
topP: "0.95"
26+
reasoningEffort: "low"
27+
defaultHeaders:
28+
User-Agent: "kagent/1.0"
29+
- apiVersion: kagent.dev/v1alpha2
30+
kind: Agent
31+
metadata:
32+
name: basic-agent
33+
namespace: test
34+
spec:
35+
type: Declarative
36+
declarative:
37+
description: A basic test agent
38+
systemMessage: You are a helpful assistant.
39+
modelConfig: basic-model
40+
stream: true
41+
deployment:
42+
resources:
43+
requests:
44+
cpu: 200m
45+
memory: 684Mi
46+
limits:
47+
cpu: 3000m
48+
memory: 2Gi
49+
tools: []

go/internal/controller/translator/agent/testdata/outputs/agent_with_code.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"type": "openai"
3535
},
3636
"remote_agents": null,
37-
"sse_tools": null
37+
"sse_tools": null,
38+
"stream": false
3839
},
3940
"manifest": [
4041
{
@@ -63,7 +64,7 @@
6364
},
6465
"stringData": {
6566
"agent-card.json": "{\"name\":\"agent_with_code\",\"description\":\"\",\"url\":\"http://agent-with-code.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[]}",
66-
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"http_tools\":null,\"sse_tools\":null,\"remote_agents\":null}"
67+
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"http_tools\":null,\"sse_tools\":null,\"remote_agents\":null,\"stream\":false}"
6768
}
6869
},
6970
{
@@ -132,7 +133,7 @@
132133
"template": {
133134
"metadata": {
134135
"annotations": {
135-
"kagent.dev/config-hash": "15800629975074137223"
136+
"kagent.dev/config-hash": "10194218485187346598"
136137
},
137138
"labels": {
138139
"app": "kagent",

go/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"type": "openai"
4242
},
4343
"remote_agents": null,
44-
"sse_tools": null
44+
"sse_tools": null,
45+
"stream": false
4546
},
4647
"manifest": [
4748
{
@@ -70,7 +71,7 @@
7071
},
7172
"stringData": {
7273
"agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[]}",
73-
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{\"MATH\":\"sk-test-api-key\"},\"timeout\":30,\"sse_read_timeout\":300},\"tools\":[\"k8s_get_resources\"]}],\"sse_tools\":null,\"remote_agents\":null}"
74+
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{\"MATH\":\"sk-test-api-key\"},\"timeout\":30,\"sse_read_timeout\":300},\"tools\":[\"k8s_get_resources\"]}],\"sse_tools\":null,\"remote_agents\":null,\"stream\":false}"
7475
}
7576
},
7677
{
@@ -139,7 +140,7 @@
139140
"template": {
140141
"metadata": {
141142
"annotations": {
142-
"kagent.dev/config-hash": "8768892159480624774"
143+
"kagent.dev/config-hash": "11246224890029939632"
143144
},
144145
"labels": {
145146
"app": "kagent",

go/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"type": "openai"
3838
},
3939
"remote_agents": null,
40-
"sse_tools": null
40+
"sse_tools": null,
41+
"stream": false
4142
},
4243
"manifest": [
4344
{
@@ -66,7 +67,7 @@
6667
},
6768
"stringData": {
6869
"agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[]}",
69-
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"k8s_get_resources\"]}],\"sse_tools\":null,\"remote_agents\":null}"
70+
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"k8s_get_resources\"]}],\"sse_tools\":null,\"remote_agents\":null,\"stream\":false}"
7071
}
7172
},
7273
{
@@ -135,7 +136,7 @@
135136
"template": {
136137
"metadata": {
137138
"annotations": {
138-
"kagent.dev/config-hash": "12589128794779205881"
139+
"kagent.dev/config-hash": "3664580906535457241"
139140
},
140141
"labels": {
141142
"app": "kagent",

go/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"url": "http://specialist-agent.test:8080"
3636
}
3737
],
38-
"sse_tools": null
38+
"sse_tools": null,
39+
"stream": false
3940
},
4041
"manifest": [
4142
{
@@ -64,7 +65,7 @@
6465
},
6566
"stringData": {
6667
"agent-card.json": "{\"name\":\"parent_agent\",\"description\":\"\",\"url\":\"http://parent-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[]}",
67-
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a coordinating agent that can delegate tasks to specialists.\",\"http_tools\":null,\"sse_tools\":null,\"remote_agents\":[{\"name\":\"test__NS__specialist_agent\",\"url\":\"http://specialist-agent.test:8080\",\"headers\":{\"FOO\":\"sup3rs3cr3t\"}}]}"
68+
"config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a coordinating agent that can delegate tasks to specialists.\",\"http_tools\":null,\"sse_tools\":null,\"remote_agents\":[{\"name\":\"test__NS__specialist_agent\",\"url\":\"http://specialist-agent.test:8080\",\"headers\":{\"FOO\":\"sup3rs3cr3t\"}}],\"stream\":false}"
6869
}
6970
},
7071
{
@@ -133,7 +134,7 @@
133134
"template": {
134135
"metadata": {
135136
"annotations": {
136-
"kagent.dev/config-hash": "5107186355478406073"
137+
"kagent.dev/config-hash": "10891468836850404374"
137138
},
138139
"labels": {
139140
"app": "kagent",

0 commit comments

Comments
 (0)