← back to Trademarks Copyright

src/lib/qwen.ts

59 lines

/**
 * Local Qwen via Ollama at http://localhost:11434.
 * Used for brand-name extraction and discovery suggestions in the unregistered-brand hunter.
 */

// P1 fix 2026-05-04: default to MS1 per `feedback_ollama_default_ms1.md`.
// Mac2 froze 2026-05-02 under GPU contention; trademarks-copyright pipelines
// (hunt + score + swot + drops) are bulk LLM workloads that should hit MS1.
const OLLAMA_URL = process.env.OLLAMA_URL || "http://192.168.1.133:11434";
const QWEN_MODEL = process.env.QWEN_MODEL || "qwen2.5:latest";

export async function qwen(
  prompt: string,
  opts: { temperature?: number; format?: "json"; model?: string } = {}
): Promise<string> {
  const body: Record<string, unknown> = {
    model: opts.model ?? QWEN_MODEL,
    prompt,
    stream: false,
    options: { temperature: opts.temperature ?? 0.2 },
  };
  if (opts.format === "json") body.format = "json";

  // P2 fix 2026-05-04: 120s timeout via AbortSignal. Without it a stalled
  // Ollama hangs the route handler until Next.js maxDuration (300s) triggers.
  const r = await fetch(`${OLLAMA_URL}/api/generate`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(120_000),
  });
  if (!r.ok) throw new Error(`Qwen HTTP ${r.status}: ${await r.text().catch(() => "")}`);
  const j = (await r.json()) as { response: string };
  return j.response.trim();
}

export async function qwenJSON<T = unknown>(prompt: string, temperature = 0.2): Promise<T> {
  const raw = await qwen(prompt, { temperature, format: "json" });
  try {
    return JSON.parse(raw) as T;
  } catch {
    // Sometimes the model wraps JSON in fences — strip and retry.
    const m = raw.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
    if (m) return JSON.parse(m[0]) as T;
    throw new Error(`Qwen JSON parse failed: ${raw.slice(0, 200)}`);
  }
}

export async function qwenIsUp(): Promise<boolean> {
  try {
    const r = await fetch(`${OLLAMA_URL}/api/tags`);
    return r.ok;
  } catch {
    return false;
  }
}

export const QWEN_MODEL_NAME = QWEN_MODEL;