Merge branch 'release/0.0.1'

This commit is contained in:
Davidson Gomes 2025-05-12 13:20:28 -03:00
commit 5a321125de
10 changed files with 134 additions and 45 deletions

View File

@ -21,6 +21,8 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
RUN curl -fsSL https://get.docker.com | bash RUN curl -fsSL https://get.docker.com | bash
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
COPY . . COPY . .
RUN pip install --no-cache-dir -e . RUN pip install --no-cache-dir -e .

View File

@ -54,7 +54,8 @@ async def register_user(user_data: UserCreate, db: Session = Depends(get_db)):
Raises: Raises:
HTTPException: If there is an error in registration HTTPException: If there is an error in registration
""" """
user, message = create_user(db, user_data, is_admin=False, auto_verify=False) # TODO: remover o auto_verify temporariamente para teste
user, message = create_user(db, user_data, is_admin=False, auto_verify=True)
if not user: if not user:
logger.error(f"Error registering user: {message}") logger.error(f"Error registering user: {message}")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)

View File

@ -37,7 +37,7 @@ router = APIRouter(
) )
@router.post("/", response_model=Client, status_code=status.HTTP_201_CREATED) @router.post("", response_model=Client, status_code=status.HTTP_201_CREATED)
async def create_user( async def create_user(
registration: ClientRegistration, registration: ClientRegistration,
db: Session = Depends(get_db), db: Session = Depends(get_db),

View File

@ -137,6 +137,9 @@ class LLMConfig(BaseModel):
custom_mcp_servers: Optional[List[CustomMCPServerConfig]] = Field( custom_mcp_servers: Optional[List[CustomMCPServerConfig]] = Field(
default=None, description="List of custom MCP servers with URL and headers" default=None, description="List of custom MCP servers with URL and headers"
) )
agent_tools: Optional[List[UUID]] = Field(
default=None, description="List of IDs of sub-agents"
)
sub_agents: Optional[List[UUID]] = Field( sub_agents: Optional[List[UUID]] = Field(
default=None, description="List of IDs of sub-agents" default=None, description="List of IDs of sub-agents"
) )

View File

@ -122,18 +122,6 @@ class AgentBase(BaseModel):
def validate_api_key_id(cls, v, values): def validate_api_key_id(cls, v, values):
return v return v
# Código anterior (comentado temporariamente)
# # Se o tipo for llm, api_key_id é obrigatório
# if "type" in values and values["type"] == "llm" and not v:
# # Verifica se tem api_key no config (retrocompatibilidade)
# if "config" in values and values["config"] and "api_key" in values["config"]:
# # Tem api_key no config, então aceita
# return v
# raise ValueError(
# "api_key_id é obrigatório para agentes do tipo llm"
# )
# return v
@validator("config") @validator("config")
def validate_config(cls, v, values): def validate_config(cls, v, values):
if "type" in values and values["type"] == "a2a": if "type" in values and values["type"] == "a2a":

View File

@ -2,6 +2,7 @@ from typing import List, Optional, Tuple
from google.adk.agents.llm_agent import LlmAgent from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents import SequentialAgent, ParallelAgent, LoopAgent, BaseAgent from google.adk.agents import SequentialAgent, ParallelAgent, LoopAgent, BaseAgent
from google.adk.models.lite_llm import LiteLlm from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.agent_tool import AgentTool
from src.utils.logger import setup_logger from src.utils.logger import setup_logger
from src.core.exceptions import AgentNotFoundError from src.core.exceptions import AgentNotFoundError
from src.services.agent_service import get_agent from src.services.agent_service import get_agent
@ -26,14 +27,25 @@ class AgentBuilder:
self.custom_tool_builder = CustomToolBuilder() self.custom_tool_builder = CustomToolBuilder()
self.mcp_service = MCPService() self.mcp_service = MCPService()
async def _agent_tools_builder(self, agent) -> List[AgentTool]:
"""Build the tools for an agent."""
agent_tools_ids = agent.config.get("agent_tools")
agent_tools = []
if agent_tools_ids and isinstance(agent_tools_ids, list):
for agent_tool_id in agent_tools_ids:
sub_agent = get_agent(self.db, agent_tool_id)
llm_agent, _ = await self.build_llm_agent(sub_agent)
if llm_agent:
agent_tools.append(AgentTool(agent=llm_agent))
return agent_tools
async def _create_llm_agent( async def _create_llm_agent(
self, agent self, agent
) -> Tuple[LlmAgent, Optional[AsyncExitStack]]: ) -> Tuple[LlmAgent, Optional[AsyncExitStack]]:
"""Create an LLM agent from the agent data.""" """Create an LLM agent from the agent data."""
# Get custom tools from the configuration # Get custom tools from the configuration
custom_tools = [] custom_tools = []
if agent.config.get("tools"): custom_tools = self.custom_tool_builder.build_tools(agent.config)
custom_tools = self.custom_tool_builder.build_tools(agent.config["tools"])
# Get MCP tools from the configuration # Get MCP tools from the configuration
mcp_tools = [] mcp_tools = []
@ -43,8 +55,11 @@ class AgentBuilder:
agent.config, self.db agent.config, self.db
) )
# Get agent tools
agent_tools = await self._agent_tools_builder(agent)
# Combine all tools # Combine all tools
all_tools = custom_tools + mcp_tools all_tools = custom_tools + mcp_tools + agent_tools
now = datetime.now() now = datetime.now()
current_datetime = now.strftime("%d/%m/%Y %H:%M") current_datetime = now.strftime("%d/%m/%Y %H:%M")

