Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions agents/completed.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@

---

# #378: discovery drops variants= from the manifest; model= shorthand breaks with ServerHandle setup param

**Completed:** yes
**Status:** DONE (2026-07-04, Claude) — two @endpoint API bugs found by the inference-endpoints #343 port. (1) `discover_functions` deduped manifest entries by (declared_module, class_name, python_name) — variants= rows share all three with their base function, so every variant was silently dropped from endpoint.lock; the stamped `name` is now part of the dedup key. (2) The `model=` shorthand counted a `ServerHandle`-annotated setup() param as a slot candidate, so the documented runtime= shape `setup(self, model: str, server: ServerHandle)` failed at decoration; ServerHandle params are now excluded from slot resolution. Regression tests for both; suite 223 passed, 1 skipped.

## Tasks
- [x] Include entry name in the discovery dedup key (discovery/discover.py).
- [x] Skip ServerHandle-annotated setup params in `_resolve_single_slot` (api/decorators.py).
- [x] Regression tests (manifest keeps variant entries; documented runtime= shape decorates).

---

# #377: HF binding files= not honored on every download path (14GB instead of ~3GB)

**Completed:** yes
Expand Down
2 changes: 1 addition & 1 deletion agents/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
> Source for all findings: /home/fidika/cozy/python-gen-worker/AUDIT.md (2026-07-03 full-stack audit,
> file:line evidence for every claim). Counterpart protocol/orchestrator issues: tensorhub #503-#510.

next_id: 378
next_id: 379

---

Expand Down
14 changes: 14 additions & 0 deletions src/gen_worker/api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,19 @@ def _setup_params(cls: type) -> Optional[dict[str, inspect.Parameter]]:
return {p.name: p for p in params}


def _is_server_handle_param(p: inspect.Parameter) -> bool:
"""True when the param is annotated ``ServerHandle`` (a runtime= injection
target, not a model slot)."""
ann = p.annotation
if ann is inspect.Parameter.empty:
return False
from ..runtimes.server import ServerHandle

if ann is ServerHandle:
return True
return isinstance(ann, str) and ann.split(".")[-1] == "ServerHandle"


def _resolve_single_slot(
cls: type,
models: dict[str, Binding],
Expand All @@ -269,6 +282,7 @@ def _resolve_single_slot(
named = [
n for n, p in setup_kwargs.items()
if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
and not _is_server_handle_param(p)
]
if len(named) == 1:
models[named[0]] = binding
Expand Down
5 changes: 4 additions & 1 deletion src/gen_worker/discovery/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,16 @@ def discover_functions(root: Optional[Path] = None, *, main_module: str | None =
) from e

functions: List[Dict[str, Any]] = []
seen: Set[Tuple[str, str, str]] = set()
seen: Set[Tuple[str, str, str, str]] = set()
for f in found:
for entry in _extract_entries(f.obj, f.walked_module):
# name is part of the key: variants= share (module, class,
# python_name) with their base function but stamp distinct names.
key = (
entry.get("declared_module", entry.get("module", "")),
entry.get("class_name", ""),
entry.get("python_name", ""),
entry.get("name", ""),
)
if key in seen:
continue
Expand Down
53 changes: 53 additions & 0 deletions tests/test_discovery_and_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,56 @@ def test_from_scratch_example_uses_publish_contract() -> None:
assert s.output_mode == "single"
assert not s.is_async_gen
assert s.output_type is mod.FromScratchResult


def test_manifest_keeps_variant_entries(tmp_pkg: Path) -> None:
"""variants= rows share (module, class, python_name) with their base
function; the manifest dedup must key on the stamped name too."""
from gen_worker.discovery.discover import discover_functions

pkg = tmp_pkg / "ep_variants"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "main.py").write_text(textwrap.dedent("""
import msgspec
from gen_worker import HF, RequestContext, Resources, endpoint

class In_(msgspec.Struct):
x: str = ""

class Out_(msgspec.Struct):
y: str

@endpoint(
model=HF("o/base", dtype="bf16"),
resources=Resources(vram_gb=24),
variants={"generate_fp8": (HF("o/base-fp8"), Resources(vram_gb=14))},
)
class Gen:
def setup(self, model: str) -> None:
self.model = model

def generate(self, ctx: RequestContext, data: In_) -> Out_:
return Out_(y="ok")
"""))

fns = discover_functions(tmp_pkg, main_module="ep_variants.main")
assert sorted(f["name"] for f in fns) == ["generate", "generate-fp8"]


def test_model_shorthand_skips_server_handle_setup_param() -> None:
"""runtime= endpoints inject a ServerHandle into setup(); the model=
shorthand must resolve the slot from the remaining parameter."""
from gen_worker.runtimes.server import ServerHandle

@endpoint(model=HF("o/llm"), resources=Resources(vram_gb=40), runtime="vllm")
class Chat:
def setup(self, model: str, server: ServerHandle) -> None:
self.base_url = server.base_url

def complete(self, ctx: RequestContext, data: _In) -> _Out:
return _Out(result="")

(s,) = extract_specs(Chat)
assert list(s.models) == ["model"]
assert s.models["model"].ref == "o/llm"
Loading