arcadiasuite/server/python/miroflow_service.py

146 lines
4.3 KiB
Python

"""
Arcádia MiroFlow Service — Agentes científicos via MiroFlow + Ollama local
FastAPI microserviço porta 8006
"""
import os
import sys
import time
import uuid
from typing import Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# Adicionar o submodule MiroFlow ao path
MIROFLOW_MODULE_PATH = os.path.join(
os.path.dirname(__file__), "..", "modules", "miroflow"
)
sys.path.insert(0, os.path.abspath(MIROFLOW_MODULE_PATH))
from miroflow.agents.factory import build_agent
from miroflow.agents.context import AgentContext
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
RESEARCHER_MODEL = os.getenv("MIROFLOW_RESEARCHER_MODEL", "llama3.1:8b")
AGENT_MODELS = {
"statistician": "llama3.2:3b",
"fiscal_auditor": "llama3.2:3b",
"researcher": RESEARCHER_MODEL,
}
def make_agent_cfg(agent_type: str) -> dict:
model = AGENT_MODELS.get(agent_type, "deepseek-r1:14b")
return {
"type": "IterativeAgentWithToolAndRollback",
"name": f"arcadia_{agent_type}",
"max_turns": 10,
"llm": {
"provider_class": "UnifiedOpenAIClient",
"model_name": model,
"api_key": "ollama",
"base_url": f"{OLLAMA_BASE_URL}/v1",
"max_tokens": 8192,
"temperature": 0.7,
"top_p": 1.0,
"min_p": 0.0,
"top_k": -1,
"reasoning_effort": None,
"repetition_penalty": 1.0,
"max_context_length": -1,
"async_client": True,
"disable_cache_control": True,
"keep_tool_result": -1,
"use_tool_calls": False,
"oai_tool_thinking": False,
},
}
async def run_agent(agent_type: str, task: str) -> tuple[str, str]:
"""Retorna (result_text, model_name)"""
if agent_type not in AGENT_MODELS:
raise ValueError(f"Agente desconhecido: {agent_type}. Use: {list(AGENT_MODELS.keys())}")
cfg = make_agent_cfg(agent_type)
agent = build_agent(cfg)
ctx = AgentContext(
task_description=task,
system_prompt=(
"Você é um agente científico da Arcádia Suite especializado em análise de dados "
"empresariais. Responda em português de forma clara e objetiva. "
"Quando precisar de dados, explique o que analisaria e quais conclusões seriam esperadas."
),
initial_user_message=task,
)
result = await agent.run(ctx)
if isinstance(result, dict):
text = (
result.get("summary")
or result.get("final_boxed_answer")
or result.get("exceed_max_turn_summary")
or next(
(m["content"] for m in reversed(result.get("message_history") or []) if m.get("role") == "assistant"),
None,
)
or str(result)
)
else:
text = str(result)
return text, cfg["llm"]["model_name"]
app = FastAPI(title="Arcádia MiroFlow Service", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class AnalyzeRequest(BaseModel):
agent: str
task: str
context: dict = {}
tenant_id: Optional[int] = None
class AnalyzeResponse(BaseModel):
agent: str
model: str
result: str
execution_id: str
duration_ms: int
@app.get("/health")
async def health_check():
return {"status": "ok", "service": "miroflow", "agents": list(AGENT_MODELS.keys())}
@app.post("/analyze", response_model=AnalyzeResponse)
async def analyze(req: AnalyzeRequest):
if req.agent not in AGENT_MODELS:
raise HTTPException(status_code=422, detail=f"Agente inválido: {req.agent}")
start = time.time()
try:
result_text, model_name = await run_agent(req.agent, req.task)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return AnalyzeResponse(
agent=req.agent,
model=model_name,
result=result_text,
execution_id=str(uuid.uuid4()),
duration_ms=int((time.time() - start) * 1000),
)
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("MIROFLOW_PORT", 8006))
uvicorn.run(app, host="0.0.0.0", port=port)