View File

@ -224,6 +224,9 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
if "custom_tools" in config: if "custom_tools" in config:
processed_config["custom_tools"] = config["custom_tools"] processed_config["custom_tools"] = config["custom_tools"]
if "agent_tools" in config:
processed_config["agent_tools"] = config["agent_tools"]
if "sub_agents" in config: if "sub_agents" in config:
processed_config["sub_agents"] = config["sub_agents"] processed_config["sub_agents"] = config["sub_agents"]
@ -236,6 +239,7 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
"tools", "tools",
"custom_tools", "custom_tools",
"sub_agents", "sub_agents",
"agent_tools",
"custom_mcp_servers", "custom_mcp_servers",
"mcp_servers", "mcp_servers",
]: ]:
@ -303,6 +307,12 @@ async def create_agent(db: Session, agent: AgentCreate) -> Agent:
str(agent_id) for agent_id in config["sub_agents"] str(agent_id) for agent_id in config["sub_agents"]
] ]
# Process agent tools
if "agent_tools" in config and config["agent_tools"] is not None:
processed_config["agent_tools"] = [
str(agent_id) for agent_id in config["agent_tools"]
]
# Process tools # Process tools
if "tools" in config and config["tools"] is not None: if "tools" in config and config["tools"] is not None:
processed_tools = [] processed_tools = []
@ -484,6 +494,9 @@ async def update_agent(
if "sub_agents" in config: if "sub_agents" in config:
processed_config["sub_agents"] = config["sub_agents"] processed_config["sub_agents"] = config["sub_agents"]
if "agent_tools" in config:
processed_config["agent_tools"] = config["agent_tools"]
if "custom_mcp_servers" in config: if "custom_mcp_servers" in config:
processed_config["custom_mcp_servers"] = config["custom_mcp_servers"] processed_config["custom_mcp_servers"] = config["custom_mcp_servers"]
@ -493,6 +506,7 @@ async def update_agent(
"tools", "tools",
"custom_tools", "custom_tools",
"sub_agents", "sub_agents",
"agent_tools",
"custom_mcp_servers", "custom_mcp_servers",
"mcp_servers", "mcp_servers",
]: ]:
@ -563,6 +577,12 @@ async def update_agent(
str(agent_id) for agent_id in config["sub_agents"] str(agent_id) for agent_id in config["sub_agents"]
] ]
# Process agent tools
if "agent_tools" in config and config["agent_tools"] is not None:
processed_config["agent_tools"] = [
str(agent_id) for agent_id in config["agent_tools"]
]
# Process tools # Process tools
if "tools" in config and config["tools"] is not None: if "tools" in config and config["tools"] is not None:
processed_tools = [] processed_tools = []

View File

