Skip to content

feat(backends): LocalFileBinding implements verbs (PEFT/aLoRA path) + from_catalog() + OTel spans (Epic #929 Phase 2) #1141

@planetf1

Description

@planetf1

Parent epic: #929
Design proposal: PR #1080 §8.1, §9.2, §9.3
Phase: 2 (Wave 4 — start after 2.1 merges)
Depends on: 0.1, 0.2, 2.1
Blocks: 4.1


Problem

LocalFileBinding is a stub from issue 0.1, raising NotImplementedError on each verb. Reality A (today's IntrinsicAdapter — local PEFT/aLoRA file) needs the four verbs working before the binding is usable.

Agreed design

Four verbs

  • prepare() — resolves configured HF revision (from 0.2 catalogue pinned SHA; defaults to pinned SHA if unspecified). Downloads PEFT weights via existing HF download path. Registers with backend via AdapterMixin.load_peft_adapter.
  • activate(ctx) — switches adapter on (PEFT layer enabled) via backend's existing PEFT activation primitives.
  • deactivate(ctx) — switches off. Auto-called by adapter_scope even on exception.
  • release() — removes PEFT adapter via AdapterMixin.unload_peft_adapter. Second call is a no-op.

Session-scoped: prepare once per session (or explicit release()+prepare() cycle). Call-scoped: activate/deactivate.

from_catalog() classmethod

LocalFileBinding.from_catalog(name: str) -> LocalFileBinding — looks up the catalogue entry by name (post-0.2, with pinned revision), returns a fully configured binding. This is the user-facing standard path:

Adapter(name="answerability", weights=LocalFileBinding.from_catalog("answerability"))

OTel spans

Parent span intrinsic.call (wrapping adapter_scope from 1.A). Child spans:

  • intrinsic.prepare — attributes: intrinsic.name, intrinsic.revision (resolved SHA, not "main"), intrinsic.binding_type="local_file", intrinsic.source, download duration
  • intrinsic.activate — attribute: intrinsic.peft_name
  • intrinsic.deactivate
  • intrinsic.parse (emitted by io_contract.parse()) — attributes: intrinsic.revision, intrinsic.parse_ok, intrinsic.raw_len

Content capture (gated on MELLEA_TRACE_CONTENT env var, consistent with #1035/PR #1036): intrinsic.input.kwargs, intrinsic.output.raw, intrinsic.output.parsed as span events.

Docs updates (ship with this PR)

  • docs/dev/adapter_observability.md (from 2.1) — add LocalFile-specific span attributes
  • docs/docs/advanced/intrinsics.md — add new Adapter(weights=LocalFileBinding.from_catalog(...)) construction pattern; old IntrinsicAdapter(...) shown as deprecated with migration note
  • docs/examples/intrinsics/ — at least one example shows new construction; existing examples gain model_options=

Out of scope

EmbeddedBinding (2.3), ServerMediatedBinding (3.1), long-running session refresh policy (deferred — PR #1080 §17 Q5), full rewrite of docs/dev/intrinsics_and_adapters.md (4.1).

Acceptance criteria

  • All four verbs implemented for LocalFileBinding
  • prepare() uses pinned revision from catalogue; revision="main" opts into tracking-latest
  • activate/deactivate toggle PEFT layer correctly
  • release() cleanly unregisters; second call is a no-op
  • LocalFileBinding.from_catalog("answerability") returns correct binding with pinned revision
  • intrinsic.call parent span emitted; child spans intrinsic.prepare, intrinsic.activate, intrinsic.deactivate, intrinsic.parse emitted with required attributes
  • intrinsic.prepare records resolved HF SHA (not "main") as intrinsic.revision
  • MELLEA_TRACE_CONTENT=1: content events present; absent otherwise
  • IntrinsicMetricsPlugin: invocations counter increments; parse_failures on AdapterSchemaMismatchError; phase_duration_ms records prepare + activate durations
  • docs/docs/advanced/intrinsics.md updated: new construction pattern present, deprecated old pattern noted
  • docs/examples/intrinsics/ updated; all examples pass uv run pytest docs/examples/intrinsics/
  • docs/dev/adapter_observability.md updated with LocalFile-specific attributes
  • E2E adapter call (prepare → activate → generate → deactivate → release) passes
  • ruff format, ruff check, mypy clean

Test plan

Unit tests (test/backends/adapters/test_local_file_binding.py) with mocked HF download + mocked backend:

  • test_prepare_uses_pinned_revision
  • test_prepare_allows_main_override
  • test_release_is_idempotent
  • test_from_catalog_returns_binding_with_correct_revision
  • test_activate_deactivate_call_correct_mixin_verbs
  • Span tests via synthetic OTel exporter: test_call_span_emitted, test_prepare_span_has_revision_attribute, test_content_events_absent_by_default, test_content_events_present_with_gate_set
  • test_metrics_invocation_counter_increments
  • test_metrics_parse_failures_increments — inject synthetic schema-mismatch

Integration tests (test/backends/adapters/test_local_file_integration.py, @pytest.mark.integration, @pytest.mark.hf, @pytest.mark.slow):

Qualitative (optional, @pytest.mark.qualitative): test_check_answerability_quality

Breaking changes

None for end users. Replaces internal stubs; behaviour matches existing IntrinsicAdapter runtime path post-1.A.

References

Metadata

Metadata

Assignees

Labels

area/backendsProvider-specific work: Ollama, HF, LiteLLM, OpenAI, Bedrock, vLLMarea/intrinsicsGranite intrinsic adapters: RAG, Guardian, Corearea/telemetryOTel spans, metrics, tracing, semconvenhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions