feat: add embeddings endpoint and config for OpenAI-compatible text embeddings via gateway
Add POST /openai/v1/embeddings route in openai_gateway router, new config fields for embedding upstream URL/model/key/enabled toggle with defaults pointing to OpenRouter Qwen3 8B, INTERNAL_EMBED_MODEL constant in config.py, documentation in docs_api.py and README.md describing the molodetz~embed model mapping, and embed-call tracking in gateway metrics alongside existing chat/vision counters.
This commit is contained in:
@@ -202,3 +202,85 @@ def test_authorize_require_auth_off_is_open(local_db):
|
||||
assert svc.authorize(_make_request_openai_gateway()) is True
|
||||
finally:
|
||||
set_setting("gateway_require_auth", "1")
|
||||
|
||||
|
||||
class FakeEmbedClient_openai_gateway:
|
||||
def __init__(self, *a, **k):
|
||||
self.calls = []
|
||||
|
||||
def build_request(self, method, url, headers=None, json=None, content=None):
|
||||
return FakeRequest(method, url, json)
|
||||
|
||||
async def send(self, request):
|
||||
self.calls.append((request.url, request.json_body))
|
||||
body = request.json_body or {}
|
||||
return FakeResp_openai_gateway(
|
||||
payload={
|
||||
"object": "list",
|
||||
"model": body.get("model"),
|
||||
"data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2]}],
|
||||
"usage": {"prompt_tokens": 5, "total_tokens": 5},
|
||||
}
|
||||
)
|
||||
|
||||
async def aclose(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_embeddings_remaps_alias_to_configured_model(local_db, monkeypatch):
|
||||
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
|
||||
svc = GatewayService()
|
||||
cfg = svc.effective_config()
|
||||
cfg["gateway_force_model"] = False
|
||||
cfg["gateway_embed_enabled"] = True
|
||||
cfg["gateway_embed_model"] = "qwen/qwen3-embedding-8b"
|
||||
rt = svc.runtime()
|
||||
run_async(
|
||||
rt.handle_embeddings(
|
||||
{"model": "molodetz~embed", "input": "hello"},
|
||||
cfg,
|
||||
("guest", "test"),
|
||||
"test",
|
||||
)
|
||||
)
|
||||
assert rt._client.calls[-1][1]["model"] == "qwen/qwen3-embedding-8b"
|
||||
|
||||
|
||||
def test_embeddings_disabled_returns_503(local_db, monkeypatch):
|
||||
monkeypatch.setattr(gwmod.httpx, "AsyncClient", FakeEmbedClient_openai_gateway)
|
||||
svc = GatewayService()
|
||||
cfg = svc.effective_config()
|
||||
cfg["gateway_embed_enabled"] = False
|
||||
rt = svc.runtime()
|
||||
resp = run_async(
|
||||
rt.handle_embeddings(
|
||||
{"input": "hello"}, cfg, ("guest", "test"), "test"
|
||||
)
|
||||
)
|
||||
assert resp.status_code == 503
|
||||
|
||||
|
||||
def test_compute_cost_embed_branch():
|
||||
from devplacepy.services.openai_gateway.usage import (
|
||||
Pricing,
|
||||
compute_cost,
|
||||
normalize_usage,
|
||||
)
|
||||
|
||||
pricing = Pricing(
|
||||
chat_cache_hit_per_m=0.0,
|
||||
chat_cache_miss_per_m=0.0,
|
||||
chat_output_per_m=0.0,
|
||||
vision_input_per_m=0.0,
|
||||
vision_output_per_m=0.0,
|
||||
embed_input_per_m=0.01,
|
||||
)
|
||||
usage = {"prompt_tokens": 1_000_000, "total_tokens": 1_000_000}
|
||||
norm = normalize_usage(usage)
|
||||
total, input_cost, output_cost, native = compute_cost(
|
||||
usage, norm, pricing, "embed"
|
||||
)
|
||||
assert native is False
|
||||
assert output_cost == 0.0
|
||||
assert abs(total - 0.01) < 1e-9
|
||||
assert abs(input_cost - 0.01) < 1e-9
|
||||
|
||||
Reference in New Issue
Block a user