init commit

This commit is contained in:
2026-04-12 07:19:48 -03:00
commit 9dbf89da02
111 changed files with 14925 additions and 0 deletions

View File

24
agents/shared/llm.py Normal file
View File

@@ -0,0 +1,24 @@
"""LLM factory — Bedrock Converse API (production) or direct Anthropic SDK (local dev)."""
import os
def get_agent_llm():
"""Returns a LangChain chat model for agent orchestration."""
if os.getenv("USE_BEDROCK", "").lower() == "true":
from langchain_aws import ChatBedrockConverse
return ChatBedrockConverse(
model=os.getenv("BEDROCK_MODEL_ID", "anthropic.claude-sonnet-4-20250514-v1:0"),
region_name=os.getenv("AWS_DEFAULT_REGION", "us-east-1"),
temperature=0.3,
max_tokens=4096,
)
else:
from langchain_anthropic import ChatAnthropic
return ChatAnthropic(
model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-20250514"),
temperature=0.3,
max_tokens=4096,
)

137
agents/shared/mcp_client.py Normal file
View File

@@ -0,0 +1,137 @@
"""Multi-server MCP client using fastmcp composition.
Composes the three domain-scoped MCP servers into namespaced configurations
that agents connect to as a single client.
"""
import json
from contextlib import asynccontextmanager
from typing import Any
from fastmcp import Client
# Server configurations for stdio transport
SERVERS = {
"shared": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.shared"],
},
"ops": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.ops"],
},
"passenger": {
"command": "uv",
"args": ["run", "python", "-m", "mcp_servers.passenger"],
},
}
# Agent profiles — which servers each agent connects to
AGENT_PROFILES = {
"efhas": ["shared", "ops", "passenger"],
"handover": ["shared", "ops"],
}
class MCPMultiClient:
"""Manages connections to multiple MCP servers via fastmcp Client."""
def __init__(self) -> None:
self._clients: dict[str, Client] = {}
async def connect(self, server_names: list[str]) -> None:
"""Connect to the specified MCP servers."""
for name in server_names:
if name not in SERVERS:
raise ValueError(f"Unknown server: {name}. Available: {list(SERVERS.keys())}")
config = {"mcpServers": {"default": SERVERS[name]}}
client = Client(config)
await client.__aenter__()
self._clients[name] = client
async def close(self) -> None:
"""Close all server connections."""
for client in self._clients.values():
try:
await client.__aexit__(None, None, None)
except (Exception, BaseException):
pass
self._clients.clear()
async def call_tool(self, server: str, tool_name: str, arguments: dict) -> Any:
"""Call a tool on a specific server. Returns parsed result."""
client = self._clients.get(server)
if not client:
raise ValueError(f"Not connected to server: {server}")
result = await client.call_tool(tool_name, arguments)
# Parse the result content
if isinstance(result, list):
texts = [c.text for c in result if hasattr(c, "text")]
elif hasattr(result, "content"):
texts = [c.text for c in result.content if hasattr(c, "text")]
else:
return result
if len(texts) == 1:
try:
return json.loads(texts[0])
except (json.JSONDecodeError, TypeError):
return texts[0]
elif len(texts) > 1:
parsed = []
for t in texts:
try:
parsed.append(json.loads(t))
except (json.JSONDecodeError, TypeError):
parsed.append(t)
return parsed
return None
async def read_resource(self, server: str, uri: str) -> Any:
"""Read a resource from a specific server."""
client = self._clients.get(server)
if not client:
raise ValueError(f"Not connected to server: {server}")
result = await client.read_resource(uri)
if isinstance(result, str):
try:
return json.loads(result)
except (json.JSONDecodeError, TypeError):
return result
return result
async def get_prompt(self, server: str, prompt_name: str, arguments: dict) -> str:
"""Get a rendered prompt from a specific server."""
client = self._clients.get(server)
if not client:
raise ValueError(f"Not connected to server: {server}")
result = await client.get_prompt(prompt_name, arguments)
if isinstance(result, str):
return result
# Handle structured prompt response
texts = []
if hasattr(result, "messages"):
for msg in result.messages:
if hasattr(msg.content, "text"):
texts.append(msg.content.text)
elif isinstance(msg.content, list):
for c in msg.content:
if hasattr(c, "text"):
texts.append(c.text)
return "\n".join(texts) if texts else str(result)
@asynccontextmanager
async def connect_servers(server_names: list[str]):
"""Context manager for multi-server MCP connections."""
client = MCPMultiClient()
try:
await client.connect(server_names)
yield client
finally:
await client.close()