arcadiasuite/.planning/phases/03-miroflow-embutido/03-01-PLAN.md

14 KiB

id phase plan type wave depends_on files_modified autonomous requirements must_haves
03-01 03-miroflow-embutido 01 execute 0
server/python/miroflow_service.py
server/python/test_miroflow_service.py
true
REQ-3.1
REQ-3.2
REQ-3.3
REQ-3.4
truths artifacts key_links
ollama pull llama3.1:8b completa sem erro
miroflow_service.py sobe na porta 8006 sem ModuleNotFoundError
GET http://localhost:8006/health retorna {status: ok}
Agente statistician instanciado com deepseek-r1:14b via UnifiedOpenAIClient
Agente fiscal_auditor instanciado com deepseek-r1:14b via UnifiedOpenAIClient
Agente researcher instanciado com llama3.1:8b via UnifiedOpenAIClient
path provides exports
server/python/miroflow_service.py FastAPI microserviço porta 8006 com 3 agentes MiroFlow
app
AnalyzeRequest
AnalyzeResponse
make_agent_cfg
run_agent
path provides contains
server/python/test_miroflow_service.py Testes pytest para REQ-3.1 a REQ-3.4 test_health, test_statistician_model, test_fiscal_auditor_model, test_researcher_model
from to via pattern
miroflow_service.py http://localhost:11434/v1/chat/completions UnifiedOpenAIClient base_url=OLLAMA_BASE_URL/v1 OLLAMA_BASE_URL.*v1
from to via pattern
miroflow_service.py server/modules/miroflow sys.path.insert(0, /app/server/modules/miroflow) sys.path.insert
Preparar o ambiente e criar o microserviço Python MiroFlow (FastAPI, porta 8006) com os 3 agentes especializados configurados para Ollama local.

Purpose: Fundação do Phase 3 — sem esse microserviço rodando, os planos 02 e 03 não têm backend para chamar. Output: miroflow_service.py funcional + testes pytest cobrindo REQ-3.1 a REQ-3.4 + llama3.1:8b instalado.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-miroflow-embutido/03-RESEARCH.md app = FastAPI(title="...", version="1.0.0") app.add_middleware(CORSMiddleware, allow_origins=["*"], ...)

@app.get("/health") async def health_check(): return {"status": "ok", "service": "..."}

@app.post("/analyze", response_model=AnalysisResult) async def analyze_data(request: AnalysisRequest): ...

if name == "main": port = int(os.environ.get("SERVICE_PORT", 8003)) uvicorn.run(app, host="0.0.0.0", port=port)

from miroflow.agents.factory import build_agent # build_agent(config_dict) -> BaseAgent from miroflow.agents.context import AgentContext # AgentContext(task_description="...")

agent.run(ctx) -> dict com chave "summary" ou texto direto