@ -18,10 +18,14 @@ class CustomToolBuilder:
endpoint = tool_config["endpoint"] endpoint = tool_config["endpoint"]
method = tool_config["method"] method = tool_config["method"]
headers = tool_config.get("headers", {}) headers = tool_config.get("headers", {})
parameters = tool_config.get("parameters", {}) parameters = tool_config.get("parameters", {}) or {}
values = tool_config.get("values", {}) values = tool_config.get("values", {})
error_handling = tool_config.get("error_handling", {}) error_handling = tool_config.get("error_handling", {})
path_params = parameters.get("path_params") or {}
query_params = parameters.get("query_params") or {}
body_params = parameters.get("body_params") or {}
def http_tool(**kwargs): def http_tool(**kwargs):
try: try:
# Combines default values with provided values # Combines default values with provided values
@ -35,32 +39,30 @@ class CustomToolBuilder:
# Processes path parameters # Processes path parameters
url = endpoint url = endpoint
for param, value in parameters.get("path_params", {}).items(): for param, value in path_params.items():
if param in all_values: if param in all_values:
url = url.replace(f"{{{param}}}", str(all_values[param])) url = url.replace(f"{{{param}}}", str(all_values[param]))
# Process query parameters # Process query parameters
query_params = {} query_params_dict = {}
for param, value in parameters.get("query_params", {}).items(): for param, value in query_params.items():
if isinstance(value, list): if isinstance(value, list):
# If the value is a list, join with comma # If the value is a list, join with comma
query_params[param] = ",".join(value) query_params_dict[param] = ",".join(value)
elif param in all_values: elif param in all_values:
# If the parameter is in the values, use the provided value # If the parameter is in the values, use the provided value
query_params[param] = all_values[param] query_params_dict[param] = all_values[param]
else: else:
# Otherwise, use the default value from the configuration # Otherwise, use the default value from the configuration
query_params[param] = value query_params_dict[param] = value
# Adds default values to query params if they are not present # Adds default values to query params if they are not present
for param, value in values.items(): for param, value in values.items():
if param not in query_params and param not in parameters.get( if param not in query_params_dict and param not in path_params:
"path_params", {} query_params_dict[param] = value
):
query_params[param] = value
body_data = {} body_data = {}
for param, param_config in parameters.get("body_params", {}).items(): for param, param_config in body_params.items():
if param in all_values: if param in all_values:
body_data[param] = all_values[param] body_data[param] = all_values[param]
@ -68,8 +70,8 @@ class CustomToolBuilder:
for param, value in values.items(): for param, value in values.items():
if ( if (
param not in body_data param not in body_data
and param not in query_params and param not in query_params_dict
and param not in parameters.get("path_params", {}) and param not in path_params
): ):
body_data[param] = value body_data[param] = value
@ -78,7 +80,7 @@ class CustomToolBuilder:
method=method, method=method,
url=url, url=url,
headers=processed_headers, headers=processed_headers,
params=query_params, params=query_params_dict,
json=body_data if body_data else None, json=body_data if body_data else None,
timeout=error_handling.get("timeout", 30), timeout=error_handling.get("timeout", 30),
) )
@ -104,18 +106,18 @@ class CustomToolBuilder:
param_docs = [] param_docs = []
# Adds path parameters # Adds path parameters
for param, value in parameters.get("path_params", {}).items(): for param, value in path_params.items():
param_docs.append(f"{param}: {value}") param_docs.append(f"{param}: {value}")
# Adds query parameters # Adds query parameters
for param, value in parameters.get("query_params", {}).items(): for param, value in query_params.items():
if isinstance(value, list): if isinstance(value, list):
param_docs.append(f"{param}: List[{', '.join(value)}]") param_docs.append(f"{param}: List[{', '.join(value)}]")
else: else:
param_docs.append(f"{param}: {value}") param_docs.append(f"{param}: {value}")
# Adds body parameters # Adds body parameters
for param, param_config in parameters.get("body_params", {}).items(): for param, param_config in body_params.items():
required = "Required" if param_config.get("required", False) else "Optional" required = "Required" if param_config.get("required", False) else "Optional"
param_docs.append( param_docs.append(
f"{param} ({param_config['type']}, {required}): {param_config['description']}" f"{param} ({param_config['type']}, {required}): {param_config['description']}"
@ -143,11 +145,24 @@ class CustomToolBuilder:
return FunctionTool(func=http_tool) return FunctionTool(func=http_tool)
def build_tools(self, tools_config: Dict[str, Any]) -> List[FunctionTool]: def build_tools(self, tools_config: Dict[str, Any]) -> List[FunctionTool]:
"""Builds a list of tools based on the provided configuration.""" """Builds a list of tools based on the provided configuration. Accepts both 'tools' and 'custom_tools' (with http_tools)."""
self.tools = [] self.tools = []
# Processes HTTP tools http_tools = []
for http_tool_config in tools_config.get("http_tools", []): if tools_config.get("http_tools"):
http_tools = tools_config.get("http_tools", [])
elif tools_config.get("custom_tools") and tools_config["custom_tools"].get(
"http_tools"
):
http_tools = tools_config["custom_tools"].get("http_tools", [])
elif (
tools_config.get("tools")
and isinstance(tools_config["tools"], dict)
and tools_config["tools"].get("http_tools")
):
http_tools = tools_config["tools"].get("http_tools", [])
for http_tool_config in http_tools:
self.tools.append(self._create_http_tool(http_tool_config)) self.tools.append(self._create_http_tool(http_tool_config))
return self.tools return self.tools

View File

@ -79,6 +79,9 @@ class MCPService:
self, tools: List[Any], agent_tools: List[str] self, tools: List[Any], agent_tools: List[str]
) -> List[Any]: ) -> List[Any]:
"""Filters tools compatible with the agent.""" """Filters tools compatible with the agent."""
if not agent_tools or len(agent_tools) == 0:
return tools
filtered_tools = [] filtered_tools = []
for tool in tools: for tool in tools:
logger.info(f"Tool: {tool.name}") logger.info(f"Tool: {tool.name}")

