--- id: "03-01" phase: 03-miroflow-embutido plan: 01 type: execute wave: 0 depends_on: [] files_modified: - server/python/miroflow_service.py - server/python/test_miroflow_service.py autonomous: true requirements: - REQ-3.1 - REQ-3.2 - REQ-3.3 - REQ-3.4 must_haves: truths: - "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" artifacts: - path: "server/python/miroflow_service.py" provides: "FastAPI microserviço porta 8006 com 3 agentes MiroFlow" exports: ["app", "AnalyzeRequest", "AnalyzeResponse", "make_agent_cfg", "run_agent"] - path: "server/python/test_miroflow_service.py" provides: "Testes pytest para REQ-3.1 a REQ-3.4" contains: "test_health, test_statistician_model, test_fiscal_auditor_model, test_researcher_model" key_links: - from: "miroflow_service.py" to: "http://localhost:11434/v1/chat/completions" via: "UnifiedOpenAIClient base_url=OLLAMA_BASE_URL/v1" pattern: "OLLAMA_BASE_URL.*v1" - from: "miroflow_service.py" to: "server/modules/miroflow" via: "sys.path.insert(0, /app/server/modules/miroflow)" pattern: "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. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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) - /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) 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" - `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 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 - /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) - 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 - `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 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 ``` - `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 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)