Patrones de integración: chat streaming · JSON estructurado · tool use · fine-tuning
// Zero-change migration: mismo SDK de OpenAI, distinto baseURL import OpenAI from "openai"; // Modelos disponibles para NutriTrack export const MODELS = { chat: "accounts/fireworks/models/llama-v3p3-70b-instruct", // asistente dietético extract: "accounts/fireworks/models/llama-v3p3-70b-instruct", // extracción alimentos classify: "accounts/fireworks/models/llama-v3p1-8b-instruct", // clasificación riesgo (coste bajo) tools: "accounts/fireworks/models/firefunction-v2", // function calling } as const; // Singleton reutilizable en toda la app export const fireworks = new OpenAI({ apiKey: process.env.FIREWORKS_API_KEY!, baseURL: "https://api.fireworks.ai/inference/v1", });
// src/lib/nutritrack/chat.ts export async function streamNutriAnswer( question: string, onChunk: (text: string) => void ): Promise<string> { const stream = await fireworks.chat.completions.create({ model: MODELS.chat, stream: true, messages: [ { role: "system", content: "Eres un asistente nutricional experto. " + "Responde siempre en español, con rigor clínico " + "y referencias a evidencia científica cuando sea útil." }, { role: "user", content: question }, ], temperature: 0.4, max_tokens: 1024, }); let full = ""; for await (const chunk of stream) { const text = chunk.choices[0]?.delta?.content ?? ""; full += text; onChunk(text); } return full; } // Uso en route handler de Next.js App Router // await streamNutriAnswer( // "¿Qué alimentos ricos en hierro pueden tomar pacientes con IRC?", // (chunk) => controller.enqueue(encoder.encode(chunk)) // );
// src/lib/nutritrack/extract.ts interface FoodEntry { alimento: string; cantidad_g: number; calorias: number; proteinas_g: number; carbos_g: number; grasas_g: number; } export async function extractMeal( freeText: string ): Promise<FoodEntry[]> { const res = await fireworks.chat.completions.create({ model: MODELS.extract, temperature: 0, response_format: { type: "json_object", schema: { type: "object", properties: { alimentos: { type: "array", items: { type: "object", properties: { alimento: { type: "string" }, cantidad_g: { type: "number" }, calorias: { type: "number" }, proteinas_g: { type: "number" }, carbos_g: { type: "number" }, grasas_g: { type: "number" }, }, required: ["alimento","cantidad_g","calorias"], }, }, }, required: ["alimentos"], }, } as any, messages: [ { role: "system", content: "Extrae los alimentos de la descripción con sus macros estimados." }, { role: "user", content: freeText }, ], }); const { alimentos } = JSON.parse(res.choices[0].message.content!); return alimentos; }
// src/lib/nutritrack/risk.ts interface RiskResult { nivel: "bajo" | "medio" | "alto"; confianza: number; // 0.0 – 1.0 factores: string[]; // e.g. ["déficit proteína", "exceso sodio"] } export async function classifyRisk( weeklyIntake: string ): Promise<RiskResult> { const res = await fetch( "https://api.fireworks.ai/inference/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${process.env.FIREWORKS_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: MODELS.classify, messages: [ { role: "system", content: "Eres un evaluador de riesgo nutricional. Analiza la ingesta semanal y clasifica." }, { role: "user", content: weeklyIntake }, ], response_format: { type: "json_object", schema: { type: "object", properties: { nivel: { type: "string", enum: ["bajo","medio","alto"] }, confianza: { type: "number", minimum: 0, maximum: 1 }, factores: { type: "array", items: { type: "string" } }, }, required: ["nivel","confianza","factores"], }, }, temperature: 0, }), }); const data = await res.json(); return JSON.parse(data.choices[0].message.content) as RiskResult; }
# fine_tune_nutritrack.py — Llama 3.1 8B especializado en nutrición import requests, os, json API_KEY = os.environ["FIREWORKS_API_KEY"] BASE = "https://api.fireworks.ai/inference/v1" HDRS = { "Authorization": f"Bearer {API_KEY}" } # 1. Dataset de entrenamiento (JSONL) # Formato: par instrucción → respuesta del nutricionista TRAINING_EXAMPLES = [ { "messages": [ { "role": "system", "content": "Eres un dietista clínico experto." }, { "role": "user", "content": "Plan semanal para paciente diabético tipo 2, 70 kg." }, { "role": "assistant", "content": "Lunes: desayuno..." }, ]}, # ... 500+ ejemplos validados por nutricionistas ] def upload_dataset(filepath: str) -> str: with open(filepath, "rb") as f: r = requests.post( f"{BASE}/files", headers=HDRS, files={ "file": (filepath, f, "application/jsonl") }, data={ "purpose": "fine-tune" }, ) return r.json()["id"] def create_job(dataset_id: str) -> dict: return requests.post( f"{BASE}/fine_tuning/jobs", headers={ **HDRS, "Content-Type": "application/json" }, json={ "model": "accounts/fireworks/models/llama-v3p1-8b-instruct", "training_file": dataset_id, "hyperparameters": { "n_epochs": 4, # 4 épocas para dominio especializado "learning_rate_multiplier": 0.8, "batch_size": 16, }, }, ).json() # Después del fine-tuning: usar el modelo especializado # model = "accounts/YOUR_ACCOUNT/models/nutritrack-llama-8b-v1" # Resultado: ~60% más barato que 70B, respuestas nutricionales más precisas
| Caso de uso | Modelo | Latencia est. | Coste relativo | Razón |
|---|---|---|---|---|
| Asistente chat (streaming) | llama-v3p3-70b-instruct | ~1.2s TTFT | razonamientoespañol | |
| Extracción de alimentos | llama-v3p3-70b-instruct | ~1.8s | JSON schemaprecisión | |
| Clasificación de riesgo | llama-v3p1-8b-instruct | ~0.6s | tarea simpleenums | |
| Búsqueda en base datos (tool use) | firefunction-v2 | ~0.9s | function callingfiable | |
| Fine-tuned (futuro) dominio | nutritrack-llama-8b-v1 | ~0.5s | especializado-60% coste |
Migración zero-change: mismo SDK de openai, solo cambia el baseURL. No se reescribe código existente.
Fireworks soporta grammar-constrained generation con schema JSON completo: tipos, enums, required. Salida 100% estructurada sin parseo frágil.
Clasificación de riesgo usa 8B (tarea simple + enums). Chat usa 70B (razonamiento). Ahorro estimado: ~40-50% en costes de API.