aigency 0.0.1__tar.gz

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-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: aigency
3
+ Version: 0.0.1
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: google-adk>=1.11.0
8
+ Requires-Dist: a2a-sdk==0.3.0
9
+ Requires-Dist: litellm<1.73.0,>=1.72.6
10
+ Requires-Dist: pyyaml==6.0.2
11
+ Requires-Dist: PyJWT==2.10.1
12
+
13
+ # aigency-lib
14
+
15
+
16
+ aigency-lib/examples/simple_agents/hello_world_agent
17
+
18
+ docker compose up
@@ -0,0 +1,6 @@
1
+ # aigency-lib
2
+
3
+
4
+ aigency-lib/examples/simple_agents/hello_world_agent
5
+
6
+ docker compose up
File without changes
@@ -0,0 +1,141 @@
1
+ """Agent executor module for A2A integration."""
2
+
3
+ from a2a.server.agent_execution import AgentExecutor
4
+ from a2a.server.agent_execution.context import RequestContext
5
+ from a2a.server.events.event_queue import EventQueue
6
+ from a2a.server.tasks import TaskUpdater
7
+ from a2a.types import AgentCard, TaskState, UnsupportedOperationError
8
+ from a2a.utils.errors import ServerError
9
+ from google.adk.runners import Runner
10
+ from google.genai import types
11
+
12
+ from aigency.utils.logger import get_logger
13
+ from aigency.utils.utils import (
14
+ convert_a2a_part_to_genai,
15
+ convert_genai_part_to_a2a,
16
+ )
17
+
18
+ logger = get_logger()
19
+
20
+ # TODO: This needs to be changed
21
+ DEFAULT_USER_ID = "self"
22
+
23
+ class AgentA2AExecutor(AgentExecutor):
24
+ """Agent executor for A2A integration with Google ADK runners."""
25
+
26
+ def __init__(self, runner: Runner, card: AgentCard):
27
+ """Initialize the BaseAgentA2AExecutor.
28
+
29
+ Args:
30
+ card (AgentCard): The agent card containing metadata about the agent.
31
+ """
32
+ self._card = card
33
+ # Track active sessions for potential cancellation
34
+ self._active_sessions: set[str] = set()
35
+ self.runner = runner
36
+
37
+ async def _upsert_session(self, session_id: str) -> "Session":
38
+ """Retrieve a session if it exists, otherwise create a new one.
39
+
40
+ Ensures that async session service methods are properly awaited.
41
+
42
+ Args:
43
+ session_id (str): The ID of the session to retrieve or create.
44
+
45
+ Returns:
46
+ Session: The retrieved or newly created session object.
47
+ """
48
+ logger.info("session_id: %s", session_id)
49
+ session = await self.runner.session_service.get_session(
50
+ app_name=self.runner.app_name,
51
+ user_id=DEFAULT_USER_ID,
52
+ session_id=session_id,
53
+ )
54
+ if session is None:
55
+ session = await self.runner.session_service.create_session(
56
+ app_name=self.runner.app_name,
57
+ user_id=DEFAULT_USER_ID,
58
+ session_id=session_id,
59
+ )
60
+ return session
61
+
62
+ async def _process_request(
63
+ self,
64
+ new_message: types.Content,
65
+ session_id: str,
66
+ task_updater: TaskUpdater,
67
+ ) -> None:
68
+ session_obj = await self._upsert_session(session_id)
69
+ session_id = session_obj.id
70
+
71
+ self._active_sessions.add(session_id)
72
+
73
+ try:
74
+ async for event in self.runner.run_async(
75
+ session_id=session_id,
76
+ user_id=DEFAULT_USER_ID,
77
+ new_message=new_message,
78
+ ):
79
+ if event.is_final_response():
80
+ parts = []
81
+ if event.content:
82
+ parts = [
83
+ convert_genai_part_to_a2a(part)
84
+ for part in event.content.parts
85
+ if (part.text or part.file_data or part.inline_data)
86
+ ]
87
+ logger.debug("Yielding final response: %s", parts)
88
+ await task_updater.add_artifact(parts)
89
+ await task_updater.update_status(TaskState.completed, final=True)
90
+ break
91
+ if not event.get_function_calls():
92
+ logger.debug("Yielding update response")
93
+ message_parts = []
94
+ if event.content:
95
+ message_parts = [
96
+ convert_genai_part_to_a2a(part)
97
+ for part in event.content.parts
98
+ if (part.text)
99
+ ]
100
+ await task_updater.update_status(
101
+ TaskState.working,
102
+ message=task_updater.new_agent_message(message_parts),
103
+ )
104
+ else:
105
+ logger.debug("Skipping event")
106
+ finally:
107
+ # Remove from active sessions when done
108
+ self._active_sessions.discard(session_id)
109
+
110
+ async def execute(
111
+ self,
112
+ context: RequestContext,
113
+ event_queue: EventQueue,
114
+ ):
115
+ # Run the agent until either complete or the task is suspended.
116
+ updater = TaskUpdater(event_queue, context.task_id, context.context_id)
117
+ # Immediately notify that the task is submitted.
118
+ if not context.current_task:
119
+ await updater.update_status(TaskState.submitted)
120
+ await updater.update_status(TaskState.working)
121
+ await self._process_request(
122
+ types.UserContent(
123
+ parts=[
124
+ convert_a2a_part_to_genai(part) for part in context.message.parts
125
+ ],
126
+ ),
127
+ context.context_id,
128
+ updater,
129
+ )
130
+
131
+ logger.debug("[ADKAgentA2AExecutor] execute exiting")
132
+
133
+ async def cancel(self, context: RequestContext, event_queue: EventQueue):
134
+ session_id = context.context_id
135
+ if session_id in self._active_sessions:
136
+ logger.info("Cancellation requested for active session: %s", session_id)
137
+ self._active_sessions.discard(session_id)
138
+ else:
139
+ logger.debug("Cancellation requested for inactive session: %s", session_id)
140
+
141
+ raise ServerError(error=UnsupportedOperationError())
@@ -0,0 +1,73 @@
1
+ """Agent generator module for creating A2A agents."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from a2a.types import AgentCapabilities, AgentCard, AgentSkill
6
+ from google.adk.agents import Agent
7
+ from google.adk.artifacts import InMemoryArtifactService
8
+ from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
9
+ from google.adk.runners import Runner
10
+ from google.adk.sessions import InMemorySessionService
11
+
12
+ from aigency.agents.executor import AgentA2AExecutor
13
+ from aigency.models.config import AgentConfig
14
+ from aigency.tools.generator import ToolGenerator
15
+
16
+
17
+ class AgentA2AGenerator:
18
+ """Generator for creating A2A agents and related components."""
19
+
20
+ @staticmethod
21
+ def create_agent(agent_config: AgentConfig) -> Agent:
22
+
23
+ tools = [ToolGenerator.create_tool(tool_cfg) for tool_cfg in agent_config.tools]
24
+
25
+ return Agent(
26
+ name=agent_config.name,
27
+ model=agent_config.model.name,
28
+ instruction=agent_config.instruction,
29
+ tools=tools,
30
+ )
31
+
32
+ @staticmethod
33
+ def build_agent_card(agent_config: AgentConfig) -> AgentCard:
34
+
35
+ # TODO: Parse properly
36
+ capabilities = AgentCapabilities(streaming=agent_config.capabilities.streaming)
37
+
38
+ skills = [
39
+ AgentSkill(
40
+ id=skill.id,
41
+ name=skill.name,
42
+ description=skill.description,
43
+ tags=skill.tags,
44
+ examples=skill.examples,
45
+ )
46
+ for skill in agent_config.skills
47
+ ]
48
+
49
+ 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,
56
+ capabilities=capabilities,
57
+ skills=skills,
58
+ )
59
+
60
+ @staticmethod
61
+ def build_executor(
62
+ agent: Agent, agent_card: AgentCard
63
+ ) -> AgentA2AExecutor:
64
+
65
+ runner = Runner(
66
+ app_name=agent.name,
67
+ agent=agent,
68
+ artifact_service=InMemoryArtifactService(),
69
+ session_service=InMemorySessionService(),
70
+ memory_service=InMemoryMemoryService(),
71
+ )
72
+
73
+ return AgentA2AExecutor(runner=runner, card=agent_card)
@@ -0,0 +1,69 @@
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
+
@@ -0,0 +1,28 @@
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
@@ -0,0 +1,39 @@
1
+ """Tool models for agent configuration."""
2
+
3
+ from enum import Enum
4
+ from typing import Dict, List, Optional
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ class ToolType(str, Enum):
10
+ """Enum for tool types."""
11
+ MCP = "mcp"
12
+ FUNCTION = "function"
13
+
14
+ class Tool(BaseModel):
15
+ """Define an external tool that the agent can use."""
16
+ type: ToolType
17
+ name: str
18
+ description: str
19
+
20
+ class FunctionTool(Tool):
21
+ """Configuration for function-based tools."""
22
+ module_path: str
23
+ function_name: str
24
+
25
+ class McpTypeStreamable(BaseModel):
26
+ """Model for streamable tool type."""
27
+ url: str
28
+ port: int
29
+ path: str = "/"
30
+
31
+ class McpTypeStdio(BaseModel):
32
+ """Model for stdio tool type."""
33
+ command: str
34
+ args: List[str]
35
+ env: Optional[Dict[str, str]] = None
36
+
37
+ class McpTool(Tool):
38
+ """Configuration for MCP-based tools."""
39
+ mcp_config: McpTypeStreamable | McpTypeStdio
@@ -0,0 +1,83 @@
1
+ """Tool Factory for dynamically loading and managing agent tools.
2
+
3
+ This module provides a flexible way to load different types of tools based on
4
+ configuration. It uses the Strategy pattern and Pydantic for validation.
5
+ """
6
+
7
+ import importlib
8
+ from typing import Any, Optional
9
+
10
+ from google.adk.tools.mcp_tool.mcp_toolset import (
11
+ MCPToolset,
12
+ StdioConnectionParams,
13
+ StdioServerParameters,
14
+ StreamableHTTPConnectionParams,
15
+ )
16
+
17
+ from aigency.models.tools import (
18
+ FunctionTool,
19
+ McpTool,
20
+ McpTypeStdio,
21
+ McpTypeStreamable,
22
+ Tool,
23
+ ToolType,
24
+ )
25
+ from aigency.utils.utils import expand_env_vars
26
+
27
+
28
+ class ToolGenerator:
29
+ """Generator for creating tools based on configuration."""
30
+
31
+ @staticmethod
32
+ def load_function_tool(config: FunctionTool) -> Any:
33
+ """Load a function tool from configuration."""
34
+ try:
35
+ module = importlib.import_module(config.module_path)
36
+ return getattr(module, config.function_name)
37
+ except (ImportError, AttributeError) as e:
38
+ raise ValueError(f"Error loading function tool: {e}")
39
+
40
+ @staticmethod
41
+ def load_mcp_tool(config: McpTool) -> Any:
42
+ """Load an MCP tool from configuration."""
43
+
44
+ if isinstance(config.mcp_config, McpTypeStreamable):
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
+ )
51
+ elif isinstance(config.mcp_config, McpTypeStdio):
52
+ command = config.mcp_config.command
53
+ args = config.mcp_config.args
54
+ env = expand_env_vars(config.mcp_config.env)
55
+
56
+ return MCPToolset(
57
+ connection_params=StdioConnectionParams(
58
+ server_params=StdioServerParameters(
59
+ command=command, args=args, env=env
60
+ )
61
+ )
62
+ )
63
+
64
+ STRATEGIES = {
65
+ ToolType.MCP: load_mcp_tool,
66
+ ToolType.FUNCTION: load_function_tool,
67
+ }
68
+
69
+ @staticmethod
70
+ def create_tool(tool: Tool) -> Optional[Any]:
71
+ """Create a tool based on its configuration.
72
+
73
+ Args:
74
+ tool: Tool configuration
75
+
76
+ Returns:
77
+ The created tool or None if creation failed
78
+
79
+ Raises:
80
+ ValueError: If tool type is not supported or config is invalid
81
+ """
82
+
83
+ return ToolGenerator.STRATEGIES[tool.type](tool)
@@ -0,0 +1,68 @@
1
+ """Configuration service for loading and parsing agent configurations."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Optional
6
+
7
+ import yaml
8
+
9
+ from aigency.models.config import AgentConfig
10
+ from aigency.utils.logger import get_logger
11
+
12
+
13
+ logger = get_logger()
14
+
15
+ class ConfigService:
16
+ """Service for loading and managing agent configurations."""
17
+ def __init__(self, config_file: str, environment: Optional[str] = None):
18
+ self.config_file = config_file
19
+ self.environment = environment or os.getenv('ENVIRONMENT', None)
20
+ self.config = self._load_and_parse()
21
+
22
+ def _load_and_parse(self) -> AgentConfig:
23
+ """Carga los YAMLs, los mergea y parsea según AgentConfig."""
24
+
25
+ logger.info(f"Loading configuration from {self.config_file}")
26
+ config = self._load_yaml(self.config_file)
27
+
28
+ if self.environment is not None:
29
+ logger.info(f"Environment '{self.environment}' detected, loading environment-specific configuration")
30
+ env_config = self._load_env_config()
31
+ if env_config:
32
+ logger.info(f"Successfully loaded environment configuration with {len(env_config)} keys: {list(env_config.keys())}")
33
+ config = self._merge_configs(config, env_config)
34
+ logger.debug(f"Configuration merged successfully for environment '{self.environment}'")
35
+ else:
36
+ logger.warning(f"No environment-specific configuration found for '{self.environment}', using base configuration only")
37
+
38
+ return AgentConfig(**config)
39
+
40
+ def _load_yaml(self, file_path: str) -> Dict[str, Any]:
41
+ """Carga un archivo YAML."""
42
+ try:
43
+ with open(file_path, "r", encoding="utf-8") as file:
44
+ return yaml.safe_load(file) or {}
45
+ except FileNotFoundError:
46
+ raise FileNotFoundError(f"Archivo de configuración no encontrado: {file_path}")
47
+ except yaml.YAMLError as e:
48
+ raise ValueError(f"Error al parsear YAML {file_path}: {e}")
49
+
50
+ def _load_env_config(self) -> Optional[Dict[str, Any]]:
51
+ """Carga configuración específica del entorno."""
52
+ config_path = Path(self.config_file)
53
+ env_file = config_path.parent / f"{config_path.stem}.{self.environment}{config_path.suffix}"
54
+
55
+ return self._load_yaml(str(env_file)) if env_file.exists() else None
56
+
57
+ def _merge_configs(self, base: Dict[str, Any], env: Optional[Dict[str, Any]]) -> Dict[str, Any]:
58
+ """Mergea configuración base con configuración de entorno."""
59
+ if not env:
60
+ return base
61
+
62
+ result = base.copy()
63
+ for key, value in env.items():
64
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
65
+ result[key] = self._merge_configs(result[key], value)
66
+ else:
67
+ result[key] = value
68
+ return result
@@ -0,0 +1,101 @@
1
+ import logging
2
+ import sys
3
+ from typing import Optional, Dict, Any
4
+ from aigency.utils.singleton import Singleton
5
+
6
+
7
+ class Logger(Singleton):
8
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
9
+ if hasattr(self, '_initialized'):
10
+ # Si ya está inicializado y se pasa nueva config, actualizar
11
+ if config and config != getattr(self, 'config', {}):
12
+ self.config.update(config)
13
+ self._setup_logger()
14
+ return
15
+
16
+ self._initialized = True
17
+ self.config = config or {}
18
+ self._logger = None
19
+ self._setup_logger()
20
+
21
+ def _setup_logger(self):
22
+ """Configura el logger con la configuración proporcionada"""
23
+ # Obtener configuración del logger
24
+ log_level = self.config.get('log_level', 'INFO').upper()
25
+ log_format = self.config.get('log_format', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
26
+ log_file = self.config.get('log_file')
27
+ logger_name = self.config.get('logger_name', 'aigency')
28
+
29
+ # Crear logger
30
+ self._logger = logging.getLogger(logger_name)
31
+ self._logger.setLevel(getattr(logging, log_level, logging.INFO))
32
+
33
+ # Evitar duplicar handlers si ya existen
34
+ if self._logger.handlers:
35
+ return
36
+
37
+ # Crear formatter
38
+ formatter = logging.Formatter(log_format)
39
+
40
+ # Handler para consola
41
+ console_handler = logging.StreamHandler(sys.stdout)
42
+ console_handler.setLevel(getattr(logging, log_level, logging.INFO))
43
+ console_handler.setFormatter(formatter)
44
+ self._logger.addHandler(console_handler)
45
+
46
+ # Handler para archivo si se especifica
47
+ if log_file:
48
+ file_handler = logging.FileHandler(log_file)
49
+ file_handler.setLevel(getattr(logging, log_level, logging.INFO))
50
+ file_handler.setFormatter(formatter)
51
+ self._logger.addHandler(file_handler)
52
+
53
+ def debug(self, message: str, *args, **kwargs):
54
+ """Log a debug message"""
55
+ self._logger.debug(message, *args, **kwargs)
56
+
57
+ def info(self, message: str, *args, **kwargs):
58
+ """Log an info message"""
59
+ self._logger.info(message, *args, **kwargs)
60
+
61
+ def warning(self, message: str, *args, **kwargs):
62
+ """Log a warning message"""
63
+ self._logger.warning(message, *args, **kwargs)
64
+
65
+ def error(self, message: str, *args, **kwargs):
66
+ """Log an error message"""
67
+ self._logger.error(message, *args, **kwargs)
68
+
69
+ def critical(self, message: str, *args, **kwargs):
70
+ """Log a critical message"""
71
+ self._logger.critical(message, *args, **kwargs)
72
+
73
+ def exception(self, message: str, *args, **kwargs):
74
+ """Log an exception with traceback"""
75
+ self._logger.exception(message, *args, **kwargs)
76
+
77
+ def set_level(self, level: str):
78
+ """Cambiar el nivel de logging dinámicamente"""
79
+ log_level = level.upper()
80
+ self._logger.setLevel(getattr(logging, log_level, logging.INFO))
81
+ for handler in self._logger.handlers:
82
+ handler.setLevel(getattr(logging, log_level, logging.INFO))
83
+
84
+ def get_logger(self):
85
+ """Obtener la instancia del logger interno"""
86
+ return self._logger
87
+
88
+
89
+ # Función de conveniencia para obtener la instancia del logger
90
+ def get_logger(config: Optional[Dict[str, Any]] = None) -> Logger:
91
+ """
92
+ Obtiene la instancia singleton del logger.
93
+ Si es la primera vez que se llama y se proporciona config, se usa esa configuración.
94
+
95
+ Args:
96
+ config: Configuración opcional para el logger (solo se usa en la primera llamada)
97
+
98
+ Returns:
99
+ Instancia singleton del Logger
100
+ """
101
+ return Logger(config)
@@ -0,0 +1,12 @@
1
+ class SingletonMeta(type):
2
+ _instances = {}
3
+
4
+ def __call__(cls, *args, **kwargs):
5
+ if cls not in cls._instances:
6
+ instance = super().__call__(*args, **kwargs)
7
+ cls._instances[cls] = instance
8
+ return cls._instances[cls]
9
+
10
+
11
+ class Singleton(metaclass=SingletonMeta):
12
+ pass
@@ -0,0 +1,96 @@
1
+ """Utility functions for type conversions and environment variable handling."""
2
+
3
+ import asyncio
4
+ import os
5
+ import threading
6
+
7
+ from a2a.types import FilePart, FileWithBytes, Part, TextPart
8
+ from google.genai import types
9
+
10
+ from aigency.utils.logger import get_logger
11
+
12
+ logger = get_logger()
13
+
14
+ def convert_a2a_part_to_genai(part: Part) -> types.Part:
15
+ """Convert a single A2A Part type into a Google Gen AI Part type.
16
+
17
+ Args:
18
+ part (Part): The A2A Part to convert.
19
+
20
+ Returns:
21
+ types.Part: The equivalent Google Gen AI Part.
22
+
23
+ Raises:
24
+ ValueError: If the part type is not supported.
25
+ """
26
+ part = part.root
27
+ if isinstance(part, TextPart):
28
+ return types.Part(text=part.text)
29
+ raise ValueError(f"Unsupported part type: {type(part)}")
30
+
31
+
32
+ def convert_genai_part_to_a2a(part: types.Part) -> Part:
33
+ """Convert a single Google Gen AI Part type into an A2A Part type.
34
+
35
+ Args:
36
+ part (types.Part): The Google Gen AI Part to convert.
37
+
38
+ Returns:
39
+ Part: The equivalent A2A Part.
40
+
41
+ Raises:
42
+ ValueError: If the part type is not supported.
43
+ """
44
+ if part.text:
45
+ return TextPart(text=part.text)
46
+ if part.inline_data:
47
+ return Part(
48
+ root=FilePart(
49
+ file=FileWithBytes(
50
+ bytes=part.inline_data.data,
51
+ mime_type=part.inline_data.mime_type,
52
+ )
53
+ )
54
+ )
55
+ raise ValueError(f"Unsupported part type: {part}")
56
+
57
+ def expand_env_vars(env_dict):
58
+ """
59
+ Expande los valores del diccionario usando variables de entorno solo si el valor es una clave de entorno existente.
60
+ Si la variable no existe en el entorno, deja el valor literal.
61
+ """
62
+ result = {}
63
+ for k, v in env_dict.items():
64
+ if isinstance(v, str) and v in os.environ:
65
+ result[k] = os.getenv(v)
66
+ else:
67
+ logger.warning(f"Environment variable {v} not found")
68
+ return result
69
+
70
+ def safe_async_run(coro):
71
+ """Simple wrapper to safely run async code."""
72
+ try:
73
+ loop = asyncio.get_event_loop()
74
+ if loop.is_running():
75
+
76
+ result = None
77
+ exception = None
78
+
79
+ def run_in_thread():
80
+ nonlocal result, exception
81
+ try:
82
+ result = asyncio.run(coro)
83
+ except Exception as e:
84
+ exception = e
85
+
86
+ thread = threading.Thread(target=run_in_thread)
87
+ thread.start()
88
+ thread.join()
89
+
90
+ if exception:
91
+ raise exception
92
+ return result
93
+ else:
94
+ return loop.run_until_complete(coro)
95
+ except RuntimeError:
96
+ return asyncio.run(coro)
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: aigency
3
+ Version: 0.0.1
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: google-adk>=1.11.0
8
+ Requires-Dist: a2a-sdk==0.3.0
9
+ Requires-Dist: litellm<1.73.0,>=1.72.6
10
+ Requires-Dist: pyyaml==6.0.2
11
+ Requires-Dist: PyJWT==2.10.1
12
+
13
+ # aigency-lib
14
+
15
+
16
+ aigency-lib/examples/simple_agents/hello_world_agent
17
+
18
+ docker compose up
@@ -0,0 +1,18 @@
1
+ README.md
2
+ pyproject.toml
3
+ aigency/__init__.py
4
+ aigency.egg-info/PKG-INFO
5
+ aigency.egg-info/SOURCES.txt
6
+ aigency.egg-info/dependency_links.txt
7
+ aigency.egg-info/requires.txt
8
+ aigency.egg-info/top_level.txt
9
+ aigency/agents/executor.py
10
+ aigency/agents/generator.py
11
+ aigency/models/config.py
12
+ aigency/models/core.py
13
+ aigency/models/tools.py
14
+ aigency/tools/generator.py
15
+ aigency/utils/config_service.py
16
+ aigency/utils/logger.py
17
+ aigency/utils/singleton.py
18
+ aigency/utils/utils.py
@@ -0,0 +1,5 @@
1
+ google-adk>=1.11.0
2
+ a2a-sdk==0.3.0
3
+ litellm<1.73.0,>=1.72.6
4
+ pyyaml==6.0.2
5
+ PyJWT==2.10.1
@@ -0,0 +1 @@
1
+ aigency
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "aigency"
3
+ version = "0.0.1"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "google-adk>=1.11.0",
9
+ "a2a-sdk==0.3.0",
10
+ "litellm>=1.72.6,<1.73.0",
11
+ "pyyaml==6.0.2",
12
+ "PyJWT==2.10.1"
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["setuptools>=61.0", "wheel"]
17
+ build-backend = "setuptools.build_meta"
18
+
19
+ [tool.setuptools.packages.find]
20
+ include = ["aigency*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+