chore: embeddings.py warns on fallback for non-default models (T107)
This commit is contained in:
@@ -10,6 +10,7 @@ EmbeddingResult shape stays the same, only the generator changes.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ from pydantic import BaseModel
|
|||||||
from chat.llm.client import LLMClient
|
from chat.llm.client import LLMClient
|
||||||
|
|
||||||
|
|
||||||
|
_log = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_EMBEDDING_DIM = 384
|
DEFAULT_EMBEDDING_DIM = 384
|
||||||
DEFAULT_EMBEDDING_MODEL = "pseudo-sha256-384"
|
DEFAULT_EMBEDDING_MODEL = "pseudo-sha256-384"
|
||||||
FALLBACK_EMBEDDING_MODEL = "fallback"
|
FALLBACK_EMBEDDING_MODEL = "fallback"
|
||||||
@@ -93,7 +96,15 @@ async def generate_embedding(
|
|||||||
return EmbeddingResult(vector=_pseudo_embed(text, dim), model=model, dim=dim)
|
return EmbeddingResult(vector=_pseudo_embed(text, dim), model=model, dim=dim)
|
||||||
|
|
||||||
# Future: real embedding via client.embed(...). Phase 4.5 work.
|
# Future: real embedding via client.embed(...). Phase 4.5 work.
|
||||||
# For Phase 4, any non-default model falls through to fallback.
|
# For Phase 4, any non-default model falls through to fallback —
|
||||||
|
# warn so misconfigured callers (e.g., a real-model swap that isn't
|
||||||
|
# wired up yet) don't silently degrade to a zero vector.
|
||||||
|
_log.warning(
|
||||||
|
"generate_embedding: non-default model %r returned fallback "
|
||||||
|
"(model client.embed() not yet implemented in Phase 4.5+); "
|
||||||
|
"downstream search will degrade silently. Configure a supported model.",
|
||||||
|
model,
|
||||||
|
)
|
||||||
return EmbeddingResult(
|
return EmbeddingResult(
|
||||||
vector=[0.0] * dim, model=FALLBACK_EMBEDDING_MODEL, dim=dim
|
vector=[0.0] * dim, model=FALLBACK_EMBEDDING_MODEL, dim=dim
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ The pseudo path doesn't touch the LLMClient, so we pass an empty
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -89,3 +90,33 @@ async def test_generate_embedding_unit_normalized():
|
|||||||
result = await generate_embedding(_client(), text="some non-empty text")
|
result = await generate_embedding(_client(), text="some non-empty text")
|
||||||
norm_sq = sum(x * x for x in result.vector)
|
norm_sq = sum(x * x for x in result.vector)
|
||||||
assert math.isclose(norm_sq, 1.0, abs_tol=1e-6)
|
assert math.isclose(norm_sq, 1.0, abs_tol=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_embedding_non_default_model_logs_warning(caplog):
|
||||||
|
"""T107: non-default model falls through to fallback and must warn.
|
||||||
|
|
||||||
|
A Phase 4.5+ caller pointing at a real model that isn't yet wired
|
||||||
|
up would otherwise silently degrade (zero vector → useless cosine).
|
||||||
|
The warning surfaces the misconfiguration in logs.
|
||||||
|
"""
|
||||||
|
caplog.set_level(logging.WARNING, logger="chat.services.embeddings")
|
||||||
|
result = await generate_embedding(_client(), text="hello", model="real-model")
|
||||||
|
|
||||||
|
# Behavior unchanged: still returns the fallback sentinel.
|
||||||
|
assert result.model == FALLBACK_EMBEDDING_MODEL == "fallback"
|
||||||
|
assert all(x == 0.0 for x in result.vector)
|
||||||
|
|
||||||
|
# Warning fired and names the offending model.
|
||||||
|
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
|
||||||
|
assert any("non-default model" in r.getMessage() for r in warnings)
|
||||||
|
assert any("real-model" in r.getMessage() for r in warnings)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_embedding_default_model_does_not_warn(caplog):
|
||||||
|
"""T107: the silent default path must stay silent."""
|
||||||
|
caplog.set_level(logging.WARNING, logger="chat.services.embeddings")
|
||||||
|
await generate_embedding(_client(), text="hello")
|
||||||
|
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
|
||||||
|
assert warnings == []
|
||||||
|
|||||||
Reference in New Issue
Block a user