Skip to content

Commit 17d7223

Browse files
author
Davidson Gomes
committed
Merge branch 'release/0.0.10'
2 parents 6cfff4c + 22a771a commit 17d7223

23 files changed

+1883
-145
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.0.10] - 2025-05-15
9+
10+
### Added
11+
12+
- Add Task Agent for structured single-task execution
13+
- Improve context management in agent execution
14+
- Add file support for A2A protocol (Agent-to-Agent) endpoints
15+
- Implement multimodal content processing in A2A messages
16+
817
## [0.0.9] - 2025-05-13
918

1019
### Added

README.md

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ The Evo AI platform allows:
1111
- Client management
1212
- MCP server configuration
1313
- Custom tools management
14+
- **[Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/)**: Base framework for agent development, providing support for LLM Agents, Sequential Agents, Loop Agents, Parallel Agents and Custom Agents
1415
- JWT authentication with email verification
15-
- **Agent 2 Agent (A2A) Protocol Support**: Interoperability between AI agents following Google's A2A specification
16-
- **Workflow Agent with LangGraph**: Building complex agent workflows with LangGraph and ReactFlow
16+
- **[Agent 2 Agent (A2A) Protocol Support](https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/)**: Interoperability between AI agents following Google's A2A specification
17+
- **[Workflow Agent with LangGraph](https://www.langchain.com/langgraph)**: Building complex agent workflows with LangGraph and ReactFlow
1718
- **Secure API Key Management**: Encrypted storage of API keys with Fernet encryption
1819
- **Agent Organization**: Folder structure for organizing agents by categories
1920

@@ -30,6 +31,8 @@ Agent based on language models like GPT-4, Claude, etc. Can be configured with t
3031
"client_id": "{{client_id}}",
3132
"name": "personal_assistant",
3233
"description": "Specialized personal assistant",
34+
"role": "Personal Assistant",
35+
"goal": "Help users with daily tasks and provide relevant information",
3336
"type": "llm",
3437
"model": "gpt-4",
3538
"api_key_id": "stored-api-key-uuid",
@@ -150,6 +153,39 @@ Executes sub-agents in a custom workflow defined by a graph structure. This agen
150153

151154
The workflow structure is built using ReactFlow in the frontend, allowing visual creation and editing of complex agent workflows with nodes (representing agents or decision points) and edges (representing flow connections).
152155

156+
### 7. Task Agent
157+
158+
Executes a specific task using a target agent. Task Agent provides a streamlined approach for structured task execution, where the agent_id specifies which agent will process the task, and the task description can include dynamic content placeholders.
159+
160+
```json
161+
{
162+
"client_id": "{{client_id}}",
163+
"name": "web_search_task",
164+
"type": "task",
165+
"folder_id": "folder_id (optional)",
166+
"config": {
167+
"tasks": [
168+
{
169+
"agent_id": "search-agent-uuid",
170+
"description": "Search the web for information about {content}",
171+
"expected_output": "Comprehensive search results with relevant information"
172+
}
173+
],
174+
"sub_agents": ["post-processing-agent-uuid"]
175+
}
176+
}
177+
```
178+
179+
Key features of Task Agent:
180+
181+
- Passes structured task instructions to the designated agent
182+
- Supports variable content using {content} placeholder in the task description
183+
- Provides clear task definition with instructions and expected output format
184+
- Can execute sub-agents after the main task is completed
185+
- Simplifies orchestration for single-focused task execution
186+
187+
Task Agent is ideal for scenarios where you need to execute a specific, well-defined task with clear instructions and expectations.
188+
153189
### Common Characteristics
154190

155191
- All agent types can have sub-agents
@@ -160,7 +196,7 @@ The workflow structure is built using ReactFlow in the frontend, allowing visual
160196

161197
### MCP Server Configuration
162198

163-
Agents can be integrated with MCP (Model Control Protocol) servers for distributed processing:
199+
Agents can be integrated with MCP (Model Context Protocol) servers for distributed processing:
164200

165201
```json
166202
{
@@ -355,7 +391,7 @@ Evo AI implements the Google's Agent 2 Agent (A2A) protocol, enabling seamless c
355391
- **Standardized Communication**: Agents can communicate using a common protocol regardless of their underlying implementation
356392
- **Interoperability**: Support for agents built with different frameworks and technologies
357393
- **Well-Known Endpoints**: Standardized endpoints for agent discovery and interaction
358-
- **Task Management**: Support for task-based interactions between agents
394+
- **Task Management**: Support for task creation, execution, and status tracking
359395
- **State Management**: Tracking of agent states and conversation history
360396
- **Authentication**: Secure API key-based authentication for agent interactions
361397

@@ -507,7 +543,7 @@ cd evo-ai-frontend
507543

508544
After installation, follow these steps to set up your first agent:
509545

510-
1. **Configure MCP Server**: Set up your Model Control Protocol server configuration first
546+
1. **Configure MCP Server**: Set up your Model Context Protocol server configuration first
511547
2. **Create Client or Register**: Create a new client or register a user account
512548
3. **Create Agents**: Set up the agents according to your needs (LLM, A2A, Sequential, Parallel, Loop, or Workflow)
513549

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""add_task_agent_type_agents_table
2+
3+
Revision ID: 2df073c7b564
4+
Revises: 611d84e70bb2
5+
Create Date: 2025-05-14 11:46:39.573247
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "2df073c7b564"
17+
down_revision: Union[str, None] = "611d84e70bb2"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
"""Upgrade schema."""
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.drop_constraint("check_agent_type", "agents", type_="check")
26+
op.create_check_constraint(
27+
"check_agent_type",
28+
"agents",
29+
"type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow', 'crew_ai', 'task')",
30+
)
31+
# ### end Alembic commands ###
32+
33+
34+
def downgrade() -> None:
35+
"""Downgrade schema."""
36+
# ### commands auto generated by Alembic - please adjust! ###
37+
op.drop_constraint("check_agent_type", "agents", type_="check")
38+
op.create_check_constraint(
39+
"check_agent_type",
40+
"agents",
41+
"type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow', 'crew_ai')",
42+
)
43+
# ### end Alembic commands ###
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""add_crew_ai_coluns_agents_table
2+
3+
Revision ID: 611d84e70bb2
4+
Revises: bdc5d363e2e1
5+
Create Date: 2025-05-14 07:31:08.741620
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '611d84e70bb2'
16+
down_revision: Union[str, None] = 'bdc5d363e2e1'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column('agents', sa.Column('role', sa.String(), nullable=True))
25+
op.add_column('agents', sa.Column('goal', sa.Text(), nullable=True))
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade() -> None:
30+
"""Downgrade schema."""
31+
# ### commands auto generated by Alembic - please adjust! ###
32+
op.drop_column('agents', 'goal')
33+
op.drop_column('agents', 'role')
34+
# ### end Alembic commands ###
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""add_crew_ai_agent_type_agents_table
2+
3+
Revision ID: bdc5d363e2e1
4+
Revises: 6db4a526335b
5+
Create Date: 2025-05-14 06:23:14.701878
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "bdc5d363e2e1"
17+
down_revision: Union[str, None] = "6db4a526335b"
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
"""Upgrade schema."""
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.drop_constraint("check_agent_type", "agents", type_="check")
26+
op.create_check_constraint(
27+
"check_agent_type",
28+
"agents",
29+
"type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow', 'crew_ai')",
30+
)
31+
# ### end Alembic commands ###
32+
33+
34+
def downgrade() -> None:
35+
"""Downgrade schema."""
36+
# ### commands auto generated by Alembic - please adjust! ###
37+
op.drop_constraint("check_agent_type", "agents", type_="check")
38+
op.create_check_constraint(
39+
"check_agent_type",
40+
"agents",
41+
"type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow')",
42+
)
43+
# ### end Alembic commands ###

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dependencies = [
3030
"google-cloud-aiplatform==1.90.0",
3131
"python-dotenv==1.1.0",
3232
"google-adk==0.3.0",
33-
"litellm==1.68.1",
33+
"litellm>=1.68.0,<1.69.0",
3434
"python-multipart==0.0.20",
3535
"alembic==1.15.2",
3636
"asyncpg==0.30.0",

src/api/a2a_routes.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
Routes for the A2A (Agent-to-Agent) protocol.
3232
3333
This module implements the standard A2A routes according to the specification.
34+
Supports both text messages and file uploads through the message parts mechanism.
3435
"""
3536

3637
import uuid
@@ -92,18 +93,100 @@ async def process_a2a_request(
9293
db: Session = Depends(get_db),
9394
a2a_service: A2AService = Depends(get_a2a_service),
9495
):
95-
"""Processes an A2A request."""
96+
"""
97+
Processes an A2A request.
98+
99+
Supports both text messages and file uploads. For file uploads,
100+
include file parts in the message following the A2A protocol format:
101+
102+
{
103+
"jsonrpc": "2.0",
104+
"id": "request-id",
105+
"method": "tasks/send",
106+
"params": {
107+
"id": "task-id",
108+
"sessionId": "session-id",
109+
"message": {
110+
"role": "user",
111+
"parts": [
112+
{
113+
"type": "text",
114+
"text": "Analyze this image"
115+
},
116+
{
117+
"type": "file",
118+
"file": {
119+
"name": "example.jpg",
120+
"mimeType": "image/jpeg",
121+
"bytes": "base64-encoded-content"
122+
}
123+
}
124+
]
125+
}
126+
}
127+
}
128+
"""
96129
# Verify the API key
97130
if not verify_api_key(db, x_api_key):
98131
raise HTTPException(status_code=401, detail="Invalid API key")
99132

100133
# Process the request
101134
try:
102135
request_body = await request.json()
136+
137+
debug_request_body = {}
138+
if "method" in request_body:
139+
debug_request_body["method"] = request_body["method"]
140+
if "id" in request_body:
141+
debug_request_body["id"] = request_body["id"]
142+
143+
logger.info(f"A2A request received: {debug_request_body}")
144+
145+
# Log if request contains file parts for debugging
146+
if isinstance(request_body, dict) and "params" in request_body:
147+
params = request_body.get("params", {})
148+
message = params.get("message", {})
149+
parts = message.get("parts", [])
150+
151+
logger.info(f"A2A message contains {len(parts)} parts")
152+
for i, part in enumerate(parts):
153+
if not isinstance(part, dict):
154+
logger.warning(f"Part {i+1} is not a dictionary: {type(part)}")
155+
continue
156+
157+
part_type = part.get("type")
158+
logger.info(f"Part {i+1} type: {part_type}")
159+
160+
if part_type == "file":
161+
file_info = part.get("file", {})
162+
logger.info(
163+
f"File part found: {file_info.get('name')} ({file_info.get('mimeType')})"
164+
)
165+
if "bytes" in file_info:
166+
bytes_data = file_info.get("bytes", "")
167+
bytes_size = len(bytes_data) * 0.75
168+
logger.info(f"File size: ~{bytes_size/1024:.2f} KB")
169+
if bytes_data:
170+
sample = (
171+
bytes_data[:10] + "..."
172+
if len(bytes_data) > 10
173+
else bytes_data
174+
)
175+
logger.info(f"Sample of base64 data: {sample}")
176+
elif part_type == "text":
177+
text_content = part.get("text", "")
178+
preview = (
179+
text_content[:30] + "..."
180+
if len(text_content) > 30
181+
else text_content
182+
)
183+
logger.info(f"Text part found: '{preview}'")
184+
103185
result = await a2a_service.process_request(agent_id, request_body)
104186

105187
# If the response is a streaming response, return as EventSourceResponse
106188
if hasattr(result, "__aiter__"):
189+
logger.info("Returning streaming response")
107190

108191
async def event_generator():
109192
async for item in result:
@@ -115,11 +198,15 @@ async def event_generator():
115198
return EventSourceResponse(event_generator())
116199

117200
# Otherwise, return as JSONResponse
201+
logger.info("Returning standard JSON response")
118202
if hasattr(result, "model_dump"):
119203
return JSONResponse(result.model_dump(exclude_none=True))
120204
return JSONResponse(result)
121205
except Exception as e:
122206
logger.error(f"Error processing A2A request: {e}")
207+
import traceback
208+
209+
logger.error(f"Full traceback: {traceback.format_exc()}")
123210
return JSONResponse(
124211
status_code=500,
125212
content={

0 commit comments

Comments
 (0)