2026-08-27 22:30:50 -04:00
|
|
|
"""HTTP client for the Nous inference chat completions API."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
|
|
|
|
_DEFAULT_UA = "osint-dashboard-news-summarizer"
|
|
|
|
|
_DEFAULT_BASE = "https://inference-api.nousresearch.com/v1"
|
2026-08-29 20:40:27 -04:00
|
|
|
_JSON_SYSTEM = (
|
|
|
|
|
"You are an OSINT executive briefer. Reply with a single complete JSON object. "
|
|
|
|
|
"Never truncate mid-sentence. If you run out of room, drop the lowest-priority item."
|
|
|
|
|
)
|
2026-08-27 22:30:50 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def chat(prompt, *, api_key, model, base_url, json_mode=False) -> str:
|
|
|
|
|
resolved = (base_url or os.environ.get("NOUS_BASE_URL", _DEFAULT_BASE)).rstrip("/")
|
|
|
|
|
url = f"{resolved}/chat/completions"
|
|
|
|
|
headers = {
|
|
|
|
|
"Authorization": f"Bearer {api_key}",
|
|
|
|
|
"User-Agent": os.environ.get("OSINT_USER_AGENT") or _DEFAULT_UA,
|
|
|
|
|
}
|
2026-08-29 20:40:27 -04:00
|
|
|
max_tokens = 8192 if json_mode else 4096
|
|
|
|
|
timeout = 120.0 if json_mode else 60.0
|
|
|
|
|
messages = [{"role": "user", "content": prompt}]
|
|
|
|
|
if json_mode:
|
|
|
|
|
messages = [
|
|
|
|
|
{"role": "system", "content": _JSON_SYSTEM},
|
|
|
|
|
{"role": "user", "content": prompt},
|
|
|
|
|
]
|
2026-08-27 22:30:50 -04:00
|
|
|
payload = {
|
|
|
|
|
"model": model,
|
2026-08-29 20:40:27 -04:00
|
|
|
"messages": messages,
|
2026-08-27 22:30:50 -04:00
|
|
|
"temperature": 0.2,
|
2026-08-29 20:40:27 -04:00
|
|
|
"max_tokens": max_tokens,
|
2026-08-27 22:30:50 -04:00
|
|
|
}
|
|
|
|
|
if json_mode:
|
|
|
|
|
payload["response_format"] = {"type": "json_object"}
|
2026-08-29 20:40:27 -04:00
|
|
|
last_content = ""
|
2026-08-27 22:30:50 -04:00
|
|
|
try:
|
2026-08-29 20:40:27 -04:00
|
|
|
for attempt in range(2):
|
|
|
|
|
with httpx.Client(timeout=timeout) as client:
|
|
|
|
|
resp = client.post(url, headers=headers, json=payload)
|
|
|
|
|
if resp.status_code == 401 or resp.status_code >= 500:
|
|
|
|
|
return ""
|
|
|
|
|
data = resp.json()
|
|
|
|
|
choice = (data.get("choices") or [{}])[0]
|
|
|
|
|
last_content = (choice.get("message") or {}).get("content") or ""
|
|
|
|
|
finish = choice.get("finish_reason")
|
|
|
|
|
if finish == "length" and attempt == 0:
|
|
|
|
|
payload["max_tokens"] = min(int(payload["max_tokens"]) * 2, 16384)
|
|
|
|
|
continue
|
|
|
|
|
return last_content
|
|
|
|
|
return last_content
|
2026-08-27 22:30:50 -04:00
|
|
|
except Exception:
|
|
|
|
|
return ""
|