mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
feat(learn): metered embeddings and the cache (decision-engine 6.2)
plane/models/openrouter-embedding.yaml: openai/text-embedding-3-small via OpenRouter (embeddings only), input_per_m 0.02, cache/output 0, egress openrouter.ai; `openrouter` joins the provider extensions. plane/budget.py MeteredEmbeddingClient: prompt_tokens priced from the Model manifest, usd/calls ceilings stop the next call, a priced call that reports no tokens raises UnmeteredUsage (a silent zero is an instrument failure). OpenRouterEmbeddingClient keeps one usage row per call (parse_embedding_payload); its api key is out of repr. learn/embeddings.py + `ousast learn memory embed`: excerpts embedded in batches into embeddings/<model-slug>/<excerpt_sha>.json (float32 base64), cached by content hash so a rerun makes no call; the declared dimension is checked; --dry-run estimates tokens and cost without a call; a paid run needs --budget-usd and exits 3 (unfinished) at its ceiling. Not run against the provider: the store embedding waits for its budget go-ahead. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b81c9ac4e
commit
98fa4ec8cd
@@ -8,7 +8,7 @@ import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TypeVar, cast
|
||||
|
||||
|
||||
@@ -135,10 +135,11 @@ class OpenRouterChatClient:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OpenRouterEmbeddingClient:
|
||||
api_key: str
|
||||
api_key: str = field(repr=False)
|
||||
base_url: str = "https://openrouter.ai/api/v1"
|
||||
max_attempts: int = 3
|
||||
retry_base_delay: float = 0.5
|
||||
usage: list[dict[str, object]] = field(default_factory=list, compare=False, repr=False) # one per call, for the meter
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> OpenRouterEmbeddingClient:
|
||||
@@ -167,7 +168,16 @@ class OpenRouterEmbeddingClient:
|
||||
response_payload = call_with_retry(_do, attempts=self.max_attempts, base_delay=self.retry_base_delay)
|
||||
except (urllib.error.URLError, TimeoutError, http.client.HTTPException, json.JSONDecodeError) as exc:
|
||||
raise OpenRouterError(f"OpenRouter embedding request failed: {type(exc).__name__}: {exc}") from exc
|
||||
return parse_embedding_response(response_payload)
|
||||
vectors, usage = parse_embedding_payload(response_payload)
|
||||
self.usage.append(usage)
|
||||
return vectors
|
||||
|
||||
|
||||
def parse_embedding_payload(payload: object) -> tuple[list[list[float]], dict[str, object]]:
|
||||
"""The vectors and the provider's ``usage`` (``{}`` when absent: the meter treats that as unmetered, not free)."""
|
||||
vectors = parse_embedding_response(payload)
|
||||
usage = payload.get("usage") if isinstance(payload, dict) else None
|
||||
return vectors, dict(usage) if isinstance(usage, dict) else {}
|
||||
|
||||
|
||||
def parse_embedding_response(payload: object) -> list[list[float]]:
|
||||
|
||||
Reference in New Issue
Block a user