Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/qualinvest_service_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .qualinvest_service_agent import agent as root_agent
85 changes: 85 additions & 0 deletions examples/qualinvest_service_agent/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import logging

import click
import uvicorn
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
AgentCapabilities,
AgentCard,
AgentSkill,
)
from dotenv import load_dotenv
from google.adk.artifacts import InMemoryArtifactService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService

from qualinvest_service_agent import create_investify_service_agent # type: ignore
from qualinvest_service_agent_executor import investifyAgentExecutor

Comment on lines +19 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use relative imports within the package.

Absolute imports will break when running as a package module.

Apply this diff:

-from qualinvest_service_agent import create_investify_service_agent  # type: ignore
-from qualinvest_service_agent_executor import investifyAgentExecutor
+from .qualinvest_service_agent import create_investify_service_agent  # type: ignore
+from .qualinvest_service_agent_executor import InvestifyAgentExecutor
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from qualinvest_service_agent import create_investify_service_agent # type: ignore
from qualinvest_service_agent_executor import investifyAgentExecutor
from .qualinvest_service_agent import create_investify_service_agent # type: ignore
from .qualinvest_service_agent_executor import InvestifyAgentExecutor
🤖 Prompt for AI Agents
In examples/qualinvest_service_agent/__main__.py around lines 19 to 21, the file
uses absolute imports which will break when the package is executed as a module;
change them to relative imports (use the package-relative form, e.g., import
from .qualinvest_service_agent and .qualinvest_service_agent_executor) so the
module resolves correctly when run via python -m, updating both import lines
accordingly.

load_dotenv()

logging.basicConfig()


@click.command()
@click.option("--host", "host", default="localhost")
@click.option("--port", "port", default=10001)
def main(host: str, port: int) -> None:
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Annotate Click option types to avoid passing str port to uvicorn.

Without type=int, port from CLI is a string at runtime.

Apply this diff:

-@click.option("--host", "host", default="localhost")
-@click.option("--port", "port", default=10001)
+@click.option("--host", "host", default="localhost", type=str)
+@click.option("--port", "port", default=10001, type=int)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@click.command()
@click.option("--host", "host", default="localhost")
@click.option("--port", "port", default=10001)
def main(host: str, port: int) -> None:
@click.command()
@click.option("--host", "host", default="localhost", type=str)
@click.option("--port", "port", default=10001, type=int)
def main(host: str, port: int) -> None:
🤖 Prompt for AI Agents
In examples/qualinvest_service_agent/__main__.py around lines 27 to 30, the
Click option for --port is missing an explicit type so the CLI provides a string
to uvicorn; update the decorator to specify type=int for the port option (and
optionally type=str for host) so Click converts the value to an integer before
passing it into main.

skill = AgentSkill(
id="buy_stocks",
name="buy stocks",
description="buy stocks for the user",
tags=["buy"],
examples=["buy aapl for 10000$"],
)
skill2 = AgentSkill(
id="check_balance",
name="check balance",
description="check user's balance",
tags=["balance"],
examples=["do I have available 10000$ to invest in shitcoins"],
)

agent_card = AgentCard(
name="investify service agent",
description="customer service for investify finance",
url=f"http://{host}:{port}/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities=AgentCapabilities(streaming=True),
skills=[skill, skill2],
)

investify_service_agent = create_investify_service_agent()
runner = Runner(
app_name=agent_card.name,
agent=investify_service_agent,
artifact_service=InMemoryArtifactService(),
session_service=InMemorySessionService(),
memory_service=InMemoryMemoryService(),
)
agent_executor = investifyAgentExecutor(runner, agent_card)

request_handler = DefaultRequestHandler(
agent_executor=agent_executor,
task_store=InMemoryTaskStore(),
)

a2a_app = A2AStarletteApplication(
agent_card=agent_card,
http_handler=request_handler,
)

uvicorn.run(
a2a_app.build(),
host=host,
port=port,
)


if __name__ == "__main__":
main()
159 changes: 159 additions & 0 deletions examples/qualinvest_service_agent/agent_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import traceback
from typing import Any
from uuid import uuid4

import httpx
from a2a.client import A2AClient
from a2a.types import (
GetTaskRequest,
GetTaskResponse,
MessageSendParams,
SendMessageRequest,
SendMessageResponse,
SendMessageSuccessResponse,
Task,
TaskQueryParams,
)

AGENT_URL = "http://localhost:10001"


def create_send_message_payload(
text: str,
task_id: str | None = None,
context_id: str | None = None,
) -> dict[str, Any]:
"""Helper function to create the payload for sending a task."""
payload: dict[str, Any] = {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": text}],
"messageId": uuid4().hex,
},
}

if task_id:
payload["message"]["taskId"] = task_id

if context_id:
payload["message"]["contextId"] = context_id
return payload


def print_json_response(response: Any, description: str) -> None:
"""Helper function to print the JSON representation of a response."""
print(f"--- {description} ---")
if hasattr(response, "root"):
print(f"{response.root.model_dump_json(exclude_none=True)}\n")
else:
print(f'{response.model_dump(mode="json", exclude_none=True)}\n')


def build_message(
text: str = "What is the weather tomorrow in New York?",
task_id: str | None = None,
context_id: str | None = None,
) -> SendMessageRequest:
send_payload = create_send_message_payload(
text=text,
task_id=task_id,
context_id=context_id,
)
return SendMessageRequest(
id=uuid4().hex,
params=MessageSendParams(**send_payload),
)


async def send_message(
client: A2AClient,
request: SendMessageRequest,
quiet: bool = False,
) -> str:
print("--- Single Turn Request ---")
# Send Message
send_response: SendMessageResponse = await client.send_message(request)
if not quiet:
print_json_response(send_response, "Single Turn Request Response")
if not isinstance(send_response.root, SendMessageSuccessResponse):
print("received non-success response. Aborting get task")
return "received non-success response. Aborting get task"

if not isinstance(send_response.root.result, Task):
print("received non-task response. Aborting get task")
return "received non-task response. Aborting get task"

task_id: str = send_response.root.result.id
print("---Query Task---")
# query the task
get_request = GetTaskRequest(
id=uuid4().hex,
params=TaskQueryParams(id=task_id),
)
get_response: GetTaskResponse = await client.get_task(get_request)

if not quiet:
print_json_response(get_response, "Query Task Response")

try:
return get_response.root.result.artifacts[0].parts[0].root.text # type: ignore
except Exception:
return "Unable to get response from agent"


async def main() -> None:
"""Main function to run the tests."""
print(f"Connecting to agent at {AGENT_URL}...")
try:
async with httpx.AsyncClient(timeout=30) as httpx_client:
client = await A2AClient.get_client_from_agent_card_url(
httpx_client,
AGENT_URL,
)
print("Connection successful.")

context_id = uuid4().hex

print(
await send_message(
client,
build_message(
text="Who are you?",
context_id=context_id,
),
quiet=True,
),
)

print(
await send_message(
client,
build_message(
text="I want to buy bitcoin for 1000$ immediately",
context_id=context_id,
),
quiet=True,
),
)

print(
await send_message(
client,
build_message(
text="what should I invest in?",
context_id=context_id,
),
quiet=True,
),
)

except Exception as e:
traceback.print_exc()
print(f"An error occurred: {e}")
print("Ensure the agent server is running.")


if __name__ == "__main__":
import asyncio

asyncio.run(main())
Loading
Loading