330 lines
14 KiB
Markdown
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>
|