View File

@ -304,10 +304,47 @@ class WorkflowAgent(BaseAgent):
"session_id": session_id, "session_id": session_id,
} }
# Função para message-node
async def message_node_function(
state: State, node_id: str, node_data: Dict[str, Any]
) -> AsyncGenerator[State, None]:
message_data = node_data.get("message", {})
message_type = message_data.get("type", "text")
message_content = message_data.get("content", "")
print(f"\n💬 MESSAGE-NODE: {message_content}")
content = state.get("content", [])
session_id = state.get("session_id", "")
conversation_history = state.get("conversation_history", [])
# Adiciona a mensagem como um novo Event do tipo agent
new_event = Event(
author="agent",
content=Content(parts=[Part(text=message_content)]),
)
content = content + [new_event]
node_outputs = state.get("node_outputs", {})
node_outputs[node_id] = {
"message_type": message_type,
"message_content": message_content,
}
yield {
"content": content,
"status": "message_added",
"node_outputs": node_outputs,
"cycle_count": state.get("cycle_count", 0),
"conversation_history": conversation_history,
"session_id": session_id,
}
return { return {
"start-node": start_node_function, "start-node": start_node_function,
"agent-node": agent_node_function, "agent-node": agent_node_function,
"condition-node": condition_node_function, "condition-node": condition_node_function,
"message-node": message_node_function,
} }
def _evaluate_condition(self, condition: Dict[str, Any], state: State) -> bool: def _evaluate_condition(self, condition: Dict[str, Any], state: State) -> bool:
@ -708,18 +745,23 @@ class WorkflowAgent(BaseAgent):
graph = await self._create_graph(ctx, self.flow_json) graph = await self._create_graph(ctx, self.flow_json)
# 4. Prepare the initial state # 4. Prepare the initial state
initial_state = State( user_event = Event(
content=[
Event(
author="user", author="user",
content=Content(parts=[Part(text=user_message)]), content=Content(parts=[Part(text=user_message)]),
) )
],
# Se o histórico estiver vazio, adiciona a mensagem do usuário
conversation_history = ctx.session.events or []
if not conversation_history or (len(conversation_history) == 0):
conversation_history = [user_event]
initial_state = State(
content=[user_event],
status="started", status="started",
session_id=session_id, session_id=session_id,
cycle_count=0, cycle_count=0,
node_outputs={}, node_outputs={},
conversation_history=ctx.session.events, conversation_history=conversation_history,
) )
# 5. Execute the graph # 5. Execute the graph