Skip to content

Commit 4903727

Browse files
authored
Merge pull request #9 from webstackdev/feature/embeddings
Feature/embeddings
2 parents 5ef2171 + e8522bc commit 4903727

17 files changed

Lines changed: 738 additions & 3 deletions

File tree

‎.env.example‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ OPENROUTER_API_KEY=
1111
OPENROUTER_API_BASE=https://openrouter.ai/api/v1
1212
OPENROUTER_APP_URL=
1313
OPENROUTER_APP_NAME=newsletter-maker
14+
AI_CLASSIFICATION_MODEL=meta-llama/llama-3.1-70b-instruct
15+
AI_RELEVANCE_MODEL=qwen/qwen-2.5-72b-instruct
16+
AI_SUMMARIZATION_MODEL=google/gemma-3-27b-it
17+
AI_CLASSIFICATION_REVIEW_THRESHOLD=0.6
18+
AI_RELEVANCE_LOW_THRESHOLD=0.5
19+
AI_RELEVANCE_HIGH_THRESHOLD=0.85
20+
AI_RELEVANCE_REVIEW_THRESHOLD=0.4
21+
AI_RELEVANCE_SUMMARIZE_THRESHOLD=0.7
22+
AI_MAX_NODE_RETRIES=2
23+
AI_REQUEST_TIMEOUT_SECONDS=60
1424
EMBEDDING_PROVIDER=sentence-transformers
1525
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
1626
EMBEDDING_TRUST_REMOTE_CODE=false

‎.vscode/settings.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"noinput",
1919
"nomic",
2020
"OLLAMA",
21+
"ormsgpack",
2122
"PRAW",
2223
"psycopg",
2324
"pylint",
@@ -28,6 +29,8 @@
2829
"readyz",
2930
"Referer",
3031
"upserted",
31-
"upvote"
32+
"upvote",
33+
"uritemplate",
34+
"xxhash"
3235
]
3336
}

‎celerybeat-schedule‎

0 Bytes
Binary file not shown.

‎core/llm.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import re
5+
import time
6+
from dataclasses import dataclass
7+
from typing import Any
8+
9+
import httpx
10+
from django.conf import settings
11+
12+
JSON_OBJECT_PATTERN = re.compile(r"\{.*\}", re.DOTALL)
13+
14+
15+
@dataclass(slots=True)
16+
class OpenRouterJSONResponse:
17+
payload: dict[str, Any]
18+
model: str
19+
latency_ms: int
20+
21+
22+
def openrouter_chat_json(*, model: str, system_prompt: str, user_prompt: str) -> OpenRouterJSONResponse:
23+
if not settings.OPENROUTER_API_KEY:
24+
raise RuntimeError("OPENROUTER_API_KEY must be configured for OpenRouter chat completions.")
25+
26+
headers = {
27+
"Authorization": f"Bearer {settings.OPENROUTER_API_KEY}",
28+
"Content-Type": "application/json",
29+
}
30+
if settings.OPENROUTER_APP_URL:
31+
headers["HTTP-Referer"] = settings.OPENROUTER_APP_URL
32+
if settings.OPENROUTER_APP_NAME:
33+
headers["X-OpenRouter-Title"] = settings.OPENROUTER_APP_NAME
34+
35+
started_at = time.perf_counter()
36+
response = httpx.post(
37+
f"{settings.OPENROUTER_API_BASE.rstrip('/')}/chat/completions",
38+
headers=headers,
39+
json={
40+
"model": model,
41+
"temperature": 0,
42+
"response_format": {"type": "json_object"},
43+
"messages": [
44+
{"role": "system", "content": system_prompt},
45+
{"role": "user", "content": user_prompt},
46+
],
47+
},
48+
timeout=settings.AI_REQUEST_TIMEOUT_SECONDS,
49+
)
50+
latency_ms = int((time.perf_counter() - started_at) * 1000)
51+
response.raise_for_status()
52+
53+
message_content = response.json()["choices"][0]["message"]["content"]
54+
return OpenRouterJSONResponse(
55+
payload=_extract_json_object(message_content),
56+
model=model,
57+
latency_ms=latency_ms,
58+
)
59+
60+
61+
def _extract_json_object(message_content: str) -> dict[str, Any]:
62+
try:
63+
payload = json.loads(message_content)
64+
except json.JSONDecodeError:
65+
match = JSON_OBJECT_PATTERN.search(message_content)
66+
if not match:
67+
raise ValueError("Model response did not contain a JSON object.")
68+
payload = json.loads(match.group(0))
69+
if not isinstance(payload, dict):
70+
raise ValueError("Model response JSON must be an object.")
71+
return payload

0 commit comments

Comments
 (0)