Skip to content
1 change: 1 addition & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies = [
"fastapi>=0.104.0",
"pydantic>=2.0.0",
"uvicorn>=0.24.0",
"a2a-sdk[http-server]>=0.3.4",
"yfinance>=0.2.65",
"tushare>=1.4.24",
"requests>=2.32.5",
Expand Down
176 changes: 175 additions & 1 deletion python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
81 changes: 81 additions & 0 deletions python/valuecell/core/agent/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from typing import AsyncIterator

import httpx
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, PushNotificationConfig, Role, TextPart
from valuecell.utils import generate_uuid

from .types import MessageResponse


class AgentClient:
def __init__(self, agent_url: str, push_notification_url: str = None):
self.agent_url = agent_url
self.push_notification_url = push_notification_url
self._client = None
self._httpx_client = None
self._initialized = False

async def _ensure_initialized(self):
if not self._initialized:
await self._setup_client()
self._initialized = True

async def _setup_client(self):
self._httpx_client = httpx.AsyncClient(timeout=30)

config = ClientConfig(
httpx_client=self._httpx_client,
accepted_output_modes=["text"],
)

push_notification_configs = []
if self.push_notification_url:
push_notification_configs.append(
PushNotificationConfig(
id=generate_uuid("pushcfg"),
token="token",
url=self.push_notification_url,
)
)
config.push_notification_configs = push_notification_configs
config.streaming = False
config.polling = True

client_factory = ClientFactory(config)
card_resolver = A2ACardResolver(self._httpx_client, self.agent_url)
card = await card_resolver.get_agent_card()
self._client = client_factory.create(card)

async def send_message(
self, text: str, context_id: str = None, exhaustive: bool = False
) -> MessageResponse | AsyncIterator[MessageResponse]:
"""Send message to Agent"""
await self._ensure_initialized()

message = Message(
role=Role.user,
parts=[Part(root=TextPart(text=text))],
message_id=generate_uuid("msg"),
context_id=context_id or generate_uuid("ctx"),
)

generator = self._client.send_message(message)
if exhaustive:
return generator

task, event = await generator.__anext__()
await generator.aclose()
return task, event

async def get_agent_card(self):
await self._ensure_initialized()
card_resolver = A2ACardResolver(self._httpx_client, self.agent_url)
return await card_resolver.get_agent_card()

async def close(self):
if self._httpx_client:
await self._httpx_client.aclose()
self._httpx_client = None
self._client = None
self._initialized = False
Loading