diff --git a/CHANGELOG.md b/CHANGELOG.md index 6158d38..c294d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to `openarmature-python` are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The package follows [Semantic Versioning](https://semver.org/); pre-1.0 minor bumps may carry behavioral changes per [spec governance](https://github.com/LunarCommand/openarmature-spec/blob/main/GOVERNANCE.md). +## [Unreleased] + +### Notes + +- **Pinned spec version bumped to v0.17.1.** Proposal 0019 (multi-provider wire-format extension) reframes llm-provider §8 as a catalog of wire-format mappings, with the existing OpenAI-compatible body nested under §8.1. Purely textual on the spec side — no behavioral change, no fixture changes. Code and doc references to §8.X updated to match the new structure (§8.1 → §8.1.1, §8.2 → §8.1.2, §8.3 → §8.1.3, §8.5.1 → §8.1.5.1, §8.1.1 → §8.1.1.1). All existing conformance fixtures continue to pass. + ## [0.8.0] — 2026-05-23 LLM-provider span payload and GenAI semconv release. Pinned spec diff --git a/openarmature-spec b/openarmature-spec index efc0bff..5f8d25f 160000 --- a/openarmature-spec +++ b/openarmature-spec @@ -1 +1 @@ -Subproject commit efc0bff19d9feca3e176db5e367beecef5c6dc69 +Subproject commit 5f8d25fc3d6b97575e5aa4055550c36bcf83deee diff --git a/pyproject.toml b/pyproject.toml index 12647f9..d3935c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ Repository = "https://github.com/LunarCommand/openarmature-python" Specification = "https://github.com/LunarCommand/openarmature-spec" [tool.openarmature] -spec_version = "0.17.0" +spec_version = "0.17.1" [dependency-groups] dev = [ diff --git a/src/openarmature/__init__.py b/src/openarmature/__init__.py index cfd3c42..65beec1 100644 --- a/src/openarmature/__init__.py +++ b/src/openarmature/__init__.py @@ -1,4 +1,4 @@ """OpenArmature: workflow framework for LLM pipelines and tool-calling agents.""" __version__ = "0.8.0" -__spec_version__ = "0.17.0" +__spec_version__ = "0.17.1" diff --git a/src/openarmature/llm/providers/openai.py b/src/openarmature/llm/providers/openai.py index 8a08b27..babcc5b 100644 --- a/src/openarmature/llm/providers/openai.py +++ b/src/openarmature/llm/providers/openai.py @@ -1,5 +1,5 @@ -# Spec: realizes llm-provider §8 (concrete OpenAI provider) including -# the §8.3 wire-error mapping table. +# Spec: realizes llm-provider §8.1 (OpenAI-compatible wire-format mapping) including +# the §8.1.3 wire-error mapping table. """OpenAI-compatible HTTPX-based provider. @@ -389,7 +389,7 @@ async def _do_complete( return self._parse_response(cast("dict[str, Any]", payload_raw), schema_dict, schema_class) # ------------------------------------------------------------------ - # Request building (spec §8.1) + # Request building (spec §8.1.1) # ------------------------------------------------------------------ def _build_request_body( @@ -433,7 +433,7 @@ def _build_request_body( }, } elif not include_response_format: - # On the fallback path the §8.5.1 contract is "response_format + # On the fallback path the §8.1.5.1 contract is "response_format # MUST NOT be on the wire." RuntimeConfig is extra="allow" so # a caller could pass response_format through via the extras # loop above; strip it here so the fallback contract holds @@ -442,7 +442,7 @@ def _build_request_body( return body # ------------------------------------------------------------------ - # Response parsing (spec §8.2) + # Response parsing (spec §8.1.2) # ------------------------------------------------------------------ def _parse_response( @@ -460,7 +460,7 @@ def _parse_response( raise ProviderInvalidResponse(f"response missing required fields: {exc}") from exc finish_reason: str = finish_reason_raw if isinstance(finish_reason_raw, str) else "error" - # Per §8.2 (and conformance fixture 005's + # Per §8.1.2 (and conformance fixture 005's # `function_call_legacy_finish_reason_mapping` case): the # legacy `finish_reason: "function_call"` value MUST be # normalized to the spec's `"tool_calls"`. This is a @@ -717,13 +717,13 @@ def _augment_messages_with_schema_directive( def _message_to_wire(msg: Message) -> dict[str, Any]: - """Spec §8.1 request mapping for one message.""" + """Spec §8.1.1 request mapping for one message.""" if isinstance(msg, SystemMessage): return {"role": "system", "content": msg.content} if isinstance(msg, UserMessage): - # Dual-shape user content (§8.1): string maps directly; a + # Dual-shape user content (§8.1.1): string maps directly; a # content-block sequence maps to OpenAI's content-array form - # per §8.1.1. + # per §8.1.1.1. if isinstance(msg.content, str): return {"role": "user", "content": msg.content} return { @@ -760,7 +760,7 @@ def _message_to_wire(msg: Message) -> dict[str, Any]: } -# Spec §8.1.1: content-block to OpenAI content-array entry mapping. +# Spec §8.1.1.1: content-block to OpenAI content-array entry mapping. # Both URL-referenced and inline-base64 image blocks go through # OpenAI's `image_url` entry shape; the inline case is expressed as # an RFC 2397 data: URI carrying media_type + base64_data. The @@ -872,7 +872,7 @@ def classify_http_error(resp: httpx.Response) -> LlmProviderError: if status in (401, 403): return ProviderAuthentication(message or f"HTTP {status}") if status == 400: - # Spec §8.3: HTTP 400 bodies that indicate the bound model + # Spec §8.1.3: HTTP 400 bodies that indicate the bound model # rejected a content block map to provider_unsupported_content_block # rather than the generic provider_invalid_request. The # detection rule is implementation-defined. diff --git a/tests/conformance/harness/directives.py b/tests/conformance/harness/directives.py index 09dfd78..dd20d2c 100644 --- a/tests/conformance/harness/directives.py +++ b/tests/conformance/harness/directives.py @@ -484,7 +484,7 @@ class MockResponse(_AllowExtras): Permissive shape because the body's content mirrors OpenAI's wire format which is wide and evolving; modelling every field would duplicate the OpenAI schema. The ``llm-provider`` capability's - spec.md §8 is the authoritative shape. + spec.md §8.1 is the authoritative shape. """ status: int | None = None diff --git a/tests/conformance/test_llm_provider.py b/tests/conformance/test_llm_provider.py index 79f5bef..6240703 100644 --- a/tests/conformance/test_llm_provider.py +++ b/tests/conformance/test_llm_provider.py @@ -4,7 +4,7 @@ behavior in terms of OpenAI Chat Completions wire-format mock responses + expected ``Provider.complete()`` / ``Provider.ready()`` outcomes. The harness drives the real :class:`OpenAIProvider` via -``httpx.MockTransport`` so the wire-mapping path (spec §8) is +``httpx.MockTransport`` so the wire-mapping path (spec §8.1) is exercised end-to-end — fixture 005 explicitly tests that mapping, so mocking at the Provider boundary would skip what we want to verify. diff --git a/tests/test_smoke.py b/tests/test_smoke.py index c590392..49b37ba 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -9,7 +9,7 @@ def test_package_versions() -> None: assert openarmature.__version__ == "0.8.0" - assert openarmature.__spec_version__ == "0.17.0" + assert openarmature.__spec_version__ == "0.17.1" def test_spec_version_matches_pyproject() -> None: diff --git a/tests/unit/test_llm_provider.py b/tests/unit/test_llm_provider.py index ad0b096..efb238c 100644 --- a/tests/unit/test_llm_provider.py +++ b/tests/unit/test_llm_provider.py @@ -273,7 +273,7 @@ def test_transient_categories_excludes_terminal_categories() -> None: # --------------------------------------------------------------------------- -# classify_http_error — wire-mapping table (spec §8.3) +# classify_http_error — wire-mapping table (spec §8.1.3) # ---------------------------------------------------------------------------