feat: implement /analyze endpoint for MiroFlow scientific agents
- Add POST /analyze handler in engine-proxy.ts (Node host, direct Ollama access) - Routes agent requests (statistician/fiscal_auditor/researcher) to deepseek-r1:14b - Fallback to llama3.2:3b if primary model unavailable - Fixes BiWorkspace "Científico" tab end-to-end flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
53b9fc5408
commit
59bfca8c70
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Express, Request, Response } from "express";
|
import type { Express, Request, Response } from "express";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
|
||||||
const MIROFLOW_HOST = process.env.MIROFLOW_HOST || "localhost";
|
const MIROFLOW_HOST = process.env.MIROFLOW_HOST || "localhost";
|
||||||
const MIROFLOW_PORT = parseInt(process.env.MIROFLOW_PORT || "8006", 10);
|
const MIROFLOW_PORT = parseInt(process.env.MIROFLOW_PORT || "8006", 10);
|
||||||
|
|
@ -6,6 +7,61 @@ const MIROFLOW_URL = `http://${MIROFLOW_HOST}:${MIROFLOW_PORT}`;
|
||||||
const MIROFLOW_TIMEOUT = 600_000; // 10 minutos
|
const MIROFLOW_TIMEOUT = 600_000; // 10 minutos
|
||||||
const MIROFLOW_HEALTH_TIMEOUT = 5_000;
|
const MIROFLOW_HEALTH_TIMEOUT = 5_000;
|
||||||
|
|
||||||
|
const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434";
|
||||||
|
|
||||||
|
const AGENT_CONFIGS: Record<string, { model: string; system: string }> = {
|
||||||
|
statistician: {
|
||||||
|
model: "deepseek-r1:14b",
|
||||||
|
system:
|
||||||
|
"Você é um estatístico especializado em análise de dados empresariais. " +
|
||||||
|
"Responda em português brasileiro. Forneça análises quantitativas claras, " +
|
||||||
|
"com estatísticas descritivas, padrões e insights acionáveis. Seja preciso e objetivo.",
|
||||||
|
},
|
||||||
|
fiscal_auditor: {
|
||||||
|
model: "deepseek-r1:14b",
|
||||||
|
system:
|
||||||
|
"Você é um auditor fiscal especializado em compliance tributário brasileiro. " +
|
||||||
|
"Responda em português brasileiro. Identifique inconsistências, riscos fiscais " +
|
||||||
|
"e recomende ações corretivas. Cite legislação quando relevante.",
|
||||||
|
},
|
||||||
|
researcher: {
|
||||||
|
model: "deepseek-r1:14b",
|
||||||
|
system:
|
||||||
|
"Você é um pesquisador analítico especializado em inteligência de negócios. " +
|
||||||
|
"Responda em português brasileiro. Identifique correlações, tendências e " +
|
||||||
|
"hipóteses baseadas em dados. Apresente achados de forma estruturada.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FALLBACK_MODEL = "llama3.2:3b";
|
||||||
|
|
||||||
|
async function callOllama(model: string, system: string, prompt: string): Promise<string> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 300_000);
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${OLLAMA_URL}/api/chat`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: system },
|
||||||
|
{ role: "user", content: prompt },
|
||||||
|
],
|
||||||
|
stream: false,
|
||||||
|
}),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (!resp.ok) throw new Error(`Ollama error: ${resp.status}`);
|
||||||
|
const data: any = await resp.json();
|
||||||
|
return data.message?.content ?? "";
|
||||||
|
} catch (err: any) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function proxyToMiroFlow(path: string, method: string = "GET", body?: object): Promise<any> {
|
async function proxyToMiroFlow(path: string, method: string = "GET", body?: object): Promise<any> {
|
||||||
const timeoutMs = path === "/health" ? MIROFLOW_HEALTH_TIMEOUT : MIROFLOW_TIMEOUT;
|
const timeoutMs = path === "/health" ? MIROFLOW_HEALTH_TIMEOUT : MIROFLOW_TIMEOUT;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|
@ -43,12 +99,46 @@ export function registerMiroFlowRoutes(app: Express): void {
|
||||||
app.post("/api/miroflow/analyze", async (req: Request, res: Response) => {
|
app.post("/api/miroflow/analyze", async (req: Request, res: Response) => {
|
||||||
console.log("[MiroFlow] POST /api/miroflow/analyze - começando");
|
console.log("[MiroFlow] POST /api/miroflow/analyze - começando");
|
||||||
try {
|
try {
|
||||||
const inputBody = {
|
const { agent = "statistician", task, context } = req.body as {
|
||||||
...req.body,
|
agent?: string;
|
||||||
tenant_id: (req.user as any)?.tenantId ?? null,
|
task: string;
|
||||||
|
context?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
const data = await proxyToMiroFlow("/analyze", "POST", inputBody);
|
|
||||||
res.json(data);
|
if (!task?.trim()) {
|
||||||
|
return res.status(400).json({ error: "Campo 'task' é obrigatório" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentKey = AGENT_CONFIGS[agent] ? agent : "statistician";
|
||||||
|
const cfg = AGENT_CONFIGS[agentKey];
|
||||||
|
|
||||||
|
let prompt = task;
|
||||||
|
if (context && Object.keys(context).length > 0) {
|
||||||
|
const ctxStr = Object.entries(context)
|
||||||
|
.filter(([, v]) => v != null)
|
||||||
|
.map(([k, v]) => `${k}: ${v}`)
|
||||||
|
.join("\n");
|
||||||
|
if (ctxStr) prompt = `Contexto disponível:\n${ctxStr}\n\nTarefa: ${task}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
let model = cfg.model;
|
||||||
|
let result: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await callOllama(model, cfg.system, prompt);
|
||||||
|
} catch {
|
||||||
|
model = FALLBACK_MODEL;
|
||||||
|
result = await callOllama(model, cfg.system, prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
execution_id: randomUUID(),
|
||||||
|
agent: agentKey,
|
||||||
|
model,
|
||||||
|
result,
|
||||||
|
duration_ms: Date.now() - start,
|
||||||
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("[MiroFlow] erro:", err.message);
|
console.error("[MiroFlow] erro:", err.message);
|
||||||
res.status(502).json({ error: err.message });
|
res.status(502).json({ error: err.message });
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue