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

330 lines
14 KiB
Markdown

---
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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-miroflow-embutido/03-RESEARCH.md
<interfaces>
<!-- Padrão de microserviço FastAPI existente — copiar estrutura de bi_analysis_service.py -->
<!-- server/python/bi_analysis_service.py -->
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)
<!-- MiroFlow agent API — extraído de server/modules/miroflow/miroflow/agents/ -->
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
<!-- Config obrigatória do LLM (campos do base.yaml confirmados) -->
{
"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,
}
}
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Setup — baixar llama3.1:8b e instalar dependências MiroFlow</name>
<files>server/modules/miroflow/ (sem modificar), .env (adicionar MIROFLOW_PORT)</files>
<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>
<action>
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`.
</action>
<verify>
<automated>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"</automated>
</verify>
<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>
<done>Modelo llama3.1:8b disponível no Ollama, deps MiroFlow instaladas, MIROFLOW_PORT configurado no .env</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Criar test_miroflow_service.py + miroflow_service.py (FastAPI porta 8006)</name>
<files>server/python/test_miroflow_service.py, server/python/miroflow_service.py</files>
<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>
<behavior>
- 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}
</behavior>
<action>
PASSO 1 (RED): Criar server/python/test_miroflow_service.py com os 6 testes listados em <behavior>. 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.
</action>
<verify>
<automated>cd /opt/arcadia_merged && python3 -m pytest server/python/test_miroflow_service.py -x -v 2>&1 | tail -20</automated>
</verify>
<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>
<done>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.</done>
</task>
</tasks>
<verification>
```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
```
</verification>
<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>
<output>
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)
</output>