Skip to content
26 changes: 25 additions & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Python Code Style & Linting
name: Python Linting & Unit Tests

permissions:
contents: read
Expand Down Expand Up @@ -31,3 +31,27 @@ jobs:

- name: Ruff Lint
run: ruff check --config ./pyproject.toml .

unit_test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./python

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version-file: "./python/pyproject.toml"

- name: Install uv
uses: astral-sh/setup-uv@v6

- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Run tests
run: uv run pytest

7 changes: 3 additions & 4 deletions python/valuecell/core/agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The ValueCell Agent System is a distributed intelligent agent framework based on
```python
from valuecell.core.agent.decorator import serve

@serve(name="Calculator Agent", push_notifications=True)
@serve(push_notifications=True)
class CalculatorAgent:
"""An agent that can perform basic math calculations"""

Expand Down Expand Up @@ -84,7 +84,6 @@ The `@serve` decorator is the core tool for creating Agents, providing the follo

```python
@serve(
name="My Agent", # Agent name
host="localhost", # Service host
port=9100, # Service port (optional, auto-allocated)
streaming=True, # Whether to support streaming response
Expand Down Expand Up @@ -187,14 +186,14 @@ from valuecell.core.agent.connect import RemoteConnections
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@serve(name="Calculator Agent", push_notifications=True)
@serve(push_notifications=True)
class CalculatorAgent:
async def stream(self, query, session_id, task_id):
yield {"is_task_complete": False, "content": f"🧮 Calculating: {query}"}
await asyncio.sleep(0.5)
yield {"is_task_complete": True, "content": "✅ Calculation complete"}

@serve(name="Weather Agent", port=9101, push_notifications=True)
@serve(port=9101, push_notifications=True)
class WeatherAgent:
async def stream(self, query, session_id, task_id):
yield {"is_task_complete": False, "content": f"🌤️ Checking weather: {query}"}
Expand Down
14 changes: 8 additions & 6 deletions python/valuecell/core/agent/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from a2a.types import AgentCard
from valuecell.core.agent.client import AgentClient
from valuecell.core.agent.listener import NotificationListener
from valuecell.core.agent.registry import AgentRegistry
from valuecell.core.agent import registry
from valuecell.utils import get_agent_card_path, get_next_available_port

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -143,7 +143,7 @@ async def start_agent(
return await self._handle_remote_agent(agent_name)

# Handle local agent
agent_class = AgentRegistry.get_agent(agent_name)
agent_class = registry.get_agent_class_by_name(agent_name)
if not agent_class:
raise ValueError(f"Agent '{agent_name}' not found in registry")

Expand Down Expand Up @@ -314,7 +314,7 @@ def list_available_agents(self) -> List[str]:
if not self._remote_agent_configs:
self._load_remote_agent_configs()

local_agents = AgentRegistry.list_agents()
local_agents = registry.list_agent_names()
remote_agents = list(self._remote_agent_configs.keys())
return local_agents + remote_agents

Expand Down Expand Up @@ -346,9 +346,11 @@ def get_agent_info(self, agent_name: str) -> dict:
"name": agent_name,
"type": "remote",
"url": config_data.get("url"),
"card": agent_card.model_dump(exclude_none=True)
if agent_card
else config_data,
"card": (
agent_card.model_dump(exclude_none=True)
if agent_card
else config_data
),
"connected": agent_name in self._connections,
"running": False, # Remote agents are not managed by us
"has_listener": False,
Expand Down
10 changes: 3 additions & 7 deletions python/valuecell/core/agent/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
from a2a.utils import new_agent_text_message, new_task
from a2a.utils.errors import ServerError
from valuecell.core.agent.registry import AgentRegistry
from valuecell.core.agent import registry
from valuecell.core.agent.types import BaseAgent
from valuecell.utils import (
get_agent_card_path,
Expand All @@ -38,7 +38,6 @@


def serve(
name: str = None,
host: str = "localhost",
port: int = None,
streaming: bool = True,
Expand All @@ -64,7 +63,7 @@ def decorator(cls: Type) -> Type:
agent_skills.append(skill)

# Determine the agent name consistently
agent_name = name or cls.__name__
agent_name = cls.__name__

# Create decorated class
class DecoratedAgent(cls):
Expand Down Expand Up @@ -132,12 +131,9 @@ async def serve(self):
DecoratedAgent.__name__ = cls.__name__
DecoratedAgent.__qualname__ = cls.__qualname__

# Store agent name as class attribute for registry management
DecoratedAgent.__agent_name__ = agent_name

# Register to registry
try:
AgentRegistry.register(DecoratedAgent, agent_name)
registry.register(DecoratedAgent, agent_name)
except ImportError:
# Registry not available, skip registration
logger.warning(
Expand Down
Loading