5.0 KiB
5.0 KiB
1. Package scaffolding
- 1.1 Create the
semantic/package (__init__.py,models.py,llm_client.py,enricher.py,prompts.py) - 1.2 Add the
anthropicSDK dependency and asemantic*entry to[tool.setuptools.packages.find].includeinpyproject.toml - 1.3 Add enrichment configuration: enabled/disabled flag (default disabled), model name, and timeout, sourced from environment/config in a single place
semantic/reads from - 1.4 Extend the project's smoke test (that imports every package) to import
semantic
2. SemanticScene data model (capability: semantic-scene)
- 2.1 Implement
semantic/models.py:SemanticWidget(element_id: str,purpose: str) andSemanticScene(page: str,intents: list[str],widgets: list[SemanticWidget]) dataclasses withto_dict()/from_dict()mirroring the style ofcore/models.py - 2.2 Define the JSON schema for
SemanticSceneused to constrain the LLM's structured output, matching theSemanticScenedataclass shape exactly - 2.3 Write unit tests for
SemanticScene/SemanticWidgetround-tripping throughto_dict()/from_dict()
3. LLM client abstraction (capability: semantic-scene)
- 3.1 Implement
semantic/llm_client.py: a narrow client wrapper around the Anthropic SDK exposing a singleenrich(scene_json: dict, *, timeout: float) -> dictmethod that issues one structured-output (output_config.format/ schema-constrained) call and returns the parsed JSON body - 3.2 Configure the enrichment system prompt/schema block with a
cache_control: {"type": "ephemeral"}breakpoint so repeated per-step calls reuse the cached prefix - 3.3 Map the SDK's typed exceptions (timeout, connection error, rate limit, authentication error, API status error) into a single internal
EnrichmentUnavailablesignal consumed only insidesemantic/(never re-raised pastenricher.py) - 3.4 Make the client injectable/mockable (constructor parameter or factory function) so tests can supply a fake client with canned responses instead of calling the network
- 3.5 Write unit tests for the client wrapper against a fake transport covering: success, timeout, rate limit, malformed/schema-invalid JSON response
4. Enrichment pass and degrade path (capability: semantic-scene)
- 4.1 Implement
semantic/prompts.py: the enrichment system prompt describing the page-identity/intents/widget-purpose task and referencing the inputScene's elements by ID - 4.2 Implement
semantic/enricher.py:enrich_scene(scene: Scene, *, client=None) -> SemanticScene | None, serializing theSceneto JSON, calling the LLM client, and parsing the result into aSemanticScene - 4.3 Add post-response validation in
enricher.py: drop anywidgets[]entry whoseelement_iddoes not match an element ID present in the inputScene - 4.4 Make
enrich_scene()returnNone(never raise) for every expected failure mode: disabled config,EnrichmentUnavailablefrom the client, or aSemanticScenethat fails post-response validation entirely - 4.5 Write unit tests for
enrich_scene()covering: successful enrichment, disabled-by-config short-circuit (no client call made), degrade-to-Noneon client failure, and dangling-element_idfiltering - 4.6 Write an integration test (skippable without network/API credentials, following the existing
apex-agent-mvpskippable-integration-test pattern) that calls the real LLM client against a fixtureSceneand asserts a schema-validSemanticSceneis returned
5. Tool integration (capability: semantic-scene)
- 5.1 Implement
tools/describe_screen_semantic.py: calls the existingtools/describe_screen.pyfor aScene, thenenrich_scene(), returning{"scene": Scene, "semantic_scene": SemanticScene | None}, following the samedevice_id/manager/ocr_enginekwarg pattern asdescribe_screen - 5.2 Register the new tool under its own key (e.g.
"describe_screen_semantic") inruntime/executor.py'sdefault_tool_registry(), additive alongside the existing"describe_screen"entry - 5.3 Write a unit test asserting
tools/describe_screen.py's existing behavior and return type are unchanged after this change (no accidental coupling tosemantic/) - 5.4 Write a unit test for
describe_screen_semanticcovering both the enrichment-succeeds and enrichment-degrades-to-Nonecases, using a fake/mock LLM client
6. End-to-end validation
- 6.1 Write an end-to-end test simulating an Observe step that calls
describe_screen_semanticagainst a mockedDriver/Sceneand a mocked LLM client, asserting the task loop completes normally whether enrichment succeeds or is forced to fail - 6.2 Confirm enrichment stays disabled by default after applying this change (no existing task's behavior, latency, or cost changes unless a caller explicitly enables it and opts into the new tool)
- 6.3 Run the full test suite (
pytest) and confirm no existing test intests/needed a behavior change, only additive new tests