{ "type": "IterativeAgentWithToolAndRollback", "name": "arcadia_deepseek_r1_14b", "max_turns": 10, "llm": { "provider_class": "UnifiedOpenAIClient", "model_name": "deepseek-r1:14b", # ou llama3.1:8b "api_key": "ollama", "base_url": "{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, } }

Task 1: Setup — baixar llama3.1:8b e instalar dependências MiroFlow server/modules/miroflow/ (sem modificar), .env (adicionar MIROFLOW_PORT)

<read_first> - /opt/arcadia_merged/.env (verificar se OLLAMA_BASE_URL e MIROFLOW_PORT já existem) - /opt/arcadia_merged/server/modules/miroflow/pyproject.toml (confirmar deps necessárias) </read_first>

1. Baixar o modelo llama3.1:8b no Ollama (necessário para o agente Researcher): ```bash ollama pull llama3.1:8b ``` Se o download demorar muito, continuar e validar após. O fallback é llama3.2:3b já instalado.
2. Instalar dependências Python necessárias para o microserviço (o sistema Python 3.12 já tem fastapi/uvicorn, faltam as deps do MiroFlow):
   ```bash
   cd /opt/arcadia_merged/server/modules/miroflow && uv sync --frozen 2>/dev/null || pip3 install omegaconf hydra-core openai python-dotenv pydantic
   ```

3. Verificar se MIROFLOW_PORT=8006 existe no .env. Se não existir, adicionar a linha `MIROFLOW_PORT=8006` ao arquivo .env. NÃO alterar OLLAMA_BASE_URL existente.

4. Verificar se OLLAMA_BASE_URL está presente no .env. Se não, adicionar `OLLAMA_BASE_URL=http://localhost:11434`.
ollama list | grep "llama3.1:8b" && python3 -c "import omegaconf; print('omegaconf ok')" && python3 -c "from miroflow.agents.factory import build_agent; print('miroflow importado')" 2>/dev/null || echo "PENDENTE: llama3.1:8b pode estar baixando"

<acceptance_criteria> - ollama list contém "llama3.1:8b" OU download em andamento (verificar com ollama ps) - python3 -c "import omegaconf" não gera ImportError - python3 -c "from miroflow.agents.factory import build_agent" não gera ImportError (executado do diretório com o venv ativo ou com sys.path configurado) - .env contém linha MIROFLOW_PORT=8006 </acceptance_criteria>

Modelo llama3.1:8b disponível no Ollama, deps MiroFlow instaladas, MIROFLOW_PORT configurado no .env

Task 2: Criar test_miroflow_service.py + miroflow_service.py (FastAPI porta 8006) server/python/test_miroflow_service.py, server/python/miroflow_service.py

<read_first> - /opt/arcadia_merged/server/python/bi_analysis_service.py (padrão de estrutura FastAPI a seguir) - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/base.py (API do BaseAgent) - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/factory.py (build_agent signature) - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/context.py (AgentContext fields) - /opt/arcadia_merged/.env (OLLAMA_BASE_URL atual) </read_first>

- test_health: GET /health retorna {"status": "ok", "service": "miroflow"} - test_statistician_config: make_agent_cfg("statistician") retorna dict com model_name="deepseek-r1:14b" e base_url terminando em "/v1" - test_fiscal_auditor_config: make_agent_cfg("fiscal_auditor") retorna dict com model_name="deepseek-r1:14b" - test_researcher_config: make_agent_cfg("researcher") retorna dict com model_name="llama3.1:8b" (fallback "llama3.2:3b" se llama3.1:8b ausente) - test_analyze_request_validation: POST /analyze com agent="invalid" retorna 422 Unprocessable Entity - test_analyze_request_schema: AnalyzeRequest aceita {agent, task, context, tenant_id} e AnalyzeResponse tem {agent, model, result, execution_id, duration_ms} PASSO 1 (RED): Criar server/python/test_miroflow_service.py com os 6 testes listados em . Rodar pytest — DEVE falhar (miroflow_service.py não existe).
PASSO 2 (GREEN): Criar server/python/miroflow_service.py seguindo o padrão de bi_analysis_service.py:

```python
"""
Arcádia MiroFlow Service — Agentes científicos via MiroFlow + Ollama local
FastAPI microserviço porta 8006
"""
import os, sys, time, 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")

# Verificar se llama3.1:8b está disponível; senão usar fallback
RESEARCHER_MODEL = os.getenv("MIROFLOW_RESEARCHER_MODEL", "llama3.1:8b")

AGENT_MODELS = {
    "statistician": "deepseek-r1:14b",
    "fiscal_auditor": "deepseek-r1:14b",
    "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)
    result = await agent.run(ctx)
    text = result.get("summary", str(result)) if isinstance(result, dict) else 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        # "statistician" | "fiscal_auditor" | "researcher"
    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)
```

PASSO 3 (REFACTOR): Rodar pytest novamente — TODOS devem passar. Não alterar lógica, apenas organizar se necessário.

IMPORTANTE: Não usar modelos acima de 14B. Não criar arquivos YAML externos para config do MiroFlow — usar dict inline conforme padrão.
cd /opt/arcadia_merged && python3 -m pytest server/python/test_miroflow_service.py -x -v 2>&1 | tail -20

<acceptance_criteria> - pytest server/python/test_miroflow_service.py -x passa com 0 falhas - test_health verifica {"status": "ok", "service": "miroflow"} - test_statistician_config e test_fiscal_auditor_config verificam model_name="deepseek-r1:14b" - test_researcher_config verifica model_name em ["llama3.1:8b", "llama3.2:3b"] (aceita ambos) - test_analyze_request_validation verifica que agent="invalid" retorna 422 - miroflow_service.py tem menos de 150 linhas - Nenhuma referência a modelos acima de 14B no código </acceptance_criteria>

miroflow_service.py criado e testado. Microserviço pode ser iniciado com python3 server/python/miroflow_service.py na porta 8006. Todos os testes passam.

```bash # Verificar que o serviço sobe: cd /opt/arcadia_merged && MIROFLOW_PORT=8006 python3 server/python/miroflow_service.py & sleep 3 curl -s http://localhost:8006/health | python3 -m json.tool # Esperado: {"status": "ok", "service": "miroflow", "agents": ["statistician", "fiscal_auditor", "researcher"]} kill %1 ```

<success_criteria>

  • pytest server/python/test_miroflow_service.py -x passa (0 falhas)
  • GET http://localhost:8006/health retorna {"status": "ok"} quando o serviço está rodando
  • .env contém MIROFLOW_PORT=8006
  • llama3.1:8b disponível no Ollama (ou download iniciado)
  • Nenhum modelo acima de 14B referenciado </success_criteria>
Após conclusão, criar `.planning/phases/03-miroflow-embutido/03-01-SUMMARY.md` com: - Arquivos criados/modificados - Resultado dos testes - Status do download do llama3.1:8b - Modelo de fallback usado para Researcher (se aplicável)