Skip to content

Commit 376c725

Browse files
authored
Merge pull request #7 from volcengine/feat/a2a-agent
feat: support a2a agent template
2 parents f0fb0f2 + e722136 commit 376c725

File tree

9 files changed

+129
-70
lines changed

9 files changed

+129
-70
lines changed

agentkit/apps/simple_app/simple_app_handlers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ def _convert_to_sse(self, obj) -> bytes:
133133
Returns:
134134
bytes: SSE-formatted data ready for streaming
135135
"""
136-
json_string = safe_serialize_to_json_string(obj)
136+
if isinstance(obj, str):
137+
json_string = obj
138+
else:
139+
json_string = safe_serialize_to_json_string(obj)
137140
sse_data = f"data: {json_string}\n\n"
138141
return sse_data.encode("utf-8")
139142

agentkit/toolkit/cli/cli_invoke.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import typer
2121
from rich.console import Console
2222
import random
23+
import uuid
2324
from agentkit.toolkit.config import get_config
2425
import logging
2526

@@ -64,6 +65,7 @@ def build_a2a_payload(message: Optional[str], payload: Optional[str], headers: d
6465
"params": {
6566
"message": {
6667
"role": "user",
68+
"messageId": str(uuid.uuid4()),
6769
"parts": [
6870
{"kind": "text", "text": text}
6971
]
@@ -117,15 +119,6 @@ def invoke_command(
117119
config = get_config(config_path=config_file)
118120
common_config = config.get_common_config()
119121

120-
final_payload = build_standard_payload(message, payload)
121-
agent_type = getattr(common_config, "agent_type", "") or getattr(common_config, "template_type", "")
122-
is_a2a = isinstance(agent_type, str) and "a2a" in agent_type.lower()
123-
124-
# If it's an A2A Agent, reconstruct payload using A2A constructor
125-
if is_a2a:
126-
console.print("[cyan]Detected A2A agent type - constructing A2A JSON-RPC envelope[/cyan]")
127-
final_payload = build_a2a_payload(message, payload, final_headers)
128-
129122
# Process headers
130123
final_headers = {"user_id": "agentkit_user", "session_id": "agentkit_sample_session"}
131124
if headers:
@@ -137,6 +130,16 @@ def invoke_command(
137130
raise typer.Exit(1)
138131
else:
139132
console.print(f"[blue]Using default headers: {final_headers}[/blue]")
133+
134+
final_payload = build_standard_payload(message, payload)
135+
agent_type = getattr(common_config, "agent_type", "") or getattr(common_config, "template_type", "")
136+
is_a2a = isinstance(agent_type, str) and "a2a" in agent_type.lower()
137+
138+
# If it's an A2A Agent, reconstruct payload using A2A constructor
139+
if is_a2a:
140+
console.print("[cyan]Detected A2A agent type - constructing A2A JSON-RPC envelope[/cyan]")
141+
final_payload = build_a2a_payload(message, payload, final_headers)
142+
140143

141144
# Set execution context - CLI uses ConsoleReporter (with colored output and progress)
142145
from agentkit.toolkit.context import ExecutionContext
@@ -194,6 +197,10 @@ def invoke_command(
194197
parts = event["message"].get("parts", [])
195198
elif isinstance(event.get("content"), dict):
196199
parts = event["content"].get("parts", [])
200+
elif isinstance(event.get("status"), dict):
201+
role = event["status"].get("message", {}).get("role")
202+
if role == "agent":
203+
parts = event["status"].get("message", {}).get("parts", [])
197204
if not event.get("partial", True):
198205
logger.info("Partial event: %s", event) # Log partial events
199206
continue
@@ -216,7 +223,6 @@ def invoke_command(
216223

217224
# Handle status updates (e.g., final flag or completed status)
218225
if event.get("final") is True:
219-
console.print("\n[cyan]Stream marked final[/cyan]")
220226
break
221227

222228
status = event.get("status")

agentkit/toolkit/executors/init_executor.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@
5656
"type": "Stream App",
5757
"extra_requirements": ["# google-adk"],
5858
},
59+
"a2a": {
60+
"file": "a2a.py",
61+
"name": "A2A Agent App",
62+
"language": "Python",
63+
"language_version": "3.12",
64+
"description": "支持A2A协议的Agent应用",
65+
"type": "A2A App",
66+
"extra_requirements": ["# google-adk"],
67+
},
5968
# "eino_a2a": {
6069
# "filepath": "eino_a2a",
6170
# "name": "Eino A2A Agent App",
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
from veadk import Agent, Runner
18+
from veadk.a2a.agent_card import get_agent_card
19+
from veadk.prompts.agent_default_prompt import DEFAULT_DESCRIPTION, DEFAULT_INSTRUCTION
20+
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor
21+
from agentkit.apps import AgentkitA2aApp
22+
23+
logger = logging.getLogger(__name__)
24+
25+
a2a_app = AgentkitA2aApp()
26+
27+
agent_name = "{{ agent_name | default('Agent') }}"
28+
{% if description %}description = "{{ description }}" {% else %}description = DEFAULT_DESCRIPTION {% endif %}
29+
{% if system_prompt %}system_prompt = "{{ system_prompt }}" {% else %}system_prompt = DEFAULT_INSTRUCTION {% endif %}
30+
{% if model_name %}model_name = "{{ model_name }}"{% endif %}
31+
32+
tools = []
33+
{% if tools %}
34+
{% if 'web_search' in tools %}
35+
from veadk.tools.builtin_tools.web_search import web_search
36+
tools.append(web_search)
37+
{% endif %}
38+
{% if 'run_code' in tools %}
39+
from veadk.tools.builtin_tools.run_code import run_code
40+
tools.append(run_code)
41+
{% endif %}
42+
{% if 'get_weather' in tools %}
43+
# from veadk.tools.builtin_tools.get_weather import get_weather
44+
# tools.append(get_weather)
45+
{% endif %}
46+
{% else %}
47+
# from veadk.tools.builtin_tools.web_search import web_search
48+
# tools.append(web_search)
49+
{% endif %}
50+
51+
agent = Agent(
52+
name=agent_name,
53+
description=description,
54+
instruction=system_prompt,
55+
{%- if model_name %}
56+
model_name=model_name,
57+
{%- endif %}
58+
tools=tools,
59+
)
60+
runner = Runner(agent=agent)
61+
62+
@a2a_app.agent_executor(runner=runner)
63+
class MyAgentExecutor(A2aAgentExecutor):
64+
pass
65+
66+
67+
if __name__ == "__main__":
68+
from a2a.types import AgentCard, AgentProvider, AgentSkill, AgentCapabilities
69+
70+
agent_card = AgentCard(
71+
capabilities=AgentCapabilities(streaming=True), # 启用流式
72+
description=agent.description,
73+
name=agent.name,
74+
defaultInputModes=["text"],
75+
defaultOutputModes=["text"],
76+
provider=AgentProvider(organization="veadk", url=""),
77+
skills=[AgentSkill(id="0", name="chat", description="Chat", tags=["chat"])],
78+
url="http://0.0.0.0:8000",
79+
version="1.0.0",
80+
)
81+
82+
a2a_app.run(
83+
agent_card=agent_card,
84+
host="0.0.0.0",
85+
port=8000,
86+
)

agentkit/toolkit/resources/samples/basic.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
'''
16-
**simple agent demo app**
17-
18-
Before running, the user must set the following environment variables; otherwise, runtime exceptions will inevitably occur:
19-
- MODEL_AGENT_NAME # model id in Volcano Engine Ark platform
20-
- MODEL_AGENT_API_KEY # model api key in Volcano Engine Ark platform
21-
22-
MODEL_AGENT_NAME and MODEL_AGENT_API_KEY are used to access the model service of the Volcano Engine Ark platform.
23-
'''
2415
import logging
2516

2617
from veadk import Agent, Runner

agentkit/toolkit/resources/samples/basic_stream.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ async def run(payload: dict, headers: dict):
9999
# Format as SSE data
100100
sse_event = event.model_dump_json(exclude_none=True, by_alias=True)
101101
logger.debug("Generated event in agent run streaming: %s", sse_event)
102-
yield f"data: {sse_event}\n\n"
102+
yield sse_event
103103
except Exception as e:
104104
logger.exception("Error in event_generator: %s", e)
105105
# You might want to yield an error event here
106106
error_data = json.dumps({"error": str(e)})
107-
yield f'data: {error_data}\n\n'
107+
yield error_data
108108

109109

110110
@app.ping

agentkit/toolkit/resources/samples/simple_a2a_veadk.py

Lines changed: 0 additions & 46 deletions
This file was deleted.

agentkit/toolkit/runners/local_docker.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,14 @@ def invoke(self, config: LocalDockerRunnerConfig, payload: Dict[str, Any], heade
530530
)
531531

532532
# Build invocation endpoint
533+
# Auto-detect invoke path based on agent_type: A2A agents use '/', others use '/invoke'
533534
port = docker_config.invoke_port or 8000
534535
endpoint = f"http://127.0.0.1:{port}/"
535-
invoke_endpoint = urljoin(endpoint, "invoke")
536+
537+
agent_type = getattr(common_config, 'agent_type', '') or ''
538+
is_a2a = 'a2a' in agent_type.lower()
539+
invoke_path = "/" if is_a2a else "/invoke"
540+
invoke_endpoint = urljoin(endpoint, invoke_path.lstrip('/')) if invoke_path != "/" else endpoint
536541

537542
# Prepare default request headers
538543
if headers is None:

agentkit/toolkit/runners/ve_agentkit.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,12 @@ def invoke(self, config: VeAgentkitRunnerConfig, payload: Dict[str, Any], header
308308
)
309309

310310
# Construct invoke endpoint URL
311-
invoke_endpoint = urljoin(endpoint, "invoke")
311+
# Auto-detect invoke path based on agent_type: A2A agents use '/', others use '/invoke'
312+
common_config = runner_config.common_config
313+
agent_type = getattr(common_config, 'agent_type', '') or '' if common_config else ''
314+
is_a2a = 'a2a' in agent_type.lower()
315+
invoke_path = "/" if is_a2a else "/invoke"
316+
invoke_endpoint = urljoin(endpoint, invoke_path.lstrip('/')) if invoke_path != "/" else endpoint
312317

313318
# Prepare request headers
314319
if headers is None:

0 commit comments

Comments
 (0)