forked from retoor/devplacepy
feat: enforce hard test-tier requirement across all DevPlace workflow agents and feature-builder docs
Update the feature-builder agent prompt, test-maintainer agent, and all four workflow JS files (devii-tool, endpoint, feature, job-service) to codify the DevPlace test standard as a non-optional project requirement: one test file per endpoint, directory tree mirroring the URL/source path, split into three tiers (unit, api, e2e). Add explicit Test phases to devii-tool, endpoint, feature, and job-service workflows, and embed tier-specific test instructions (path mapping, fixture choice, coverage scope) directly in each workflow's meta description and TESTS constant.
This commit is contained in:
@@ -6,7 +6,8 @@ import hashlib
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from devplacepy import stealth
|
||||
from devplacepy.config import INTERNAL_EMBED_MODEL, INTERNAL_EMBED_URL
|
||||
@@ -22,6 +23,23 @@ TOKEN_PATTERN = re.compile(r"[a-z0-9]+")
|
||||
class EmbedResult:
|
||||
vectors: list[list[float]]
|
||||
backend: str
|
||||
latency_ms: int = 0
|
||||
cache_hits: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class EmbeddingCache:
|
||||
store: dict[str, list[float]] = field(default_factory=dict)
|
||||
|
||||
def key(self, text: str) -> str:
|
||||
return hashlib.sha1((text or "").encode("utf-8")).hexdigest()
|
||||
|
||||
def get(self, text: str) -> list[float] | None:
|
||||
return self.store.get(self.key(text))
|
||||
|
||||
def put(self, text: str, vector: list[float]) -> None:
|
||||
if vector:
|
||||
self.store[self.key(text)] = vector
|
||||
|
||||
|
||||
def _local_vector(text: str) -> list[float]:
|
||||
@@ -41,19 +59,51 @@ def _local_vector(text: str) -> list[float]:
|
||||
|
||||
|
||||
def local_embed(texts: list[str]) -> EmbedResult:
|
||||
return EmbedResult(vectors=[_local_vector(text) for text in texts], backend="local")
|
||||
start = time.monotonic()
|
||||
vectors = [_local_vector(text) for text in texts]
|
||||
latency_ms = int((time.monotonic() - start) * 1000)
|
||||
return EmbedResult(vectors=vectors, backend="local", latency_ms=latency_ms)
|
||||
|
||||
|
||||
async def embed_texts(
|
||||
texts: list[str], api_key: str, *, gateway_url: str = INTERNAL_EMBED_URL
|
||||
texts: list[str],
|
||||
api_key: str,
|
||||
*,
|
||||
gateway_url: str = INTERNAL_EMBED_URL,
|
||||
cache: EmbeddingCache | None = None,
|
||||
) -> EmbedResult:
|
||||
if not texts:
|
||||
return EmbedResult(vectors=[], backend="empty")
|
||||
cache_hits = 0
|
||||
pending_index: list[int] = []
|
||||
pending_text: list[str] = []
|
||||
resolved: list[list[float] | None] = [None] * len(texts)
|
||||
if cache is not None:
|
||||
for index, text in enumerate(texts):
|
||||
cached_vector = cache.get(text)
|
||||
if cached_vector is not None:
|
||||
resolved[index] = cached_vector
|
||||
cache_hits += 1
|
||||
else:
|
||||
pending_index.append(index)
|
||||
pending_text.append(text)
|
||||
else:
|
||||
pending_index = list(range(len(texts)))
|
||||
pending_text = list(texts)
|
||||
if not pending_text:
|
||||
return EmbedResult(
|
||||
vectors=[vector or [] for vector in resolved],
|
||||
backend="cache",
|
||||
latency_ms=0,
|
||||
cache_hits=cache_hits,
|
||||
)
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"model": INTERNAL_EMBED_MODEL, "input": texts}
|
||||
payload = {"model": INTERNAL_EMBED_MODEL, "input": pending_text}
|
||||
start = time.monotonic()
|
||||
backend = "gateway"
|
||||
try:
|
||||
async with stealth.stealth_async_client(timeout=EMBED_TIMEOUT_SECONDS) as client:
|
||||
response = await client.post(gateway_url, json=payload, headers=headers)
|
||||
@@ -61,10 +111,22 @@ async def embed_texts(
|
||||
raise RuntimeError(f"embed gateway returned {response.status_code}")
|
||||
data = response.json()
|
||||
rows = data.get("data") or []
|
||||
vectors = [row.get("embedding") or [] for row in rows]
|
||||
if len(vectors) != len(texts) or any(not vector for vector in vectors):
|
||||
fresh = [row.get("embedding") or [] for row in rows]
|
||||
if len(fresh) != len(pending_text) or any(not vector for vector in fresh):
|
||||
raise RuntimeError("embed gateway returned an incomplete response")
|
||||
return EmbedResult(vectors=vectors, backend="gateway")
|
||||
except Exception as exc:
|
||||
logger.warning("deepsearch embedding gateway failed, using local: %s", exc)
|
||||
return local_embed(texts)
|
||||
fresh = [_local_vector(text) for text in pending_text]
|
||||
backend = "local"
|
||||
latency_ms = int((time.monotonic() - start) * 1000)
|
||||
for offset, index in enumerate(pending_index):
|
||||
vector = fresh[offset]
|
||||
resolved[index] = vector
|
||||
if cache is not None:
|
||||
cache.put(pending_text[offset], vector)
|
||||
return EmbedResult(
|
||||
vectors=[vector or [] for vector in resolved],
|
||||
backend=backend,
|
||||
latency_ms=latency_ms,
|
||||
cache_hits=cache_hits,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user