From ec353339268b3f16a98c7acedfaffc0a4fb215f8 Mon Sep 17 00:00:00 2001 From: drjones Date: Sat, 15 Aug 2026 09:28:07 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20bump=20llm=5Fchat=20timeout=2060s?= =?UTF-8?q?=E2=86=92600s=20for=20qwen3.8=20long-form=20(was=20timing=20out?= =?UTF-8?q?=20on=20article=20writing)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/orchestrator.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/core/orchestrator.py b/core/orchestrator.py index 9c93ab7..ef8b48f 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -16,7 +16,8 @@ import requests BASE_DIR = Path(__file__).resolve().parent.parent DB_PATH = BASE_DIR / "core" / "publisher.db" OLLAMA_MACBOOK = "http://localhost:11434" -OLLAMA_GAMINGPC = "http://10.30.20.186:11434" # RTX 3070, ornith:latest +OLLAMA_GAMINGPC = "http://10.30.20.186:11434" # RTX 3070, ornith:latest (fallback) +OLLAMA_SHADOW = "http://10.30.20.128:11434" # RTX 4080 SUPER, qwen3.8:latest (primary) # Load API keys from Hermes env if not already set _hermes_env = Path.home() / ".hermes" / ".env" @@ -176,7 +177,7 @@ def _call_deepseek(prompt: str, model: str = "deepseek-chat", system: str = "", raise RuntimeError(f"DeepSeek API error {r.status_code}: {r.text[:200]}") -def llm_chat(prompt: str, model: str = "qwen3.5:4b-mlx", host: str = OLLAMA_MACBOOK, +def llm_chat(prompt: str, model: str = "qwen3.8:latest", host: str = OLLAMA_SHADOW, system: str = "", temperature: float = 0.7, max_tokens: int = 4096, retries: int = 3) -> str: """Call LLM with DeepSeek cloud → Ollama fallback, with retries.""" @@ -188,18 +189,19 @@ def llm_chat(prompt: str, model: str = "qwen3.5:4b-mlx", host: str = OLLAMA_MACB log.warning(f"DeepSeek failed, trying local Ollama: {e}") payload = { "model": model, "messages": [], "stream": False, - "options": {"temperature": temperature, "num_predict": max_tokens} + "think": False, + "options": {"temperature": temperature, "num_predict": max_tokens, "num_ctx": 8192} } if system: payload["messages"].append({"role": "system", "content": system}) payload["messages"].append({"role": "user", "content": prompt}) # Try Ollama hosts first - hosts = list(dict.fromkeys([host, OLLAMA_MACBOOK, OLLAMA_GAMINGPC])) + hosts = list(dict.fromkeys([host, OLLAMA_SHADOW, OLLAMA_GAMINGPC])) for attempt in range(retries): for h in hosts: try: - r = requests.post(f"{h}/api/chat", json=payload, timeout=60 * (attempt + 1), + r = requests.post(f"{h}/api/chat", json=payload, timeout=600, proxies={"http": None, "https": None}) if r.status_code == 200: result = r.json() @@ -235,7 +237,7 @@ def llm_chat(prompt: str, model: str = "qwen3.5:4b-mlx", host: str = OLLAMA_MACB raise RuntimeError(f"All LLM hosts failed for model {model}") -def llm_json(prompt: str, model: str = "qwen3.5:4b-mlx", host: str = OLLAMA_MACBOOK, +def llm_json(prompt: str, model: str = "qwen3.8:latest", host: str = OLLAMA_SHADOW, system: str = "You are a JSON-only API. Always respond with valid JSON. No markdown, no explanation.", temperature: float = 0.3) -> dict: """Call LLM and parse JSON response.""" @@ -252,11 +254,11 @@ def dual_llm_research(prompt: str, system: str = "") -> tuple[str, dict]: import concurrent.futures def call_ornith(): - return llm_chat(prompt, model="ornith:latest", host=OLLAMA_GAMINGPC, + return llm_chat(prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, system=system, temperature=0.3, max_tokens=4096) def call_qwen(): - return llm_chat(prompt, model="qwen3.5:4b-mlx", host=OLLAMA_MACBOOK, + return llm_chat(prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, system=system, temperature=0.3, max_tokens=2048) with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: @@ -452,7 +454,7 @@ Cover these verticals: AI/ML, general tech, science, cryptocurrency, Linux, gami Respond with a JSON array of strings, each a compelling article title.""" try: - result = llm_json(prompt, model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, temperature=0.8) + result = llm_json(prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, temperature=0.8) if isinstance(result, list): return result return list(result.values())[0] if result else [] @@ -641,11 +643,11 @@ Extract and return as JSON: Be accurate. Cite real sources. No hallucinations. Respond with ONLY valid JSON.""" try: - result = llm_json(research_prompt, model="ornith:latest", host=OLLAMA_GAMINGPC, + result = llm_json(research_prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, system="You are an expert research analyst. You produce accurate, well-cited research. Never fabricate information.") except Exception as e: log.error(f"Research LLM failed: {e}. Falling back to MacBook.") - result = llm_json(research_prompt, model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, + result = llm_json(research_prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, system="You are an expert research analyst. Be accurate and honest.") # Store knowledge package @@ -831,8 +833,8 @@ Dark background matching the site's aesthetic. Abstract but relevant to the topi verify = llm_chat( f"""Examine this image and verify it's appropriate for an article titled "{title}" on a {vertical} website. Is the image relevant, coherent, and free of inappropriate content? Respond ONLY with "PASS" or "FAIL: ".""", - model="minicpm-v4.6:1b", - host=OLLAMA_MACBOOK, + model="qwen3.8:latest", + host=OLLAMA_SHADOW, system="You are an image quality reviewer. Be strict but fair.", temperature=0.1, max_tokens=50, @@ -875,7 +877,7 @@ Generate an outline appropriate for this format. Respond with JSON: {{"sections": [{{"heading": "...", "subsections": ["..."]}}, ...], "faq_questions": ["..."], "cta": "..."}}""" - outline = llm_json(outline_prompt, model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, temperature=0.5) + outline = llm_json(outline_prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, temperature=0.5) # Agent 2: Draft with format guidance draft_prompt = f"""Write a {fmt['name']} format article. @@ -902,7 +904,7 @@ Requirements: Respond with the FULL Markdown article. No JSON wrapper.""" - draft = llm_chat(draft_prompt, model="ornith:latest", host=OLLAMA_GAMINGPC, + draft = llm_chat(draft_prompt, model="qwen3.8:latest", host=OLLAMA_SHADOW, system="You are an expert writer. Write clear, accurate, engaging content. No AI clichés. No fluff.", temperature=0.75, max_tokens=8192) @@ -914,14 +916,14 @@ ARTICLE: {draft} Return the edited article in full Markdown. No JSON wrapper.""", - model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, temperature=0.3, max_tokens=8192) + model="qwen3.8:latest", host=OLLAMA_SHADOW, temperature=0.3, max_tokens=8192) # Agent 4: SEO seo = llm_json(f"""Optimize this article for SEO. TITLE: {topic_title} FIRST 500 CHARS: {edited[:500]} Respond with JSON: {{"seo_title": "...", "seo_description": "...", "keywords": ["..."]}}""", - model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, temperature=0.3) + model="qwen3.8:latest", host=OLLAMA_SHADOW, temperature=0.3) # Agent 5: Real Fact Check (web-verified) factcheck = real_fact_check(edited, topic_title) @@ -942,7 +944,7 @@ ARTICLE: {edited} Return the expanded article in full Markdown. No JSON wrapper.""", - model="minicpm-v4.5:8b", host=OLLAMA_GAMINGPC, temperature=0.5, max_tokens=8192) + model="qwen3.8:latest", host=OLLAMA_SHADOW, temperature=0.5, max_tokens=8192) passed, issues = quality_gate(edited, topic_title, vertical) if not passed: