aigency 0.0.1rc225507851__py3-none-any.whl → 0.0.1rc238211992__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aigency/agents/client.py +59 -0
- aigency/agents/communicator.py +121 -0
- aigency/agents/generator.py +88 -23
- aigency/schemas/agent/agent.py +16 -0
- aigency/schemas/agent/model.py +12 -0
- aigency/schemas/agent/remote_agent.py +9 -0
- aigency/schemas/agent/skills.py +10 -0
- aigency/{models → schemas/agent}/tools.py +19 -5
- aigency/schemas/aigency_config.py +14 -0
- aigency/schemas/metadata/metadata.py +8 -0
- aigency/schemas/observability/observability.py +10 -0
- aigency/schemas/observability/phoenix.py +6 -0
- aigency/schemas/service/capabilities.py +5 -0
- aigency/schemas/service/interface.py +7 -0
- aigency/schemas/service/service.py +9 -0
- aigency/tools/generator.py +3 -7
- aigency/utils/config_service.py +38 -17
- aigency/utils/utils.py +19 -1
- {aigency-0.0.1rc225507851.dist-info → aigency-0.0.1rc238211992.dist-info}/METADATA +1 -1
- aigency-0.0.1rc238211992.dist-info/RECORD +26 -0
- aigency/models/config.py +0 -69
- aigency/models/core.py +0 -28
- aigency-0.0.1rc225507851.dist-info/RECORD +0 -15
- {aigency-0.0.1rc225507851.dist-info → aigency-0.0.1rc238211992.dist-info}/WHEEL +0 -0
- {aigency-0.0.1rc225507851.dist-info → aigency-0.0.1rc238211992.dist-info}/top_level.txt +0 -0
aigency/agents/client.py
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
import httpx
|
2
|
+
|
3
|
+
from a2a.client.client import ClientConfig
|
4
|
+
from a2a.client.client_factory import ClientFactory
|
5
|
+
from a2a.types import AgentCard, Message, SendMessageResponse
|
6
|
+
#TODO: Enable when auth is implemented
|
7
|
+
#from a2a.client.auth.interceptor import AuthInterceptor
|
8
|
+
|
9
|
+
|
10
|
+
class AgentClient:
|
11
|
+
"""A class to hold the connections to the remote agents.
|
12
|
+
|
13
|
+
This class manages connections to remote agents using the A2A protocol.
|
14
|
+
It provides methods for retrieving agent information and sending messages
|
15
|
+
to remote agents.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
_httpx_client (httpx.AsyncClient): The HTTP client used for asynchronous requests.
|
19
|
+
agent_card (AgentCard): The agent card containing metadata about the remote agent.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, agent_card: AgentCard):
|
23
|
+
"""Initialize a connection to a remote agent.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
agent_card (AgentCard): The agent card containing metadata about the remote agent.
|
27
|
+
|
28
|
+
Raises:
|
29
|
+
None
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
None
|
33
|
+
"""
|
34
|
+
self._httpx_client = httpx.AsyncClient(timeout=60)
|
35
|
+
self.card = agent_card
|
36
|
+
|
37
|
+
config = ClientConfig(httpx_client=self._httpx_client)
|
38
|
+
factory = ClientFactory(config=config)
|
39
|
+
self.agent_client = factory.create(agent_card)
|
40
|
+
|
41
|
+
def get_agent(self) -> AgentCard:
|
42
|
+
"""Get the agent card for this remote agent connection.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
AgentCard: The agent card containing metadata about the remote agent.
|
46
|
+
"""
|
47
|
+
return self.card
|
48
|
+
|
49
|
+
async def send_message(self, message_request: Message) -> SendMessageResponse:
|
50
|
+
"""Send a message to the remote agent.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
message_request (Message): The message request to send to the remote agent.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
SendMessageResponse: The response from the remote agent.
|
57
|
+
"""
|
58
|
+
async for response in self.agent_client.send_message(message_request):
|
59
|
+
yield response
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import uuid
|
2
|
+
from typing import Any, Awaitable, List
|
3
|
+
|
4
|
+
from a2a.types import Message, Task
|
5
|
+
from google.adk.tools.tool_context import ToolContext
|
6
|
+
|
7
|
+
from aigency.utils.logger import get_logger
|
8
|
+
|
9
|
+
logger = get_logger()
|
10
|
+
|
11
|
+
class Communicator:
|
12
|
+
"""Clase base para la comunicación entre agentes (Agent-to-Agent)."""
|
13
|
+
|
14
|
+
def __init__(self, remote_agent_connections: dict[str, Any] | None = None):
|
15
|
+
"""Inicializa el comunicador con las conexiones a los agentes remotos.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
remote_agent_connections: Un diccionario que mapea nombres de agentes
|
19
|
+
a sus objetos de conexión de cliente.
|
20
|
+
"""
|
21
|
+
self.remote_agent_connections: dict[str, Any] = remote_agent_connections or {}
|
22
|
+
|
23
|
+
async def send_message(
|
24
|
+
self, agent_name: str, task: str, tool_context: ToolContext
|
25
|
+
) -> Awaitable[Task | None]:
|
26
|
+
"""Delega una tarea a un agente remoto específico.
|
27
|
+
|
28
|
+
Este método envía un mensaje a un agente remoto, solicitando que realice una
|
29
|
+
tarea. Gestiona la creación del payload del mensaje y la comunicación.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
agent_name: Nombre del agente remoto al que se envía la tarea.
|
33
|
+
task: Descripción detallada de la tarea para el agente remoto.
|
34
|
+
tool_context: Objeto de contexto que contiene el estado y otra información.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Un objeto Task si la comunicación es exitosa, o None en caso contrario.
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
ValueError: Si el agente especificado no se encuentra en las conexiones.
|
41
|
+
"""
|
42
|
+
logger.info(
|
43
|
+
f"`send_message` iniciado para el agente: '{agent_name}' con la tarea: '{task}'"
|
44
|
+
)
|
45
|
+
client = self.remote_agent_connections.get(agent_name)
|
46
|
+
if not client:
|
47
|
+
available_agents = list(self.remote_agent_connections.keys())
|
48
|
+
logger.error(
|
49
|
+
f"El LLM intentó llamar a '{agent_name}', pero no se encontró. "
|
50
|
+
f"Agentes disponibles: {available_agents}"
|
51
|
+
)
|
52
|
+
raise ValueError(f"Agente '{agent_name}' no encontrado. Agentes disponibles: {available_agents}")
|
53
|
+
|
54
|
+
state = tool_context.state
|
55
|
+
|
56
|
+
# Simplifica la creación y obtención de contextos de agente usando setdefault
|
57
|
+
contexts = state.setdefault("remote_agent_contexts", {})
|
58
|
+
agent_context = contexts.setdefault(agent_name, {"context_id": str(uuid.uuid4())})
|
59
|
+
context_id = agent_context["context_id"]
|
60
|
+
|
61
|
+
# Obtiene IDs de forma más segura y clara
|
62
|
+
task_id = state.get("task_id")
|
63
|
+
input_metadata = state.get("input_message_metadata", {})
|
64
|
+
message_id = input_metadata.get("message_id")
|
65
|
+
|
66
|
+
# El message_id se pasa directamente al creador del payload
|
67
|
+
payload = self.create_send_message_payload(
|
68
|
+
text=task, task_id=task_id, context_id=context_id, message_id=message_id
|
69
|
+
)
|
70
|
+
logger.debug("`send_message` con el siguiente payload: %s", payload)
|
71
|
+
|
72
|
+
send_response = None
|
73
|
+
# Este bucle está diseñado para consumir un generador asíncrono y obtener
|
74
|
+
# la última respuesta, que suele ser el resultado final.
|
75
|
+
async for resp in client.send_message(
|
76
|
+
message_request=Message(**payload["message"])
|
77
|
+
):
|
78
|
+
send_response = resp
|
79
|
+
|
80
|
+
if isinstance(send_response, tuple):
|
81
|
+
send_response, _ = send_response
|
82
|
+
|
83
|
+
if not isinstance(send_response, Task):
|
84
|
+
logger.warning(
|
85
|
+
f"La respuesta recibida del agente '{agent_name}' no es un objeto Task. "
|
86
|
+
f"Tipo recibido: {type(send_response)}"
|
87
|
+
)
|
88
|
+
return None
|
89
|
+
|
90
|
+
return send_response
|
91
|
+
|
92
|
+
@staticmethod
|
93
|
+
def create_send_message_payload(
|
94
|
+
text: str,
|
95
|
+
task_id: str | None = None,
|
96
|
+
context_id: str | None = None,
|
97
|
+
message_id: str | None = None,
|
98
|
+
) -> dict[str, Any]:
|
99
|
+
"""Crea el payload de un mensaje para enviarlo a un agente remoto.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
text: El contenido de texto del mensaje.
|
103
|
+
task_id: ID de tarea opcional para asociar con el mensaje.
|
104
|
+
context_id: ID de contexto opcional para asociar con el mensaje.
|
105
|
+
message_id: ID de mensaje opcional. Si es None, se generará uno nuevo.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
Un diccionario que contiene el payload del mensaje formateado.
|
109
|
+
"""
|
110
|
+
payload: dict[str, Any] = {
|
111
|
+
"message": {
|
112
|
+
"role": "user",
|
113
|
+
"parts": [{"type": "text", "text": text}],
|
114
|
+
"message_id": message_id or uuid.uuid4().hex,
|
115
|
+
},
|
116
|
+
}
|
117
|
+
if task_id:
|
118
|
+
payload["message"]["task_id"] = task_id
|
119
|
+
if context_id:
|
120
|
+
payload["message"]["context_id"] = context_id
|
121
|
+
return payload
|
aigency/agents/generator.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from typing import
|
1
|
+
import logging
|
2
|
+
import httpx
|
3
|
+
from typing import List
|
4
|
+
import asyncio
|
4
5
|
|
5
6
|
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
|
7
|
+
from a2a.client import A2ACardResolver
|
6
8
|
from google.adk.agents import Agent
|
7
9
|
from google.adk.artifacts import InMemoryArtifactService
|
8
10
|
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
|
@@ -10,30 +12,50 @@ from google.adk.runners import Runner
|
|
10
12
|
from google.adk.sessions import InMemorySessionService
|
11
13
|
|
12
14
|
from aigency.agents.executor import AgentA2AExecutor
|
13
|
-
from aigency.
|
15
|
+
from aigency.agents.client import AgentClient
|
16
|
+
from aigency.schemas.aigency_config import AigencyConfig
|
17
|
+
from aigency.schemas.agent.remote_agent import RemoteAgent
|
14
18
|
from aigency.tools.generator import ToolGenerator
|
19
|
+
from aigency.utils.utils import generate_url, safe_async_run
|
20
|
+
from aigency.agents.communicator import Communicator
|
21
|
+
from aigency.utils.logger import get_logger
|
15
22
|
|
23
|
+
logger = get_logger()
|
16
24
|
|
17
25
|
class AgentA2AGenerator:
|
18
|
-
"""Generator for creating A2A agents and related components."""
|
19
26
|
|
20
27
|
@staticmethod
|
21
|
-
def create_agent(agent_config:
|
28
|
+
def create_agent(agent_config: AigencyConfig) -> Agent:
|
29
|
+
|
30
|
+
tools = [
|
31
|
+
ToolGenerator.create_tool(tool_cfg) for tool_cfg in agent_config.agent.tools
|
32
|
+
]
|
33
|
+
|
34
|
+
remote_agents = agent_config.agent.remote_agents
|
35
|
+
if remote_agents:
|
36
|
+
remote_agent_connections = AgentA2AGenerator.build_remote_agent_connections(
|
37
|
+
agent_config
|
38
|
+
)
|
39
|
+
logger.info(f"Remote agent connections: {remote_agent_connections}")
|
40
|
+
communicator = Communicator(
|
41
|
+
remote_agent_connections=remote_agent_connections
|
42
|
+
)
|
43
|
+
tools.append(communicator.send_message)
|
22
44
|
|
23
|
-
tools = [ToolGenerator.create_tool(tool_cfg) for tool_cfg in agent_config.tools]
|
24
|
-
|
25
45
|
return Agent(
|
26
|
-
name=agent_config.name,
|
27
|
-
model=agent_config.model.name,
|
28
|
-
instruction=agent_config.instruction,
|
46
|
+
name=agent_config.metadata.name,
|
47
|
+
model=agent_config.agent.model.name,
|
48
|
+
instruction=agent_config.agent.instruction,
|
29
49
|
tools=tools,
|
30
50
|
)
|
31
51
|
|
32
52
|
@staticmethod
|
33
|
-
def build_agent_card(agent_config:
|
53
|
+
def build_agent_card(agent_config: AigencyConfig) -> AgentCard:
|
34
54
|
|
35
55
|
# TODO: Parse properly
|
36
|
-
capabilities = AgentCapabilities(
|
56
|
+
capabilities = AgentCapabilities(
|
57
|
+
streaming=agent_config.service.capabilities.streaming
|
58
|
+
)
|
37
59
|
|
38
60
|
skills = [
|
39
61
|
AgentSkill(
|
@@ -43,24 +65,22 @@ class AgentA2AGenerator:
|
|
43
65
|
tags=skill.tags,
|
44
66
|
examples=skill.examples,
|
45
67
|
)
|
46
|
-
for skill in agent_config.skills
|
68
|
+
for skill in agent_config.agent.skills
|
47
69
|
]
|
48
70
|
|
49
71
|
return AgentCard(
|
50
|
-
name=agent_config.name,
|
51
|
-
description=agent_config.description,
|
52
|
-
url=agent_config.url,
|
53
|
-
version=agent_config.version,
|
54
|
-
default_input_modes=agent_config.default_input_modes,
|
55
|
-
default_output_modes=agent_config.default_output_modes,
|
72
|
+
name=agent_config.metadata.name,
|
73
|
+
description=agent_config.metadata.description,
|
74
|
+
url=agent_config.service.url,
|
75
|
+
version=agent_config.metadata.version,
|
76
|
+
default_input_modes=agent_config.service.interface.default_input_modes,
|
77
|
+
default_output_modes=agent_config.service.interface.default_output_modes,
|
56
78
|
capabilities=capabilities,
|
57
79
|
skills=skills,
|
58
80
|
)
|
59
81
|
|
60
82
|
@staticmethod
|
61
|
-
def build_executor(
|
62
|
-
agent: Agent, agent_card: AgentCard
|
63
|
-
) -> AgentA2AExecutor:
|
83
|
+
def build_executor(agent: Agent, agent_card: AgentCard) -> AgentA2AExecutor:
|
64
84
|
|
65
85
|
runner = Runner(
|
66
86
|
app_name=agent.name,
|
@@ -71,3 +91,48 @@ class AgentA2AGenerator:
|
|
71
91
|
)
|
72
92
|
|
73
93
|
return AgentA2AExecutor(runner=runner, card=agent_card)
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def build_remote_agent_connections(agent_config: AigencyConfig):
|
97
|
+
"""Initialize connections to all remote agents asynchronously.
|
98
|
+
|
99
|
+
Tests each connection individually with detailed logging to help identify
|
100
|
+
any connection issues. It attempts to connect to each remote agent address,
|
101
|
+
retrieve its agent card, and store the connection for later use.
|
102
|
+
|
103
|
+
Raises:
|
104
|
+
No exceptions are raised, but errors are logged.
|
105
|
+
"""
|
106
|
+
|
107
|
+
if not agent_config.agent.remote_agents:
|
108
|
+
return {}
|
109
|
+
|
110
|
+
remote_agent_configs = [
|
111
|
+
{"url": generate_url(host=remote_agent.host, port=remote_agent.port)}
|
112
|
+
for remote_agent in agent_config.agent.remote_agents
|
113
|
+
]
|
114
|
+
|
115
|
+
async def _connect():
|
116
|
+
remote_agent_connections = {}
|
117
|
+
async with httpx.AsyncClient(timeout=60) as client:
|
118
|
+
for config in remote_agent_configs:
|
119
|
+
address = config.get("url")
|
120
|
+
logger.debug(f"--- Attempting connection to: {address} ---")
|
121
|
+
try:
|
122
|
+
card_resolver = A2ACardResolver(client, address)
|
123
|
+
card = await card_resolver.get_agent_card()
|
124
|
+
remote_connection = AgentClient(agent_card=card)
|
125
|
+
remote_agent_connections[card.name] = remote_connection
|
126
|
+
except Exception as e:
|
127
|
+
logger.error(
|
128
|
+
f"--- CRITICAL FAILURE for address: {address} ---",
|
129
|
+
exc_info=True,
|
130
|
+
)
|
131
|
+
raise e
|
132
|
+
return remote_agent_connections
|
133
|
+
|
134
|
+
try:
|
135
|
+
return safe_async_run(_connect())
|
136
|
+
except Exception as e:
|
137
|
+
logger.error("--- CRITICAL FAILURE", exc_info=True)
|
138
|
+
raise e
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from typing import List, Optional
|
3
|
+
from aigency.schemas.agent.model import AgentModel
|
4
|
+
from aigency.schemas.agent.skills import Skill
|
5
|
+
from aigency.schemas.agent.tools import Tool
|
6
|
+
from aigency.schemas.agent.remote_agent import RemoteAgent
|
7
|
+
|
8
|
+
|
9
|
+
class Agent(BaseModel):
|
10
|
+
"""El 'cerebro' del agente: su lógica, modelo y capacidades."""
|
11
|
+
|
12
|
+
model: AgentModel
|
13
|
+
instruction: str
|
14
|
+
skills: List[Skill]
|
15
|
+
tools: Optional[List[Tool]] = []
|
16
|
+
remote_agents: Optional[List[RemoteAgent]] = []
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
from pydantic import BaseModel
|
3
|
+
|
4
|
+
class ProviderConfig(BaseModel):
|
5
|
+
"""Configuration for AI model provider."""
|
6
|
+
name: str
|
7
|
+
endpoint: Optional[str] = None
|
8
|
+
|
9
|
+
class AgentModel(BaseModel):
|
10
|
+
"""Configuration for AI model."""
|
11
|
+
name: str
|
12
|
+
provider: Optional[ProviderConfig] = None
|
@@ -1,39 +1,53 @@
|
|
1
1
|
"""Tool models for agent configuration."""
|
2
2
|
|
3
3
|
from enum import Enum
|
4
|
-
from typing import Dict, List, Optional
|
4
|
+
from typing import Dict, List, Optional, TypeAlias
|
5
5
|
|
6
6
|
from pydantic import BaseModel
|
7
7
|
|
8
8
|
|
9
9
|
class ToolType(str, Enum):
|
10
10
|
"""Enum for tool types."""
|
11
|
+
|
11
12
|
MCP = "mcp"
|
12
13
|
FUNCTION = "function"
|
13
14
|
|
14
|
-
|
15
|
+
|
16
|
+
class BaseTool(BaseModel):
|
15
17
|
"""Define an external tool that the agent can use."""
|
18
|
+
|
16
19
|
type: ToolType
|
17
20
|
name: str
|
18
21
|
description: str
|
19
22
|
|
20
|
-
|
23
|
+
|
24
|
+
class FunctionTool(BaseTool):
|
21
25
|
"""Configuration for function-based tools."""
|
26
|
+
|
22
27
|
module_path: str
|
23
28
|
function_name: str
|
24
29
|
|
30
|
+
|
25
31
|
class McpTypeStreamable(BaseModel):
|
26
32
|
"""Model for streamable tool type."""
|
33
|
+
|
27
34
|
url: str
|
28
35
|
port: int
|
29
36
|
path: str = "/"
|
30
37
|
|
38
|
+
|
31
39
|
class McpTypeStdio(BaseModel):
|
32
40
|
"""Model for stdio tool type."""
|
41
|
+
|
33
42
|
command: str
|
34
43
|
args: List[str]
|
35
44
|
env: Optional[Dict[str, str]] = None
|
36
45
|
|
37
|
-
|
46
|
+
|
47
|
+
class McpTool(BaseTool):
|
38
48
|
"""Configuration for MCP-based tools."""
|
39
|
-
|
49
|
+
|
50
|
+
mcp_config: McpTypeStreamable | McpTypeStdio
|
51
|
+
|
52
|
+
|
53
|
+
Tool: TypeAlias = FunctionTool | McpTool
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from typing import Optional
|
3
|
+
from aigency.schemas.observability.observability import Observability
|
4
|
+
from aigency.schemas.metadata.metadata import Metadata
|
5
|
+
from aigency.schemas.agent.agent import Agent
|
6
|
+
from aigency.schemas.service.service import Service
|
7
|
+
|
8
|
+
class AigencyConfig(BaseModel):
|
9
|
+
"""Root Pydantic model for complete agent configuration."""
|
10
|
+
|
11
|
+
metadata: Metadata
|
12
|
+
service: Service
|
13
|
+
agent: Agent
|
14
|
+
observability: Optional[Observability] = None
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from aigency.schemas.observability.phoenix import Phoenix
|
3
|
+
|
4
|
+
class Monitoring(BaseModel):
|
5
|
+
"""Configuración de las herramientas de monitoreo."""
|
6
|
+
phoenix: Phoenix
|
7
|
+
|
8
|
+
class Observability(BaseModel):
|
9
|
+
"""Agrupa todas las configuraciones de observabilidad."""
|
10
|
+
monitoring: Monitoring
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
from aigency.schemas.service.interface import Interface
|
3
|
+
from aigency.schemas.service.capabilities import Capabilities
|
4
|
+
|
5
|
+
class Service(BaseModel):
|
6
|
+
"""Configuración de red y comunicación del agente."""
|
7
|
+
url: str
|
8
|
+
interface: Interface
|
9
|
+
capabilities: Capabilities
|
aigency/tools/generator.py
CHANGED
@@ -14,7 +14,7 @@ from google.adk.tools.mcp_tool.mcp_toolset import (
|
|
14
14
|
StreamableHTTPConnectionParams,
|
15
15
|
)
|
16
16
|
|
17
|
-
from aigency.
|
17
|
+
from aigency.schemas.agent.tools import (
|
18
18
|
FunctionTool,
|
19
19
|
McpTool,
|
20
20
|
McpTypeStdio,
|
@@ -43,11 +43,7 @@ class ToolGenerator:
|
|
43
43
|
|
44
44
|
if isinstance(config.mcp_config, McpTypeStreamable):
|
45
45
|
url = f"http://{config.mcp_config.url}:{config.mcp_config.port}{config.mcp_config.path}"
|
46
|
-
return MCPToolset(
|
47
|
-
connection_params=StreamableHTTPConnectionParams(
|
48
|
-
url=url
|
49
|
-
)
|
50
|
-
)
|
46
|
+
return MCPToolset(connection_params=StreamableHTTPConnectionParams(url=url))
|
51
47
|
elif isinstance(config.mcp_config, McpTypeStdio):
|
52
48
|
command = config.mcp_config.command
|
53
49
|
args = config.mcp_config.args
|
@@ -65,7 +61,7 @@ class ToolGenerator:
|
|
65
61
|
ToolType.MCP: load_mcp_tool,
|
66
62
|
ToolType.FUNCTION: load_function_tool,
|
67
63
|
}
|
68
|
-
|
64
|
+
|
69
65
|
@staticmethod
|
70
66
|
def create_tool(tool: Tool) -> Optional[Any]:
|
71
67
|
"""Create a tool based on its configuration.
|
aigency/utils/config_service.py
CHANGED
@@ -6,36 +6,46 @@ from typing import Any, Dict, Optional
|
|
6
6
|
|
7
7
|
import yaml
|
8
8
|
|
9
|
-
from aigency.
|
9
|
+
from aigency.schemas.aigency_config import AigencyConfig
|
10
10
|
from aigency.utils.logger import get_logger
|
11
11
|
|
12
12
|
|
13
13
|
logger = get_logger()
|
14
14
|
|
15
|
+
|
15
16
|
class ConfigService:
|
16
17
|
"""Service for loading and managing agent configurations."""
|
18
|
+
|
17
19
|
def __init__(self, config_file: str, environment: Optional[str] = None):
|
18
20
|
self.config_file = config_file
|
19
|
-
self.environment = environment or os.getenv(
|
21
|
+
self.environment = environment or os.getenv("ENVIRONMENT", None)
|
20
22
|
self.config = self._load_and_parse()
|
21
23
|
|
22
|
-
def _load_and_parse(self) ->
|
23
|
-
"""Carga los YAMLs, los mergea y parsea según
|
24
|
+
def _load_and_parse(self) -> AigencyConfig:
|
25
|
+
"""Carga los YAMLs, los mergea y parsea según AigencyConfig."""
|
24
26
|
|
25
27
|
logger.info(f"Loading configuration from {self.config_file}")
|
26
28
|
config = self._load_yaml(self.config_file)
|
27
29
|
|
28
30
|
if self.environment is not None:
|
29
|
-
logger.info(
|
31
|
+
logger.info(
|
32
|
+
f"Environment '{self.environment}' detected, loading environment-specific configuration"
|
33
|
+
)
|
30
34
|
env_config = self._load_env_config()
|
31
35
|
if env_config:
|
32
|
-
logger.info(
|
36
|
+
logger.info(
|
37
|
+
f"Successfully loaded environment configuration with {len(env_config)} keys: {list(env_config.keys())}"
|
38
|
+
)
|
33
39
|
config = self._merge_configs(config, env_config)
|
34
|
-
logger.debug(
|
40
|
+
logger.debug(
|
41
|
+
f"Configuration merged successfully for environment '{self.environment}'"
|
42
|
+
)
|
35
43
|
else:
|
36
|
-
logger.warning(
|
37
|
-
|
38
|
-
|
44
|
+
logger.warning(
|
45
|
+
f"No environment-specific configuration found for '{self.environment}', using base configuration only"
|
46
|
+
)
|
47
|
+
|
48
|
+
return AigencyConfig(**config)
|
39
49
|
|
40
50
|
def _load_yaml(self, file_path: str) -> Dict[str, Any]:
|
41
51
|
"""Carga un archivo YAML."""
|
@@ -43,26 +53,37 @@ class ConfigService:
|
|
43
53
|
with open(file_path, "r", encoding="utf-8") as file:
|
44
54
|
return yaml.safe_load(file) or {}
|
45
55
|
except FileNotFoundError:
|
46
|
-
raise FileNotFoundError(
|
56
|
+
raise FileNotFoundError(
|
57
|
+
f"Archivo de configuración no encontrado: {file_path}"
|
58
|
+
)
|
47
59
|
except yaml.YAMLError as e:
|
48
60
|
raise ValueError(f"Error al parsear YAML {file_path}: {e}")
|
49
61
|
|
50
62
|
def _load_env_config(self) -> Optional[Dict[str, Any]]:
|
51
63
|
"""Carga configuración específica del entorno."""
|
52
64
|
config_path = Path(self.config_file)
|
53
|
-
env_file =
|
54
|
-
|
65
|
+
env_file = (
|
66
|
+
config_path.parent
|
67
|
+
/ f"{config_path.stem}.{self.environment}{config_path.suffix}"
|
68
|
+
)
|
69
|
+
|
55
70
|
return self._load_yaml(str(env_file)) if env_file.exists() else None
|
56
71
|
|
57
|
-
def _merge_configs(
|
72
|
+
def _merge_configs(
|
73
|
+
self, base: Dict[str, Any], env: Optional[Dict[str, Any]]
|
74
|
+
) -> Dict[str, Any]:
|
58
75
|
"""Mergea configuración base con configuración de entorno."""
|
59
76
|
if not env:
|
60
77
|
return base
|
61
|
-
|
78
|
+
|
62
79
|
result = base.copy()
|
63
80
|
for key, value in env.items():
|
64
|
-
if
|
81
|
+
if (
|
82
|
+
key in result
|
83
|
+
and isinstance(result[key], dict)
|
84
|
+
and isinstance(value, dict)
|
85
|
+
):
|
65
86
|
result[key] = self._merge_configs(result[key], value)
|
66
87
|
else:
|
67
88
|
result[key] = value
|
68
|
-
return result
|
89
|
+
return result
|
aigency/utils/utils.py
CHANGED
@@ -11,6 +11,7 @@ from aigency.utils.logger import get_logger
|
|
11
11
|
|
12
12
|
logger = get_logger()
|
13
13
|
|
14
|
+
|
14
15
|
def convert_a2a_part_to_genai(part: Part) -> types.Part:
|
15
16
|
"""Convert a single A2A Part type into a Google Gen AI Part type.
|
16
17
|
|
@@ -54,6 +55,7 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part:
|
|
54
55
|
)
|
55
56
|
raise ValueError(f"Unsupported part type: {part}")
|
56
57
|
|
58
|
+
|
57
59
|
def expand_env_vars(env_dict):
|
58
60
|
"""
|
59
61
|
Expande los valores del diccionario usando variables de entorno solo si el valor es una clave de entorno existente.
|
@@ -67,6 +69,21 @@ def expand_env_vars(env_dict):
|
|
67
69
|
logger.warning(f"Environment variable {v} not found")
|
68
70
|
return result
|
69
71
|
|
72
|
+
|
73
|
+
def generate_url(host: str, port: int, path: str = "") -> str:
|
74
|
+
"""Generate a URL from host, port, and path components.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
host (str): Hostname or IP address.
|
78
|
+
port (int): Port number.
|
79
|
+
path (str, optional): URL path. Defaults to "".
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
str: Complete URL in the format http://host:port/path.
|
83
|
+
"""
|
84
|
+
return f"http://{host}:{port}{path}"
|
85
|
+
|
86
|
+
|
70
87
|
def safe_async_run(coro):
|
71
88
|
"""Simple wrapper to safely run async code."""
|
72
89
|
try:
|
@@ -93,4 +110,5 @@ def safe_async_run(coro):
|
|
93
110
|
else:
|
94
111
|
return loop.run_until_complete(coro)
|
95
112
|
except RuntimeError:
|
96
|
-
return asyncio.run(coro)
|
113
|
+
return asyncio.run(coro)
|
114
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
aigency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
aigency/agents/client.py,sha256=JFKFnwMSAdTKrCPus5x2NJziPe-Ix9LG_nztd76Y2rI,1962
|
3
|
+
aigency/agents/communicator.py,sha256=C3c2eDSbAS7l36qCMY3PxhDS5jKjGlOSlMNQPwMv8q8,4779
|
4
|
+
aigency/agents/executor.py,sha256=ozT0wMwHLfyHi7bLcIPvs1gqnuj8w33SzlsSLB0W55k,5276
|
5
|
+
aigency/agents/generator.py,sha256=Zq8Ge_pJz-WH2PNH4p9PqJIgakKrg-2AcExXxRzB3M4,5080
|
6
|
+
aigency/schemas/aigency_config.py,sha256=trFOLAFCekuBtEcCofMTXw_e_d7DRMB9UYum9Ci8Qg4,491
|
7
|
+
aigency/schemas/agent/agent.py,sha256=Cy8tx4CdYWyIyEzoAp2WFK7u4b34GVd0CAYGnCCL1gM,519
|
8
|
+
aigency/schemas/agent/model.py,sha256=8gY7xXJdpePESctRDMMFfXIZbRtHJEbIXSyvy89Bbyo,316
|
9
|
+
aigency/schemas/agent/remote_agent.py,sha256=D1EmkfHKLnv66t1tRd64KeyP8lY_0IoBc8OROQKQuy4,180
|
10
|
+
aigency/schemas/agent/skills.py,sha256=5yhcoQmadaCf0Yg67PuOGDWy3ZIA-T-8dZ0uRyJw0EE,225
|
11
|
+
aigency/schemas/agent/tools.py,sha256=FgEx5LW_JwoR8OjSNlU1e2LfBynif9Xm5vM-wir_uoc,963
|
12
|
+
aigency/schemas/metadata/metadata.py,sha256=wu4TM8f8eOMb40-uOLRffJjgGptW_KVOablW4EpJBLc,156
|
13
|
+
aigency/schemas/observability/observability.py,sha256=WEeXyCrow9j2VO6HS7tOPkcKItISYsVfmHzGM-ap9nQ,320
|
14
|
+
aigency/schemas/observability/phoenix.py,sha256=bTJ2vhe2Khtme5h5mCHEVH1o4LJAQ8GcKP6rkjPMpjo,131
|
15
|
+
aigency/schemas/service/capabilities.py,sha256=vhMHP5pIYeOQTD-aBs79p_g5O3soHb5asfc_UhS1OFA,139
|
16
|
+
aigency/schemas/service/interface.py,sha256=csYJhwbD9M29LrCnd1G6pBYagFauJ-WRSfrK8NIU9oI,210
|
17
|
+
aigency/schemas/service/service.py,sha256=OajLiu5ICUFUi-M8yB9nH9jKbEuBUV6foIVcPfqx9QI,304
|
18
|
+
aigency/tools/generator.py,sha256=sXqZBiHMCZN6iwyXBk7gTrvIN3plNEuWqwwD1heFGc8,2446
|
19
|
+
aigency/utils/config_service.py,sha256=yN0Hj_IsDSSoTM4cYmBvR_x6vZ4bsxkoPc3Xle1SnjY,3201
|
20
|
+
aigency/utils/logger.py,sha256=NYvEknt2BeSqn484ivaLFwP0KbAtBxDXFoJiiKiXKeI,3796
|
21
|
+
aigency/utils/singleton.py,sha256=u5stRwgwiXRt6b2vTX4W8zuchWvQ9kbcvztQDceaF3E,313
|
22
|
+
aigency/utils/utils.py,sha256=CAyF2n6V64xqiosal9KZmL8RIw-RfGevQR2cIvGKg9Y,3098
|
23
|
+
aigency-0.0.1rc238211992.dist-info/METADATA,sha256=mnh0dJbUK7XNiLmoLP9IjOcfI38yt1rYDq1FSpggrkw,7170
|
24
|
+
aigency-0.0.1rc238211992.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
25
|
+
aigency-0.0.1rc238211992.dist-info/top_level.txt,sha256=nr33Htucgjs3wJFNugPV8w7b9ZgSnytxCtRiinR3eb8,8
|
26
|
+
aigency-0.0.1rc238211992.dist-info/RECORD,,
|
aigency/models/config.py
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
"""Configuration models for agents."""
|
2
|
-
|
3
|
-
from typing import List, Optional, Union
|
4
|
-
|
5
|
-
from pydantic import BaseModel
|
6
|
-
|
7
|
-
from aigency.models.core import Capabilities, ModelConfig, Skill
|
8
|
-
from aigency.models.tools import FunctionTool, McpTool, Tool
|
9
|
-
|
10
|
-
#class SecurityScheme(BaseModel):
|
11
|
-
# """Define un esquema de seguridad individual."""
|
12
|
-
# type: str
|
13
|
-
# description: Optional[str] = None
|
14
|
-
# scheme: str
|
15
|
-
# bearerFormat: Optional[str] = None
|
16
|
-
#
|
17
|
-
#class AuthConfig(BaseModel):
|
18
|
-
# """Configuración de autenticación."""
|
19
|
-
# type: str
|
20
|
-
# securitySchemes: Optional[Dict[str, SecurityScheme]] = None
|
21
|
-
# security: Optional[List[Dict[str, List[str]]]] = None
|
22
|
-
|
23
|
-
# --- Modelos para secciones opcionales ---
|
24
|
-
|
25
|
-
#class MonitoringConfig(BaseModel):
|
26
|
-
# """Configuración de monitorización y observabilidad."""
|
27
|
-
# phoenix_host: Optional[str] = None
|
28
|
-
# phoenix_port: Optional[int] = None
|
29
|
-
|
30
|
-
#class RemoteAgent(BaseModel):
|
31
|
-
# """Configuración para la comunicación con un agente remoto."""
|
32
|
-
# name: str
|
33
|
-
# host: str
|
34
|
-
# port: int
|
35
|
-
#auth: Optional[AuthConfig] = None # Reutilizamos la configuración de Auth
|
36
|
-
|
37
|
-
# --- Clase Principal que une todo ---
|
38
|
-
|
39
|
-
class AgentConfig(BaseModel):
|
40
|
-
"""Root Pydantic model for complete agent configuration."""
|
41
|
-
# Configuración Básica
|
42
|
-
name: str
|
43
|
-
description: str
|
44
|
-
url: str
|
45
|
-
version: str
|
46
|
-
default_input_modes: Optional[List[str]] = None
|
47
|
-
default_output_modes: Optional[List[str]] = None
|
48
|
-
capabilities: Optional[Capabilities] = None
|
49
|
-
|
50
|
-
# Autenticación
|
51
|
-
#auth: Optional[AuthConfig] = None
|
52
|
-
|
53
|
-
# Configuración del Modelo
|
54
|
-
model: ModelConfig
|
55
|
-
|
56
|
-
# Comportamiento
|
57
|
-
instruction: Optional[str] = None
|
58
|
-
skills: Optional[List[Skill]] = None
|
59
|
-
|
60
|
-
# Herramientas
|
61
|
-
tools: Optional[List[Union[FunctionTool, McpTool, Tool]]] = None
|
62
|
-
|
63
|
-
# Comunicación Multi-Agente
|
64
|
-
#remote_agents_addresses: Optional[List[RemoteAgent]] = None
|
65
|
-
|
66
|
-
# Monitorización
|
67
|
-
#monitoring: Optional[MonitoringConfig] = None
|
68
|
-
|
69
|
-
|
aigency/models/core.py
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
"""Core models for agent configuration."""
|
2
|
-
|
3
|
-
from typing import Dict, List, Optional
|
4
|
-
|
5
|
-
from pydantic import BaseModel
|
6
|
-
|
7
|
-
|
8
|
-
class ProviderConfig(BaseModel):
|
9
|
-
"""Configuration for AI model provider."""
|
10
|
-
name: str
|
11
|
-
endpoint: Optional[str] = None
|
12
|
-
|
13
|
-
class ModelConfig(BaseModel):
|
14
|
-
"""Configuration for AI model."""
|
15
|
-
name: str
|
16
|
-
provider: Optional[ProviderConfig] = None
|
17
|
-
|
18
|
-
class Capabilities(BaseModel):
|
19
|
-
"""Agent capabilities, such as streaming."""
|
20
|
-
streaming: Optional[bool] = None
|
21
|
-
|
22
|
-
class Skill(BaseModel):
|
23
|
-
"""Define a specific agent skill."""
|
24
|
-
id: str
|
25
|
-
name: str
|
26
|
-
description: str
|
27
|
-
tags: Optional[List[str]] = None
|
28
|
-
examples: Optional[List[str]] = None
|
@@ -1,15 +0,0 @@
|
|
1
|
-
aigency/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
aigency/agents/executor.py,sha256=ozT0wMwHLfyHi7bLcIPvs1gqnuj8w33SzlsSLB0W55k,5276
|
3
|
-
aigency/agents/generator.py,sha256=8RzhUgcw99Wt7p0LIaQKXitx2wpuVgeg8XTsEWZ3fjM,2359
|
4
|
-
aigency/models/config.py,sha256=c6dGPkn9AQ8x_vF-dNd509q-AOFJ1VFGPQtipCKz4Bs,1992
|
5
|
-
aigency/models/core.py,sha256=Y6FSlv7KGVr7H-WbT0wYY_bf7LeywcCjfXWlIVa1CIk,684
|
6
|
-
aigency/models/tools.py,sha256=VXdA0cKptAzu-4Oa0McxoI7uZtpsGXjXfAxUKzv2f4E,885
|
7
|
-
aigency/tools/generator.py,sha256=2TNbQAHDsZF2nt63X0k8GnxTFMUYwA110Irxq4BYmfw,2511
|
8
|
-
aigency/utils/config_service.py,sha256=ql2NsQBjhwH6phbsbqp698je1NmAUlZuyXOE0Bb9LWM,2921
|
9
|
-
aigency/utils/logger.py,sha256=NYvEknt2BeSqn484ivaLFwP0KbAtBxDXFoJiiKiXKeI,3796
|
10
|
-
aigency/utils/singleton.py,sha256=u5stRwgwiXRt6b2vTX4W8zuchWvQ9kbcvztQDceaF3E,313
|
11
|
-
aigency/utils/utils.py,sha256=1eA-RxMZW6QIxhrK9TbxysxwKhGiJqjrvOk69xR-zKo,2698
|
12
|
-
aigency-0.0.1rc225507851.dist-info/METADATA,sha256=ZG4oKDTI1SDA02JiBQzizH0PCMr7Df9OjjxnSTM2WJI,7170
|
13
|
-
aigency-0.0.1rc225507851.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
-
aigency-0.0.1rc225507851.dist-info/top_level.txt,sha256=nr33Htucgjs3wJFNugPV8w7b9ZgSnytxCtRiinR3eb8,8
|
15
|
-
aigency-0.0.1rc225507851.dist-info/RECORD,,
|
File without changes
|
File without changes
|