From e0ae37f4176cdb48b9b6dcaca55ac751571cd9be Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Wed, 28 Oct 2026 07:55:29 -0700 Subject: [PATCH 1/5] fix(AIC-3568): give the launchdarkly-ai-python barrel the same __all__ as launchdarkly-ai-server The barrel re-exported core with import * but had no __all__ of its own, so its public list was not declared. It now imports core's __all__, and a test asserts the two are equal. Co-Authored-By: Claude Opus 5.5 --- packages/ai/src/launchdarkly_ai_python/__init__.py | 1 + packages/ai/tests/test_reexports.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/packages/ai/src/launchdarkly_ai_python/__init__.py b/packages/ai/src/launchdarkly_ai_python/__init__.py index 2b52edf8..dadbc1ec 100644 --- a/packages/ai/src/launchdarkly_ai_python/__init__.py +++ b/packages/ai/src/launchdarkly_ai_python/__init__.py @@ -6,6 +6,7 @@ __version__ = "0.4.0" # x-release-please-version from launchdarkly_ai_server import * # noqa: F403 +from launchdarkly_ai_server import __all__ as __all__ from launchdarkly_ai_server import register_ai_sdk_package register_ai_sdk_package("launchdarkly-ai-python", __version__) diff --git a/packages/ai/tests/test_reexports.py b/packages/ai/tests/test_reexports.py index 18a7fbe6..054e062c 100644 --- a/packages/ai/tests/test_reexports.py +++ b/packages/ai/tests/test_reexports.py @@ -16,6 +16,12 @@ def test_all_named_exports_from_server_are_reexported(self) -> None: f"launchdarkly_ai_python is missing re-export: {name!r}" ) + def test_all_matches_server(self) -> None: + server = importlib.import_module("launchdarkly_ai_server") + barrel = importlib.import_module("launchdarkly_ai_python") + + assert barrel.__all__ == server.__all__ + def test_reexported_values_are_identical_references(self) -> None: server = importlib.import_module("launchdarkly_ai_server") barrel = importlib.import_module("launchdarkly_ai_python") From 0ca1b06a5fb5dc6daa489f0f5d1bc35d94ad02bb Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Wed, 28 Oct 2026 08:05:42 -0700 Subject: [PATCH 2/5] docs(AIC-3568): stop citing the private spec in AGENTS.md The SDK repository is public, so it must not point at TESTING.md in a private repository. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b4eb3bb3..f898828a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,8 +92,6 @@ PRs are squash-merged with the PR title as the commit message, and release-pleas - **A breaking experimental change** uses `!` while the package is 0.x: `feat(evaluations)!:` bumps the minor version, which is what an experimental break is allowed. After 1.0, `!` would force a major release. Use `feat(evaluations):` without `!` instead, and say the change is breaking in the title. - **When a feature is promoted to core,** remove its rows from `changelog-sections`. -The rule is specified in `TESTING.md` §0.3 in `launchdarkly/ai-sdks-monorepo`. - ## Package Hierarchy The monorepo is organized into three tiers plus a convenience barrel. Dependencies only flow **downward** — never sideways between packages in the same tier, and never upward. From 5ddfa4d11dff369a0ddeb6a676b5eaeaebaa5a3b Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Wed, 28 Oct 2026 08:06:56 -0700 Subject: [PATCH 3/5] docs(AIC-3568): stop citing the private spec in client comments and tests Co-Authored-By: Claude Opus 5.5 --- packages/client/src/launchdarkly_ai_server/skills_core.py | 2 +- packages/client/tests/test_lifecycle.py | 2 +- packages/client/tests/test_public_surface.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/client/src/launchdarkly_ai_server/skills_core.py b/packages/client/src/launchdarkly_ai_server/skills_core.py index ff1ee127..744acbb3 100644 --- a/packages/client/src/launchdarkly_ai_server/skills_core.py +++ b/packages/client/src/launchdarkly_ai_server/skills_core.py @@ -192,7 +192,7 @@ def clear_state() -> None: # Registered on load, so core's ``shutdown()`` clears this state without -# importing any Agent Skills module (TESTING.md §0.3). +# importing any Agent Skills module. register_shutdown_hook("Agent Skills", clear_state) diff --git a/packages/client/tests/test_lifecycle.py b/packages/client/tests/test_lifecycle.py index dd4da8ac..abf65852 100644 --- a/packages/client/tests/test_lifecycle.py +++ b/packages/client/tests/test_lifecycle.py @@ -659,7 +659,7 @@ async def test_idempotent_double_shutdown(self) -> None: async def test_a_failure_clearing_experimental_state_is_logged_not_raised( self, caplog: pytest.LogCaptureFixture ) -> None: - """Experimental behaviour must not break a core call (TESTING.md §0.3).""" + """Experimental behaviour must not break a core call.""" stub = _make_stub_client() with patch.object(lifecycle_module, "_setup_telemetry", return_value=None): await init_client(client=stub) diff --git a/packages/client/tests/test_public_surface.py b/packages/client/tests/test_public_surface.py index bf106ef8..e8495c85 100644 --- a/packages/client/tests/test_public_surface.py +++ b/packages/client/tests/test_public_surface.py @@ -1,8 +1,8 @@ """ The package root's public surface. -``ROOT_SURFACE`` is the checked-in snapshot of ``launchdarkly_ai_server.__all__`` -(TESTING.md §0.3). It is spelled out so that any change to the root, including +``ROOT_SURFACE`` is the checked-in snapshot of ``launchdarkly_ai_server.__all__``. +It is spelled out so that any change to the root, including an experimental name leaking onto it, fails here and is seen in review. Update it deliberately, in the same change that adds or removes a root export. """ From 0f17d6de2848c9c10f26578c68d9feb221fe355b Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Wed, 28 Oct 2026 08:15:09 -0700 Subject: [PATCH 4/5] docs(AIC-3568): drop the remaining private-spec references This repository is public, so comments, docstrings, docs and test messages no longer point at TESTING.md, its section numbers or the private spec repository. Where a comment relied on the reference, it now states the rule in plain words. No runtime change. Co-Authored-By: Claude Opus 5.5 --- packages/ai/tests/test_reexports.py | 3 +- packages/claude-agents/agents.md | 4 +-- .../launchdarkly_ai_claude_agents/handler.py | 2 +- packages/claude-agents/tests/test_builtins.py | 3 +- packages/claude-agents/tests/test_graph.py | 3 +- packages/claude-agents/tests/test_handler.py | 10 +++--- .../claude-agents/tests/test_native_graph.py | 5 ++- .../claude-agents/tests/test_sdk_usage.py | 2 +- packages/claude-agents/tests/test_skills.py | 2 +- .../handler.py | 2 +- .../claude-messages/tests/test_handler.py | 31 ++++++++--------- .../claude-messages/tests/test_sdk_usage.py | 2 +- .../src/launchdarkly_ai_server/graph.py | 4 +-- .../src/launchdarkly_ai_server/history.py | 4 +-- .../src/launchdarkly_ai_server/lifecycle.py | 2 +- .../parameter_forwarding.py | 2 +- packages/client/tests/test_client.py | 15 ++++---- .../client/tests/test_evaluations_spans.py | 5 +-- packages/client/tests/test_graph.py | 11 +++--- packages/client/tests/test_graph_stream.py | 9 +++-- packages/client/tests/test_judges.py | 5 ++- .../client/tests/test_ld_span_attributes.py | 2 +- packages/client/tests/test_lifecycle.py | 7 ++-- packages/client/tests/test_registry.py | 11 +++--- packages/client/tests/test_sdk_info.py | 2 +- packages/client/tests/test_sdk_usage.py | 4 +-- packages/client/tests/test_skills_fdv2.py | 2 +- packages/client/tests/test_tracking.py | 3 +- packages/client/tests/test_utils.py | 5 ++- packages/langchain-agents/agents.md | 2 +- .../handler.py | 8 ++--- .../messages.py | 2 +- packages/langchain-agents/tests/test_graph.py | 3 +- .../langchain-agents/tests/test_handler.py | 31 ++++++++--------- .../tests/test_native_graph.py | 9 +++-- .../langchain-agents/tests/test_sdk_usage.py | 2 +- .../handler.py | 8 ++--- .../langchain-messages/tests/test_handler.py | 18 +++++----- .../tests/test_sdk_usage.py | 2 +- .../litellm-agents/tests/test_sdk_usage.py | 2 +- .../handler.py | 2 +- .../litellm-messages/tests/test_sdk_usage.py | 2 +- .../launchdarkly_ai_openai_agents/handler.py | 2 +- .../native_graph.py | 2 +- packages/openai-agents/tests/test_graph.py | 3 +- packages/openai-agents/tests/test_handler.py | 20 +++++------ .../openai-agents/tests/test_native_graph.py | 9 +++-- .../openai-agents/tests/test_sdk_usage.py | 2 +- packages/openai-agents/tests/test_utils.py | 5 ++- .../handler.py | 2 +- .../openai-messages/tests/test_handler.py | 34 +++++++++---------- .../openai-messages/tests/test_sdk_usage.py | 2 +- packages/opencode-agents/agents.md | 2 +- .../opencode-agents/tests/test_sdk_usage.py | 2 +- packages/typesafe/tests/test_sdk_usage.py | 2 +- packages/vercel-agents/agents.md | 2 -- .../launchdarkly_ai_vercel_agents/handler.py | 2 +- .../vercel-agents/tests/test_sdk_usage.py | 2 +- packages/vercel-messages/agents.md | 2 -- .../handler.py | 2 +- .../vercel-messages/tests/test_sdk_usage.py | 2 +- tests/forwarding_spec.py | 2 +- tests/never_forwarded.py | 2 +- 63 files changed, 165 insertions(+), 191 deletions(-) diff --git a/packages/ai/tests/test_reexports.py b/packages/ai/tests/test_reexports.py index 054e062c..28648fc0 100644 --- a/packages/ai/tests/test_reexports.py +++ b/packages/ai/tests/test_reexports.py @@ -1,6 +1,5 @@ """ -Tests for §5 launchdarkly-ai-python re-export barrel. -Reference: TESTING.md §5 +Tests for the launchdarkly-ai-python re-export barrel. """ import importlib diff --git a/packages/claude-agents/agents.md b/packages/claude-agents/agents.md index 82caf6f6..3abef561 100644 --- a/packages/claude-agents/agents.md +++ b/packages/claude-agents/agents.md @@ -141,7 +141,7 @@ The CLI also writes its own `AssistantMessage`, with `model` set to the literal an API call fails and after a response stops at `max_output_tokens`. It is not a model response: it gets no `chat` span and never joins the conversation a later span reports. -**Important:** a bare `return` inside `async for` abandons the generator. Python's asyncio finalizer will try to `aclose()` it later and may raise `RuntimeError: aclose(): asynchronous generator is already running` if the generator is still suspended inside a real SDK `await`. Always use `break` + explicit `aclose()`. See **Appendix A.4** in `TESTING.md`. +**Important:** a bare `return` inside `async for` abandons the generator. Python's asyncio finalizer will try to `aclose()` it later and may raise `RuntimeError: aclose(): asynchronous generator is already running` if the generator is still suspended inside a real SDK `await`. Always use `break` + explicit `aclose()`. ### 4. Telemetry @@ -236,6 +236,6 @@ Span names and attributes are described in [Implementation Details → Telemetry ## Common Pitfalls - **MCP tool name prefix**: tools registered in the MCP server are accessible as `mcp__tool-mcp__`. The `allowed_tools` list must use this prefix; omitting it will cause the agent to not invoke any tools. -- **Async generator teardown**: always use `break` inside `async for` (not `return`) and wrap the loop in `try/finally: await gen.aclose()`. See TESTING.md Appendix A.4. +- **Async generator teardown**: always use `break` inside `async for` (not `return`) and wrap the loop in `try/finally: await gen.aclose()`. - **`message.usage` shape**: the raw usage dict from the `claude_agent_sdk` is passed through directly to `parse_usage`. It accepts `input_tokens`/`output_tokens` which the SDK provides. - **Native tools** (`ClaudeWebSearch`, etc.) are registered in `tool_handlers` as `NativeTool` sentinel instances (not callables). The handler's `_partition_tools` function separates them from user-defined tools — native tools go into `native_tool_names` for the agent's built-in access, user tools go through the MCP server. diff --git a/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py b/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py index 664b1dbf..c3b63803 100644 --- a/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py +++ b/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py @@ -89,7 +89,7 @@ #: environment, working directory, file access, permissions, settings files, plugins, sandbox, #: session state) stays under the application's control, never a config's. #: -#: This is the cross-SDK list for Claude Agents (TESTING.md section 1.12), shared with the native +#: This is the cross-SDK list for Claude Agents, shared with the native #: graph through :func:`_options_parameters`. #: #: The SDK offers no ``temperature``/``top_p``/``top_k``/``max_tokens``/``stop_sequences``/ diff --git a/packages/claude-agents/tests/test_builtins.py b/packages/claude-agents/tests/test_builtins.py index 6ec8599a..1808cd5a 100644 --- a/packages/claude-agents/tests/test_builtins.py +++ b/packages/claude-agents/tests/test_builtins.py @@ -1,6 +1,5 @@ """ -Tests for §4 Claude Agents built-ins (builtins.py). -Reference: TESTING.md §4 +Tests for the Claude Agents built-ins (builtins.py). """ from launchdarkly_ai_claude_agents.builtins import ( diff --git a/packages/claude-agents/tests/test_graph.py b/packages/claude-agents/tests/test_graph.py index c59aae24..d80f6142 100644 --- a/packages/claude-agents/tests/test_graph.py +++ b/packages/claude-agents/tests/test_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.1 graph convenience wrapper (claude_graph). -Reference: TESTING.md §2.1 +Tests for the graph convenience wrapper (claude_graph). """ from collections.abc import AsyncIterator diff --git a/packages/claude-agents/tests/test_handler.py b/packages/claude-agents/tests/test_handler.py index ea8838ca..538c0fe8 100644 --- a/packages/claude-agents/tests/test_handler.py +++ b/packages/claude-agents/tests/test_handler.py @@ -416,7 +416,7 @@ def test_native_tool_aliases_map_ld_key_to_provider_name(self) -> None: # --------------------------------------------------------------------------- -# §1.3 Tool conversion — restored from the pre-rewrite file (not telemetry). +# Tool conversion — restored from the pre-rewrite file (not telemetry). # ``_partition_tools`` kept its 3-tuple return, so these run byte-for-byte. # --------------------------------------------------------------------------- @@ -444,7 +444,7 @@ def test_empty_tools_no_tools_sent(self) -> None: # --------------------------------------------------------------------------- -# §1.4 Tool execution loop (via _build_tool_mcp) — restored from the pre-rewrite +# Tool execution loop (via _build_tool_mcp) — restored from the pre-rewrite # file. ``_build_tool_mcp`` kept its lazy ``importlib.import_module`` pattern # (native_graph.py depends on it), so the old SDK-mocking approach still works # unmodified for this one. @@ -2006,7 +2006,7 @@ async def test_forwards_cwd_and_setting_sources_to_query( # --------------------------------------------------------------------------- -# §1.2 Path C — None user_input must not produce None prompt. +# None user_input must not produce None prompt. # Restored from the pre-rewrite file (not telemetry). Adapted from the old # ``_patch_query``/mocked-module approach to ``monkeypatch.setattr(handler_mod, "query", ...)``, # since ``query`` is now resolved once at import time rather than through @@ -2015,7 +2015,7 @@ async def test_forwards_cwd_and_setting_sources_to_query( class TestNoneUserInput: - """TESTING.md §1.2 Path C: When user_input is None, the prompt passed to + """When user_input is None, the prompt passed to the provider must be '' (empty string), not None.""" async def test_none_user_input_instructions_path_prompt_is_empty_string( @@ -2228,7 +2228,7 @@ async def _query(**kwargs: Any) -> AsyncIterator[Any]: @pytest.mark.parametrize("path", _PATHS) class TestHostOptionsAndBuiltinToolSet: - """TESTING.md §2.x Claude Agents Handler — host options and built-in tool set. + """Claude Agents Handler — host options and built-in tool set. ``cwd`` and ``setting_sources`` configure the CLI process, so only the application sets them. ``setting_sources`` not given sends ``[]``: the Agent SDK's own ``None`` loads every settings diff --git a/packages/claude-agents/tests/test_native_graph.py b/packages/claude-agents/tests/test_native_graph.py index e3dc332e..870fe7c5 100644 --- a/packages/claude-agents/tests/test_native_graph.py +++ b/packages/claude-agents/tests/test_native_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.2 native graph adapter (to_claude_agents) plus Anthropic-specific specs. -Reference: TESTING.md §2.2, §2.x (Anthropic) +Tests for the native graph adapter (to_claude_agents) plus Anthropic-specific specs. """ from __future__ import annotations @@ -147,7 +146,7 @@ async def _query(**kwargs: Any) -> AsyncIterator[Any]: # --------------------------------------------------------------------------- -# §2.2 Generic topology +# Generic topology # --------------------------------------------------------------------------- diff --git a/packages/claude-agents/tests/test_sdk_usage.py b/packages/claude-agents/tests/test_sdk_usage.py index 4b733adb..6e068850 100644 --- a/packages/claude-agents/tests/test_sdk_usage.py +++ b/packages/claude-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — claude-agents.""" +"""Tests for SDK helper usage events — claude-agents.""" import hashlib from pathlib import Path diff --git a/packages/claude-agents/tests/test_skills.py b/packages/claude-agents/tests/test_skills.py index 18d3ecc1..5fdb836d 100644 --- a/packages/claude-agents/tests/test_skills.py +++ b/packages/claude-agents/tests/test_skills.py @@ -1,5 +1,5 @@ """ -Tests for TESTING.md §2.x Claude Agents Skills Integration (A.18). +Tests for the Claude Agents Skills integration. ``query()`` is replaced with a fake async generator that records the options it was given and can look at the filesystem while the run is in progress. The skill store is a real diff --git a/packages/claude-messages/src/launchdarkly_ai_claude_messages/handler.py b/packages/claude-messages/src/launchdarkly_ai_claude_messages/handler.py index ebd17db4..672793ed 100644 --- a/packages/claude-messages/src/launchdarkly_ai_claude_messages/handler.py +++ b/packages/claude-messages/src/launchdarkly_ai_claude_messages/handler.py @@ -55,7 +55,7 @@ #: ``TestMessagesStreamAcceptsExactlyTheseKeys`` in this package's tests assert this #: classification stays exhaustive as the SDK's own signatures change. #: -#: This is the cross-SDK list for Claude Messages (TESTING.md section 1.12). A top-level +#: This is the cross-SDK list for Claude Messages. A top-level #: ``effort`` is also accepted and moved into ``output_config.effort`` before this list applies #: (see :func:`_rename_effort_to_output_config`), ``output_config`` keeps only its ``effort`` (see #: :func:`_rebuild_output_config`), and ``max_tokens`` defaults to 1024. The same list diff --git a/packages/claude-messages/tests/test_handler.py b/packages/claude-messages/tests/test_handler.py index 3732a860..a56bfe4d 100644 --- a/packages/claude-messages/tests/test_handler.py +++ b/packages/claude-messages/tests/test_handler.py @@ -1,7 +1,6 @@ """ Tests for launchdarkly-ai-claude-messages handler. -Covers §1.1–1.9 (generic handler tests). -Reference: TESTING.md §1 +Covers the generic handler tests. """ from __future__ import annotations @@ -104,7 +103,7 @@ def mock_anthropic(mocker): # --------------------------------------------------------------------------- -# §1.1 Factory function and metadata +# Factory function and metadata # --------------------------------------------------------------------------- @@ -138,7 +137,7 @@ def test_multiple_calls_return_independent_instances( # --------------------------------------------------------------------------- -# §1.2 Prompt construction +# Prompt construction # --------------------------------------------------------------------------- @@ -294,7 +293,7 @@ async def test_path_c_both_instructions_and_messages_messages_wins( # --------------------------------------------------------------------------- -# §1.3 Tool conversion +# Tool conversion # --------------------------------------------------------------------------- @@ -373,7 +372,7 @@ async def test_custom_parameters_passthrough( # --------------------------------------------------------------------------- -# §1.4 Tool execution loop +# Tool execution loop # --------------------------------------------------------------------------- @@ -740,7 +739,7 @@ async def test_multiple_consecutive_tool_calls( # --------------------------------------------------------------------------- -# §1.5 Telemetry +# Telemetry # --------------------------------------------------------------------------- # Span recording # --------------------------------------------------------------------------- @@ -1292,7 +1291,7 @@ async def test_still_writes_the_legacy_content_events_when_enabled( # --------------------------------------------------------------------------- -# §1.6 Error handling +# Error handling # --------------------------------------------------------------------------- @@ -1406,7 +1405,7 @@ async def test_rethrows_error(self, mock_anthropic: MagicMock) -> None: # --------------------------------------------------------------------------- -# §1.9 Structured output (outputFormat) +# Structured output (outputFormat) # --------------------------------------------------------------------------- @@ -1460,7 +1459,7 @@ async def test_output_format_with_messages_system_appended( # --------------------------------------------------------------------------- -# §1.7 Convenience export +# Convenience export # --------------------------------------------------------------------------- @@ -1508,7 +1507,7 @@ def test_callable_without_extra_kwargs(self) -> None: # --------------------------------------------------------------------------- -# §1.8 Streaming +# Streaming # --------------------------------------------------------------------------- @@ -1828,12 +1827,12 @@ async def _bad_ctx() -> AsyncGenerator[Any, None]: # --------------------------------------------------------------------------- -# §1.2 Path C — None user_input must not produce None content +# None user_input must not produce None content # --------------------------------------------------------------------------- class TestNoneUserInput: - """TESTING.md §1.2 Path C: When user_input is None, the user-role message + """When user_input is None, the user-role message content sent to the provider must be '' not None.""" async def test_none_user_input_instructions_path_no_none_content( @@ -1864,12 +1863,12 @@ async def _capture(**kwargs: Any) -> Any: # --------------------------------------------------------------------------- -# §1.10 MAX_STEPS cap +# MAX_STEPS cap # --------------------------------------------------------------------------- class TestMaxStepsCap: - """TESTING.md §1.10: The tool loop must break with an error after MAX_STEPS (5) iterations.""" + """The tool loop must break with an error after MAX_STEPS (5) iterations.""" def _tool_use_response(self, id: str = "tu1") -> MagicMock: return _anthropic_response( @@ -1957,7 +1956,7 @@ async def _iter() -> AsyncGenerator: # --------------------------------------------------------------------------- -# §1.5 Streaming telemetry (Appendix A.5 — do not patch _HAS_OTEL=False) +# Streaming telemetry (do not patch _HAS_OTEL=False) # --------------------------------------------------------------------------- diff --git a/packages/claude-messages/tests/test_sdk_usage.py b/packages/claude-messages/tests/test_sdk_usage.py index 7ecc294b..dfc041d9 100644 --- a/packages/claude-messages/tests/test_sdk_usage.py +++ b/packages/claude-messages/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — claude-messages.""" +"""Tests for SDK helper usage events — claude-messages.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/client/src/launchdarkly_ai_server/graph.py b/packages/client/src/launchdarkly_ai_server/graph.py index 4f8677b6..c84d7e80 100644 --- a/packages/client/src/launchdarkly_ai_server/graph.py +++ b/packages/client/src/launchdarkly_ai_server/graph.py @@ -337,8 +337,8 @@ def _make_handoff_fn(t: str) -> Callable[..., str]: def _fn(*a: Any, **kw: Any) -> str: if not chosen: chosen.append(t) - # Both routing strings are pinned byte for byte in TESTING.md §3.15a, which - # gives the reasons: "recorded" keeps a node with a real tool from skipping + # Both routing strings are pinned byte for byte across SDKs, for two + # reasons: "recorded" keeps a node with a real tool from skipping # it, and asking for no "final response" keeps a router from answering for # its target. return ( diff --git a/packages/client/src/launchdarkly_ai_server/history.py b/packages/client/src/launchdarkly_ai_server/history.py index f5f46948..228fb4c2 100644 --- a/packages/client/src/launchdarkly_ai_server/history.py +++ b/packages/client/src/launchdarkly_ai_server/history.py @@ -1,8 +1,8 @@ """Shared conversation-history composition and multimodal content helpers. Mirrors the TypeScript ``history`` module so every handler composes runtime -``history`` the same way (TESTING.md §1.11) and maps LaunchDarkly-canonical -content blocks to each provider's native shape (Appendix A.7). +``history`` the same way and maps LaunchDarkly-canonical +content blocks to each provider's native shape. History messages are plain dicts: ``{"role": ..., "content": ...}`` where ``content`` is either a string or a list of content-block dicts: diff --git a/packages/client/src/launchdarkly_ai_server/lifecycle.py b/packages/client/src/launchdarkly_ai_server/lifecycle.py index 9b87cbe7..943c18af 100644 --- a/packages/client/src/launchdarkly_ai_server/lifecycle.py +++ b/packages/client/src/launchdarkly_ai_server/lifecycle.py @@ -614,7 +614,7 @@ async def extract_variation( f"Variation '{config_key}' returned None (flag may be disabled)." ) - # Only an explicit ``_ldMeta.enabled`` counts as enabled (spec §3.10, as in JS and Go). + # Only an explicit ``_ldMeta.enabled`` counts as enabled (as in JS and Go). ld_meta = (raw.get("_ldMeta") or {}) if isinstance(raw, dict) else {} if not isinstance(ld_meta, dict) or not ld_meta.get("enabled"): raise RuntimeError(f"Variation '{config_key}' is disabled.") diff --git a/packages/client/src/launchdarkly_ai_server/parameter_forwarding.py b/packages/client/src/launchdarkly_ai_server/parameter_forwarding.py index 69622078..f87bfc2f 100644 --- a/packages/client/src/launchdarkly_ai_server/parameter_forwarding.py +++ b/packages/client/src/launchdarkly_ai_server/parameter_forwarding.py @@ -5,7 +5,7 @@ sense across providers (``temperature``, ``top_p``, ``max_tokens``, ``tool_choice``, ...). No single provider SDK accepts all of them, and each handler package classifies every key its own provider entry point accepts into exactly one written-down list: forwarded (model and run settings -only, the cross-SDK list for that handler in TESTING.md section 1.12), handler-owned (set by the +only, the cross-SDK list for that handler), handler-owned (set by the call site itself), or excluded (accepted by the provider but never forwarded: credentials, endpoints and connection settings, raw request injection such as extra headers or ``model_kwargs``, remote tools, host-process settings, data location and retention, server-side state, identity and diff --git a/packages/client/tests/test_client.py b/packages/client/tests/test_client.py index 83fa514a..051f8d62 100644 --- a/packages/client/tests/test_client.py +++ b/packages/client/tests/test_client.py @@ -1,7 +1,6 @@ """ -Tests for §3.10 config(), §3.12 LD context interpolation, -§3.15 config().stream(). -Reference: TESTING.md §3.10, §3.12, §3.15 +Tests for config(), LD context interpolation and +config().stream(). """ from collections.abc import AsyncGenerator @@ -88,7 +87,7 @@ def mock_ld_client() -> MagicMock: # --------------------------------------------------------------------------- -# §3.10 config() — single handler +# config() — single handler # --------------------------------------------------------------------------- @@ -414,7 +413,7 @@ async def test_parse_failure_returns_raw_string_when_output_format_set( """When outputFormat is set but the handler returns an unparseable string, invoke() returns the raw string rather than raising — agents and streaming handlers cannot guarantee structured output (best-effort, consistent with - TypeScript SDK behavior). See TESTING.md §3.10.""" + TypeScript SDK behavior).""" raw = { "model": {"name": "gpt-4"}, "provider": {"name": "TestProvider"}, @@ -435,7 +434,7 @@ async def test_parse_failure_returns_raw_string_when_output_format_set( # --------------------------------------------------------------------------- -# §3.10 config() — multi-handler routing +# config() — multi-handler routing # --------------------------------------------------------------------------- @@ -618,7 +617,7 @@ async def test_throws_mode_specific_error_when_provider_matches_but_mode_does_no # --------------------------------------------------------------------------- -# §3.12 LD context interpolation +# LD context interpolation # --------------------------------------------------------------------------- @@ -707,7 +706,7 @@ async def capturing_fn( # --------------------------------------------------------------------------- -# §3.15 config().stream() +# config().stream() # --------------------------------------------------------------------------- diff --git a/packages/client/tests/test_evaluations_spans.py b/packages/client/tests/test_evaluations_spans.py index 4dd806d3..154b9a3a 100644 --- a/packages/client/tests/test_evaluations_spans.py +++ b/packages/client/tests/test_evaluations_spans.py @@ -1,7 +1,4 @@ -"""Offline evaluation spans join the customer project on the existing exporter. - -Reference: TESTING.md §8.10. -""" +"""Offline evaluation spans join the customer project on the existing exporter.""" from __future__ import annotations diff --git a/packages/client/tests/test_graph.py b/packages/client/tests/test_graph.py index 24155844..ab46ad52 100644 --- a/packages/client/tests/test_graph.py +++ b/packages/client/tests/test_graph.py @@ -1,6 +1,5 @@ """ -Tests for §3.12 resolve_graph() and graph(). -Reference: TESTING.md §3.12 +Tests for resolve_graph() and graph(). """ from typing import Any @@ -327,7 +326,7 @@ async def test_graph_node_tracked_per_visited_node( async def test_node_variations_resolved_on_every_call( self, mock_ld_client: MagicMock ) -> None: - """Each invoke() re-resolves the graph and node flags. See TESTING.md §3.11.""" + """Each invoke() re-resolves the graph and node flags.""" g = graph("graph-key", handlers=[_make_handler()]) await g.invoke("hi", CONTEXT) after_first = mock_ld_client.variation.call_count @@ -337,7 +336,7 @@ async def test_node_variations_resolved_on_every_call( async def test_flag_change_reaches_reused_graph( self, mock_ld_client: MagicMock ) -> None: - """A node flag change between calls is seen on the next call. See TESTING.md §3.11.""" + """A node flag change between calls is seen on the next call.""" state = {"instructions": "v1", "graph_enabled": True} async def variation(key: str, ctx: dict, default: Any) -> Any: @@ -541,7 +540,7 @@ async def test_reverse_traverse_key_is_snake_case( self, mock_ld_client: MagicMock ) -> None: """Python GraphDefinition must expose 'reverse_traverse' (snake_case), not - 'reverseTraverse' (camelCase). See TESTING.md Appendix A.2.""" + 'reverseTraverse' (camelCase).""" gd = await resolve_graph( "graph-key", context=CONTEXT, handlers=[_make_handler()] ) @@ -556,7 +555,7 @@ async def test_disabled_graph_reverse_traverse_key_is_snake_case( self, mock_ld_client: MagicMock ) -> None: """Disabled GraphDefinition stub must also use 'reverse_traverse', not - 'reverseTraverse'. See TESTING.md Appendix A.2.""" + 'reverseTraverse'.""" mock_ld_client.variation = AsyncMock(return_value={"edges": {}}) gd = await resolve_graph("graph-key", context=CONTEXT) assert hasattr(gd, "reverse_traverse") diff --git a/packages/client/tests/test_graph_stream.py b/packages/client/tests/test_graph_stream.py index f3f6d624..f2e79596 100644 --- a/packages/client/tests/test_graph_stream.py +++ b/packages/client/tests/test_graph_stream.py @@ -1,6 +1,5 @@ """ -Tests for §3.15a ``graph().stream()``. -Reference: TESTING.md §3.15a, Appendix A.4 / A.13. +Tests for ``graph().stream()``. """ from __future__ import annotations @@ -27,7 +26,7 @@ CONTEXT = {"kind": "user", "key": "u1"} -# TESTING.md §3.15a pins these byte for byte; every SDK sends the same text. +# These are pinned byte for byte; every SDK sends the same text. ROUTING_SUFFIX = ( "\n\nComplete your task using your available tools first. Only once your own " "part is done, call exactly one transfer tool to route to the next agent. Do " @@ -769,7 +768,7 @@ async def invoke_fn( async def test_routing_text_matches_the_spec( self, mock_ld_client: MagicMock, mode: str, own_tool: bool ) -> None: - """TESTING.md §3.15a pins both strings byte for byte across SDKs (AIC-3601). + """Both strings are pinned byte for byte across SDKs (AIC-3601). The same text goes to a node with a tool of its own and to one with none, since the graph has no router role to branch on. ```` is the raw @@ -849,7 +848,7 @@ async def stream_fn( # --------------------------------------------------------------------------- -# Conversation id + OTel parenting + abandonment (§3.15a / A.4) +# Conversation id + OTel parenting + abandonment # --------------------------------------------------------------------------- diff --git a/packages/client/tests/test_judges.py b/packages/client/tests/test_judges.py index 6e53b4ee..ee5fd7c7 100644 --- a/packages/client/tests/test_judges.py +++ b/packages/client/tests/test_judges.py @@ -1,6 +1,5 @@ """ -Tests for §3.14 run_judges. -Reference: TESTING.md §3.14 +Tests for run_judges. """ import json @@ -704,7 +703,7 @@ async def judge_fn( class TestRunJudgeTrackData: - """§3.13 run_judge result track_data carries the evaluated (parent) config identity.""" + """run_judge result track_data carries the evaluated (parent) config identity.""" PARENT: ClassVar[dict[str, Any]] = { "runId": "parent-run", diff --git a/packages/client/tests/test_ld_span_attributes.py b/packages/client/tests/test_ld_span_attributes.py index bd0b470a..edbda39d 100644 --- a/packages/client/tests/test_ld_span_attributes.py +++ b/packages/client/tests/test_ld_span_attributes.py @@ -1,4 +1,4 @@ -"""Context identity on the root feature_flag span. TESTING.md §3.18.""" +"""Context identity on the root feature_flag span.""" from __future__ import annotations diff --git a/packages/client/tests/test_lifecycle.py b/packages/client/tests/test_lifecycle.py index abf65852..164ff5ca 100644 --- a/packages/client/tests/test_lifecycle.py +++ b/packages/client/tests/test_lifecycle.py @@ -1,6 +1,5 @@ """ -Tests for §3.9 init_client / get_client / shutdown. -Reference: TESTING.md §3.9 +Tests for init_client / get_client / shutdown. """ import ast @@ -724,7 +723,7 @@ async def test_completes_teardown_even_if_flush_throws(self) -> None: # --------------------------------------------------------------------------- -# OTel setup details (§3.9) +# OTel setup details # --------------------------------------------------------------------------- @@ -1119,7 +1118,7 @@ async def test_never_raises_when_variation_throws(self) -> None: class TestExtractVariationMissingEnabled: - # Spec §3.10: only ``_ldMeta.enabled: true`` is enabled, as in JS and Go. + # Only ``_ldMeta.enabled: true`` is enabled, as in JS and Go. @pytest.mark.parametrize("ld_meta", [None, {}], ids=["no_ldMeta", "empty_ldMeta"]) async def test_raises_when_enabled_is_missing( self, ld_meta: dict[str, Any] | None diff --git a/packages/client/tests/test_registry.py b/packages/client/tests/test_registry.py index 407ed96e..1aab1dc1 100644 --- a/packages/client/tests/test_registry.py +++ b/packages/client/tests/test_registry.py @@ -1,6 +1,5 @@ """ -Tests for §3.6 Registry and compose, §3.7 resolve_handlers and resolve_tools. -Reference: TESTING.md §3.6–3.7 +Tests for Registry and compose, and for resolve_handlers and resolve_tools. """ import logging @@ -27,7 +26,7 @@ async def fn(config, user_input, tool_handlers, variables): # type: ignore[over # --------------------------------------------------------------------------- -# §3.6 Registry +# Registry # --------------------------------------------------------------------------- @@ -121,7 +120,7 @@ def test_register_is_additive(self) -> None: # --------------------------------------------------------------------------- -# §3.6 compose +# compose # --------------------------------------------------------------------------- @@ -170,7 +169,7 @@ def test_non_conflicting_entries_are_merged(self) -> None: # --------------------------------------------------------------------------- -# §3.7 resolve_handlers +# resolve_handlers # --------------------------------------------------------------------------- @@ -202,7 +201,7 @@ def test_registry_present_but_empty_no_local(self) -> None: # --------------------------------------------------------------------------- -# §3.7 resolve_tools +# resolve_tools # --------------------------------------------------------------------------- diff --git a/packages/client/tests/test_sdk_info.py b/packages/client/tests/test_sdk_info.py index dff75744..43e53e59 100644 --- a/packages/client/tests/test_sdk_info.py +++ b/packages/client/tests/test_sdk_info.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.9 AI SDK package information events.""" +"""Tests for AI SDK package information events.""" from unittest.mock import MagicMock diff --git a/packages/client/tests/test_sdk_usage.py b/packages/client/tests/test_sdk_usage.py index 73f0e33a..f0aa1034 100644 --- a/packages/client/tests/test_sdk_usage.py +++ b/packages/client/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 helper usage events.""" +"""Tests for SDK helper usage events.""" import time from pathlib import Path @@ -516,7 +516,7 @@ async def test_package_name_without_version_reports_the_core( # --------------------------------------------------------------------------- -# Agent Skills helpers (§3.27) +# Agent Skills helpers # --------------------------------------------------------------------------- SKILL_KEY = "test-skill" diff --git a/packages/client/tests/test_skills_fdv2.py b/packages/client/tests/test_skills_fdv2.py index f2ed212c..071d5879 100644 --- a/packages/client/tests/test_skills_fdv2.py +++ b/packages/client/tests/test_skills_fdv2.py @@ -1678,7 +1678,7 @@ def test_a_304_confirms_a_payload_but_cannot_establish_one( Releasing ``wait_for_skills`` on it would make ``is_initialized`` true over an empty committed set — the fact ``write_skills("*")`` prunes on — so a reconcile that raced delivery would delete every managed skill on - disk instead of reporting the retrieval unavailable (§3.21). Failing + disk instead of reporting the retrieval unavailable. Failing closed costs a boot that is genuinely waiting nothing it was not already waiting for. """ diff --git a/packages/client/tests/test_tracking.py b/packages/client/tests/test_tracking.py index 8a625d3f..eb9db7dc 100644 --- a/packages/client/tests/test_tracking.py +++ b/packages/client/tests/test_tracking.py @@ -1,6 +1,5 @@ """ -Tests for §3.8 wrap_tool_handlers. -Reference: TESTING.md §3.8 +Tests for wrap_tool_handlers. """ from unittest.mock import MagicMock, patch diff --git a/packages/client/tests/test_utils.py b/packages/client/tests/test_utils.py index ca8b38fd..dd6035e8 100644 --- a/packages/client/tests/test_utils.py +++ b/packages/client/tests/test_utils.py @@ -1,7 +1,6 @@ """ Tests for parse_template, parse_json_with_possible_fences, parse_usage, normalize_mode, create_handler. -Reference: TESTING.md s3.1-3.4, s3.15 """ from typing import Any @@ -271,7 +270,7 @@ async def fn( class TestMakeTrackData: - """§3.10 model stamps — shared node-trackData builder for native graph adapters.""" + """Model stamps — shared node-trackData builder for native graph adapters.""" def _node(self, meta: dict) -> Any: from launchdarkly_ai_server.types import GraphNode @@ -349,7 +348,7 @@ def test_omits_environment_id_when_unset( class TestModelStampsFromMeta: - """§3.10 model stamps — malformed ``modelVersion`` is omitted, never raises.""" + """Model stamps — malformed ``modelVersion`` is omitted, never raises.""" def test_copies_int_version_and_non_empty_key(self) -> None: assert model_stamps_from_meta({"modelKey": "m", "modelVersion": 3}) == { diff --git a/packages/langchain-agents/agents.md b/packages/langchain-agents/agents.md index 2e4187ec..c89044f2 100644 --- a/packages/langchain-agents/agents.md +++ b/packages/langchain-agents/agents.md @@ -199,6 +199,6 @@ Span names and attributes are described in [Implementation Details → Telemetry - **No manual tool loop**: `create_react_agent` manages the entire reasoning loop internally. Do not add a manual tool-call loop on top — it would be redundant and would interfere with LangGraph's state graph. - **`result["messages"]` is the full history**: the agent appends every intermediate AI message, tool call, and tool result. Always take the **last** message as the final output. - **`usage_metadata` may be sparse**: not every message carries usage. The accumulation loop silently skips messages where `usage_metadata` is absent. If total tokens are `0`, the underlying model doesn't report per-message usage. -- **`add_messages` and all annotation-reducer symbols must be module-level imports.** `from __future__ import annotations` (present at the top of `native_graph.py`) causes Python to store all annotations as strings. When LangGraph calls `get_type_hints(WorkflowState)`, Python evaluates those strings in the **module's global namespace** — not in the local scope of the `invoke()` function. Any symbol that is only a local variable (e.g. imported inside `invoke()`) will raise `NameError: name 'add_messages' is not defined` at `StateGraph(WorkflowState)` time. See Appendix A.3 in `TESTING.md`. +- **`add_messages` and all annotation-reducer symbols must be module-level imports.** `from __future__ import annotations` (present at the top of `native_graph.py`) causes Python to store all annotations as strings. When LangGraph calls `get_type_hints(WorkflowState)`, Python evaluates those strings in the **module's global namespace** — not in the local scope of the `invoke()` function. Any symbol that is only a local variable (e.g. imported inside `invoke()`) will raise `NameError: name 'add_messages' is not defined` at `StateGraph(WorkflowState)` time. - **`tool()` executor receives parsed args**: LangGraph parses the model's tool call arguments before calling the executor. Do not call `json.loads` inside the tool executor. - **`last_message.content` may not be a string**: if the final message is a tool call or has a complex content list, the `isinstance(..., str)` check fails and `output` will be `''`. This should not occur in a well-behaved ReAct agent but is guarded defensively. diff --git a/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py b/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py index 5346a412..47955420 100644 --- a/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py +++ b/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/handler.py @@ -85,7 +85,7 @@ #: ``TestChatOpenAIAcceptsExactlyTheseKeys`` in this package's tests asserts this classification #: stays exhaustive as the SDK's own pydantic model changes. #: -#: This is the cross-SDK list for LangChain ChatOpenAI (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatOpenAI. #: ``reasoning_effort`` and ``seed`` are marked not supported in TypeScript there: the JS #: ``ChatOpenAI`` takes them only as per-call options, while this handler sets them on the #: constructor, which has a field for each. ``reasoning_effort`` is the Chat Completions field and @@ -131,7 +131,7 @@ #: handler gets back. ``truncation``, ``context_management`` and ``use_previous_response_id`` #: also lean on server-side conversation state. #: * ``store``: data retention. It decides whether the request is kept on the server. -#: * ``prompt_cache_key``: on the spec list, marked not supported in Python, because +#: * ``prompt_cache_key``: on the cross-SDK list, marked not supported in Python, because #: ``ChatOpenAI`` has no such constructor field (it takes it only per call). It is not in the set #: below because ``ChatOpenAI`` does not accept it at all, which the drift test requires of every #: listed key. @@ -181,7 +181,7 @@ #: as :data:`_CHAT_OPENAI_FORWARDED_KEYS`. ``TestChatAnthropicAcceptsExactlyTheseKeys`` in this #: package's tests asserts this classification stays exhaustive. #: -#: This is the cross-SDK list for LangChain ChatAnthropic (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatAnthropic. #: ``max_tokens_to_sample`` is renamed to ``max_tokens`` before this list applies, and a #: ``max_tokens`` the config also set wins. ``stop`` is renamed to ``stop_sequences`` the same way, and #: a ``stop_sequences`` the config also set wins (see :data:`_CHAT_ANTHROPIC_RENAMES`). @@ -304,7 +304,7 @@ def _rebuild_anthropic_output_config(kwargs: dict[str, Any]) -> dict[str, Any]: #: ``TestChatBedrockConverseAcceptsExactlyTheseKeys`` in this package's tests skips itself when it #: is not installed rather than asserting nothing. #: -#: This is the cross-SDK list for LangChain ChatBedrockConverse (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatBedrockConverse. #: ``reasoning_effort``, ``stop`` and ``stop_sequences`` are marked not supported in TypeScript #: there: the JS ``ChatBedrockConverse`` takes stop sequences only as a per-call option and has no #: reasoning effort field, while this class has constructor fields for both. ``stop`` is renamed to diff --git a/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/messages.py b/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/messages.py index f305df1e..21e3fa86 100644 --- a/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/messages.py +++ b/packages/langchain-agents/src/launchdarkly_ai_langchain_agents/messages.py @@ -3,7 +3,7 @@ Images travel as ``image_url`` content parts with a data or remote URL — the standard multimodal shape every LangChain chat model accepts — rather than the LaunchDarkly-canonical ``{"type": "image", "source": ...}`` block, which no -LangChain provider understands (TESTING.md Appendix A.7). +LangChain provider understands. """ from __future__ import annotations diff --git a/packages/langchain-agents/tests/test_graph.py b/packages/langchain-agents/tests/test_graph.py index e6ea186f..f3c1e28c 100644 --- a/packages/langchain-agents/tests/test_graph.py +++ b/packages/langchain-agents/tests/test_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.1 graph convenience wrapper (langchain_graph) and §2.x.2 full coverage. -Reference: TESTING.md §2.1, §2.x.2 +Tests for the graph convenience wrapper (langchain_graph), with full coverage. """ from unittest.mock import MagicMock, patch diff --git a/packages/langchain-agents/tests/test_handler.py b/packages/langchain-agents/tests/test_handler.py index a68c42a4..05c84825 100644 --- a/packages/langchain-agents/tests/test_handler.py +++ b/packages/langchain-agents/tests/test_handler.py @@ -1,7 +1,6 @@ """ Tests for launchdarkly-ai-langchain-agents handler. -Covers §1.1–1.9 (generic) and §2.x.1 span name. -Reference: TESTING.md §1, §2.x (LangChain) +Covers the generic handler tests and the LangChain span name. """ from __future__ import annotations @@ -112,14 +111,14 @@ def _side_effect(name: str) -> Any: # --------------------------------------------------------------------------- -# §2.x.0 Agent creation API (Python: create_react_agent from langgraph.prebuilt) +# Agent creation API (Python: create_react_agent from langgraph.prebuilt) # --------------------------------------------------------------------------- class TestAgentCreationAPI: @pytest.mark.asyncio async def test_calls_create_react_agent_not_createAgent(self) -> None: - """§2.x.0 — Python handler must call create_react_agent from langgraph.prebuilt.""" + """Python handler must call create_react_agent from langgraph.prebuilt.""" captured: dict[str, Any] = {"called": False, "args": None, "kwargs": None} mock_agent = AsyncMock() @@ -172,7 +171,7 @@ def _import_side_effect(name: str) -> Any: # --------------------------------------------------------------------------- -# §1.1 Factory +# Factory # --------------------------------------------------------------------------- @@ -209,7 +208,7 @@ async def test_returns_text_from_mixed_thinking_and_text_blocks(self) -> None: # --------------------------------------------------------------------------- -# §1.2 Prompt construction +# Prompt construction # --------------------------------------------------------------------------- @@ -329,7 +328,7 @@ def test_path_c_instructions_takes_priority_over_messages(self) -> None: # --------------------------------------------------------------------------- -# §1.3 Tool conversion +# Tool conversion # --------------------------------------------------------------------------- @@ -406,7 +405,7 @@ def _capture_tool(fn: Any, **kw: Any) -> Any: # --------------------------------------------------------------------------- -# §1.4 Tool execution loop +# Tool execution loop # --------------------------------------------------------------------------- @@ -628,7 +627,7 @@ async def test_no_tools_in_config_handler_never_invoked(self) -> None: # --------------------------------------------------------------------------- -# §1.5 Telemetry +# Telemetry # --------------------------------------------------------------------------- @@ -1312,7 +1311,7 @@ async def test_rethrows_error(self) -> None: # --------------------------------------------------------------------------- -# §1.7 Convenience export +# Convenience export # --------------------------------------------------------------------------- @@ -1373,7 +1372,7 @@ def test_callable_without_extra_kwargs(self) -> None: # --------------------------------------------------------------------------- -# §1.8 Streaming +# Streaming # --------------------------------------------------------------------------- @@ -1570,7 +1569,7 @@ async def _astream(*_a: Any, **_kw: Any) -> AsyncIterator[Any]: # --------------------------------------------------------------------------- -# §1.5 Streaming telemetry (Appendix A.5 — do not patch _HAS_OTEL=False) +# Streaming telemetry (do not patch _HAS_OTEL=False) # --------------------------------------------------------------------------- @@ -1715,7 +1714,7 @@ async def test_process_default_is_read_on_the_streaming_path(self) -> None: # --------------------------------------------------------------------------- -# §1.9 Output format +# Output format # --------------------------------------------------------------------------- @@ -1767,12 +1766,12 @@ def _capture_agent(*args: Any, **kw: Any) -> Any: # --------------------------------------------------------------------------- -# §1.2 Path C — None user_input must not raise or produce None content +# None user_input must not raise or produce None content # --------------------------------------------------------------------------- class TestNoneUserInput: - """TESTING.md §1.2 Path C: _build_initial_messages must not pass None to + """_build_initial_messages must not pass None to HumanMessage when user_input is None.""" def test_none_user_input_no_none_human_message_content(self) -> None: @@ -1803,7 +1802,7 @@ def test_none_user_input_no_none_human_message_content(self) -> None: for arg in captured_human_args: assert arg is not None, ( "HumanMessage was constructed with None content; " - "must use '' when user_input is None (TESTING.md §1.2 Path C)" + "must use '' when user_input is None" ) diff --git a/packages/langchain-agents/tests/test_native_graph.py b/packages/langchain-agents/tests/test_native_graph.py index c913e43a..67d68622 100644 --- a/packages/langchain-agents/tests/test_native_graph.py +++ b/packages/langchain-agents/tests/test_native_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.2 native graph adapter (to_lang_graph) and LangChain-specific specs. -Reference: TESTING.md §2.2, §2.x.3 +Tests for the native graph adapter (to_lang_graph) and LangChain-specific specs. """ from __future__ import annotations @@ -245,7 +244,7 @@ def _patch_imports(mocks: dict[str, Any]) -> Any: # --------------------------------------------------------------------------- -# §2.2 Generic topology +# Generic topology # --------------------------------------------------------------------------- @@ -355,7 +354,7 @@ async def test_runner_returns_final_output(self) -> None: # --------------------------------------------------------------------------- -# §2.x.3 LangChain-specific specs +# LangChain-specific specs # --------------------------------------------------------------------------- @@ -1229,7 +1228,7 @@ async def async_handler(args: dict[str, Any]) -> str: # --------------------------------------------------------------------------- -# §2.x.3 WorkflowState annotations resolve — real StateGraph (no mock) +# WorkflowState annotations resolve — real StateGraph (no mock) # --------------------------------------------------------------------------- diff --git a/packages/langchain-agents/tests/test_sdk_usage.py b/packages/langchain-agents/tests/test_sdk_usage.py index 27a0ced8..555f47fc 100644 --- a/packages/langchain-agents/tests/test_sdk_usage.py +++ b/packages/langchain-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — langchain-agents.""" +"""Tests for SDK helper usage events — langchain-agents.""" from types import SimpleNamespace from typing import Any diff --git a/packages/langchain-messages/src/launchdarkly_ai_langchain_messages/handler.py b/packages/langchain-messages/src/launchdarkly_ai_langchain_messages/handler.py index 78f36622..1b1207ae 100644 --- a/packages/langchain-messages/src/launchdarkly_ai_langchain_messages/handler.py +++ b/packages/langchain-messages/src/launchdarkly_ai_langchain_messages/handler.py @@ -87,7 +87,7 @@ #: ``TestChatOpenAIAcceptsExactlyTheseKeys`` in this package's tests asserts this classification #: stays exhaustive as the SDK's own pydantic model changes. #: -#: This is the cross-SDK list for LangChain ChatOpenAI (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatOpenAI. #: ``reasoning_effort`` and ``seed`` are marked not supported in TypeScript there: the JS #: ``ChatOpenAI`` takes them only as per-call options, while this handler sets them on the #: constructor, which has a field for each. ``reasoning_effort`` is the Chat Completions field and @@ -133,7 +133,7 @@ #: handler gets back. ``truncation``, ``context_management`` and ``use_previous_response_id`` #: also lean on server-side conversation state. #: * ``store``: data retention. It decides whether the request is kept on the server. -#: * ``prompt_cache_key``: on the spec list, marked not supported in Python, because +#: * ``prompt_cache_key``: on the cross-SDK list, marked not supported in Python, because #: ``ChatOpenAI`` has no such constructor field (it takes it only per call). It is not in the set #: below because ``ChatOpenAI`` does not accept it at all, which the drift test requires of every #: listed key. @@ -183,7 +183,7 @@ #: as :data:`_CHAT_OPENAI_FORWARDED_KEYS`. ``TestChatAnthropicAcceptsExactlyTheseKeys`` in this #: package's tests asserts this classification stays exhaustive. #: -#: This is the cross-SDK list for LangChain ChatAnthropic (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatAnthropic. #: ``max_tokens_to_sample`` is renamed to ``max_tokens`` before this list applies, and a #: ``max_tokens`` the config also set wins. ``stop`` is renamed to ``stop_sequences`` the same way, and #: a ``stop_sequences`` the config also set wins (see :data:`_CHAT_ANTHROPIC_RENAMES`). @@ -306,7 +306,7 @@ def _rebuild_anthropic_output_config(kwargs: dict[str, Any]) -> dict[str, Any]: #: ``TestChatBedrockConverseAcceptsExactlyTheseKeys`` in this package's tests skips itself when it #: is not installed rather than asserting nothing. #: -#: This is the cross-SDK list for LangChain ChatBedrockConverse (TESTING.md section 1.12). +#: This is the cross-SDK list for LangChain ChatBedrockConverse. #: ``reasoning_effort``, ``stop`` and ``stop_sequences`` are marked not supported in TypeScript #: there: the JS ``ChatBedrockConverse`` takes stop sequences only as a per-call option and has no #: reasoning effort field, while this class has constructor fields for both. ``stop`` is renamed to diff --git a/packages/langchain-messages/tests/test_handler.py b/packages/langchain-messages/tests/test_handler.py index d18aed5f..1f5db5f9 100644 --- a/packages/langchain-messages/tests/test_handler.py +++ b/packages/langchain-messages/tests/test_handler.py @@ -1,6 +1,6 @@ """ Tests for launchdarkly-ai-langchain-messages handler. -Covers §1.1-1.10 (generic handler tests) plus TELEMETRY-CONTRACT.md sections 1-9. +Covers the generic handler tests plus TELEMETRY-CONTRACT.md sections 1-9. """ from __future__ import annotations @@ -174,7 +174,7 @@ def _make_tracer_patch(mock_span: MagicMock) -> Any: # --------------------------------------------------------------------------- -# §1.1 Factory function and metadata +# Factory function and metadata # --------------------------------------------------------------------------- @@ -206,7 +206,7 @@ def test_multiple_calls_return_independent_instances(self) -> None: # --------------------------------------------------------------------------- -# §1.2 Prompt construction +# Prompt construction # --------------------------------------------------------------------------- @@ -326,7 +326,7 @@ async def test_returns_text_from_mixed_thinking_and_text_blocks(self) -> None: # --------------------------------------------------------------------------- -# §1.3 Tool conversion +# Tool conversion # --------------------------------------------------------------------------- @@ -379,7 +379,7 @@ async def test_empty_tools_no_tools_sent(self) -> None: # --------------------------------------------------------------------------- -# §1.4 Tool execution loop +# Tool execution loop # --------------------------------------------------------------------------- @@ -1027,7 +1027,7 @@ async def test_rethrows_error(self) -> None: # --------------------------------------------------------------------------- -# §1.9 Structured output — withStructuredOutput +# Structured output — withStructuredOutput # --------------------------------------------------------------------------- @@ -1108,7 +1108,7 @@ async def test_token_usage_from_usage_metadata_when_structured_output(self) -> N # --------------------------------------------------------------------------- -# §1.7 Convenience export +# Convenience export # --------------------------------------------------------------------------- @@ -1155,7 +1155,7 @@ def test_callable_without_extra_kwargs(self) -> None: # --------------------------------------------------------------------------- -# §1.8 Streaming +# Streaming # --------------------------------------------------------------------------- @@ -1639,7 +1639,7 @@ async def test_process_default_is_read_on_the_streaming_path(self) -> None: # --------------------------------------------------------------------------- -# §1.10 MAX_STEPS cap +# MAX_STEPS cap # --------------------------------------------------------------------------- diff --git a/packages/langchain-messages/tests/test_sdk_usage.py b/packages/langchain-messages/tests/test_sdk_usage.py index e297e4c6..489dad92 100644 --- a/packages/langchain-messages/tests/test_sdk_usage.py +++ b/packages/langchain-messages/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — langchain-messages.""" +"""Tests for SDK helper usage events — langchain-messages.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/litellm-agents/tests/test_sdk_usage.py b/packages/litellm-agents/tests/test_sdk_usage.py index 776f3cba..06b99c12 100644 --- a/packages/litellm-agents/tests/test_sdk_usage.py +++ b/packages/litellm-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — litellm-agents.""" +"""Tests for SDK helper usage events — litellm-agents.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/litellm-messages/src/launchdarkly_ai_litellm_messages/handler.py b/packages/litellm-messages/src/launchdarkly_ai_litellm_messages/handler.py index 6ded1a1b..0b99700c 100644 --- a/packages/litellm-messages/src/launchdarkly_ai_litellm_messages/handler.py +++ b/packages/litellm-messages/src/launchdarkly_ai_litellm_messages/handler.py @@ -46,7 +46,7 @@ def _json_schema(output_format: dict[str, Any]) -> dict[str, Any]: return schema -#: The cross-SDK list for LiteLLM Messages (TESTING.md section 1.12): the ``model.parameters`` +#: The cross-SDK list for LiteLLM Messages: the ``model.parameters`` #: keys forwarded to ``litellm.acompletion``. Every other key is dropped. The handler sets #: ``model``, ``messages``, ``stream``, ``stream_options``, ``tools`` and ``response_format`` itself, #: and ``api_key`` (with ``api_base``) comes from the handler's options, never from the config. diff --git a/packages/litellm-messages/tests/test_sdk_usage.py b/packages/litellm-messages/tests/test_sdk_usage.py index 63ab4b88..28ca4e6f 100644 --- a/packages/litellm-messages/tests/test_sdk_usage.py +++ b/packages/litellm-messages/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — litellm-messages.""" +"""Tests for SDK helper usage events — litellm-messages.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py b/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py index d68c60f2..7bc17a8c 100644 --- a/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py +++ b/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py @@ -86,7 +86,7 @@ #: ``TestModelSettingsAcceptsExactlyTheseFields`` in this package's tests asserts this #: classification stays exhaustive as the SDK's own dataclass changes. #: -#: This is the cross-SDK list for OpenAI Agents (TESTING.md section 1.12), less the two keys that +#: This is the cross-SDK list for OpenAI Agents, less the two keys that #: are not ``ModelSettings`` fields: ``max_turns`` goes to the run, and ``text`` is read only for #: ``text.verbosity``, which becomes ``verbosity`` (see :func:`_apply_text_verbosity`). _MODEL_SETTINGS_FORWARDED_KEYS = frozenset( diff --git a/packages/openai-agents/src/launchdarkly_ai_openai_agents/native_graph.py b/packages/openai-agents/src/launchdarkly_ai_openai_agents/native_graph.py index 51ed6436..7d086081 100644 --- a/packages/openai-agents/src/launchdarkly_ai_openai_agents/native_graph.py +++ b/packages/openai-agents/src/launchdarkly_ai_openai_agents/native_graph.py @@ -279,7 +279,7 @@ async def on_agent_start(self, context: Any, agent: Any) -> None: if history: # config.instructions takes priority over config.messages, so skip # config conversation turns when instructions are set (parity with the - # single-node handler and TESTING.md §1.11 composition order). + # single-node handler's history composition order). config_messages = ( [] if root.config.get("instructions") diff --git a/packages/openai-agents/tests/test_graph.py b/packages/openai-agents/tests/test_graph.py index b7eeab0f..308e6053 100644 --- a/packages/openai-agents/tests/test_graph.py +++ b/packages/openai-agents/tests/test_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.1 graph convenience wrapper (openai_graph) and §2.x.4 full coverage. -Reference: TESTING.md §2.1, §2.x.4 +Tests for the graph convenience wrapper (openai_graph), with full coverage. """ from unittest.mock import MagicMock, patch diff --git a/packages/openai-agents/tests/test_handler.py b/packages/openai-agents/tests/test_handler.py index db1737f5..4d218e90 100644 --- a/packages/openai-agents/tests/test_handler.py +++ b/packages/openai-agents/tests/test_handler.py @@ -1,7 +1,7 @@ """ Tests for launchdarkly-ai-openai-agents handler. -TELEMETRY-CONTRACT.md sections 1-9 for the span tree, and TESTING.md §1 for the generic handler +TELEMETRY-CONTRACT.md sections 1-9 for the span tree, and the generic handler behaviours. Rewritten from the pre-span-work version: this handler drives the ``agents`` SDK's own ``Runner``, which owns the per-turn loop, so a test drives spans by calling the ``RunHooks`` callbacks (``on_llm_start`` / ``on_llm_end`` / ``on_tool_start`` / ``on_tool_end``) the way the real @@ -356,7 +356,7 @@ def _recording() -> Any: # --------------------------------------------------------------------------- -# §1.1 Factory +# Factory # --------------------------------------------------------------------------- @@ -378,7 +378,7 @@ def test_provides_for_values_are_correct(self) -> None: # --------------------------------------------------------------------------- -# §1.2 Prompt construction +# Prompt construction # --------------------------------------------------------------------------- @@ -524,7 +524,7 @@ def test_none_user_input_becomes_empty_string(self) -> None: # --------------------------------------------------------------------------- -# §1.3 / §1.4 Tool conversion and execution +# Tool conversion and execution # --------------------------------------------------------------------------- @@ -619,7 +619,7 @@ def _sync_wrapper(args: Any) -> Any: # --------------------------------------------------------------------------- -# §1.4 Tool execution loop (pre-span-era; restored, not telemetry) +# Tool execution loop (pre-span-era; restored, not telemetry) # --------------------------------------------------------------------------- @@ -1184,7 +1184,7 @@ async def test_keeps_malformed_tool_arguments_verbatim(self) -> None: # --------------------------------------------------------------------------- -# §1.6 Error handling (top-level, no partial usage) +# Error handling (top-level, no partial usage) # --------------------------------------------------------------------------- @@ -1287,7 +1287,7 @@ async def never_returns( # --------------------------------------------------------------------------- -# §1.7 Convenience export +# Convenience export # --------------------------------------------------------------------------- @@ -1691,7 +1691,7 @@ async def test_emits_no_content_by_default_on_the_streaming_path(self) -> None: # --------------------------------------------------------------------------- -# §1.9 Output format (_build_output_type) +# Output format (_build_output_type) # --------------------------------------------------------------------------- @@ -1878,12 +1878,12 @@ async def run(agent: Any, prompt: str, hooks: Any = None, **kw: Any) -> Any: # --------------------------------------------------------------------------- -# §1.2 Path C — None user_input must not produce None prompt +# None user_input must not produce None prompt # --------------------------------------------------------------------------- class TestNoneUserInput: - """TESTING.md §1.2 Path C: When user_input is None, the prompt passed to + """When user_input is None, the prompt passed to Runner.run must be '' (empty string), not None.""" async def test_none_user_input_instructions_path_prompt_is_empty_string( diff --git a/packages/openai-agents/tests/test_native_graph.py b/packages/openai-agents/tests/test_native_graph.py index 57eb5c8f..f789a3da 100644 --- a/packages/openai-agents/tests/test_native_graph.py +++ b/packages/openai-agents/tests/test_native_graph.py @@ -1,6 +1,5 @@ """ -Tests for §2.2 native graph adapter (to_openai_agents) and OpenAI-specific specs. -Reference: TESTING.md §2.2, §2.x.6 +Tests for the native graph adapter (to_openai_agents) and OpenAI-specific specs. """ from __future__ import annotations @@ -160,7 +159,7 @@ async def on_agent_start(self, context: Any, agent: Any) -> None: # --------------------------------------------------------------------------- -# §2.2 Generic topology +# Generic topology # --------------------------------------------------------------------------- @@ -399,7 +398,7 @@ async def test_runner_starts_at_root_and_returns_output(self) -> None: # --------------------------------------------------------------------------- -# §2.x.6 OpenAI-specific specs +# OpenAI-specific specs # --------------------------------------------------------------------------- @@ -935,7 +934,7 @@ async def _run_and_fire_hook(agent: Any, text: str, hooks: Any = None) -> Any: @pytest.mark.asyncio async def test_path_entries_are_unique(self) -> None: - """§2.x.6 — each node key must appear at most once in path. + """Each node key must appear at most once in path. When on_handoff adds the child key, on_agent_start must not add it again. """ # Two-node graph: root -> child diff --git a/packages/openai-agents/tests/test_sdk_usage.py b/packages/openai-agents/tests/test_sdk_usage.py index 2e0c8236..c0ba2152 100644 --- a/packages/openai-agents/tests/test_sdk_usage.py +++ b/packages/openai-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — openai-agents.""" +"""Tests for SDK helper usage events — openai-agents.""" from types import SimpleNamespace from typing import Any diff --git a/packages/openai-agents/tests/test_utils.py b/packages/openai-agents/tests/test_utils.py index 4e9ff1ec..1c8e039d 100644 --- a/packages/openai-agents/tests/test_utils.py +++ b/packages/openai-agents/tests/test_utils.py @@ -1,6 +1,5 @@ """ -Tests for §2.x.5 _build_output_type utility. -Reference: TESTING.md §2.x.5 +Tests for the _build_output_type utility. """ from launchdarkly_ai_openai_agents.utils import _build_output_type @@ -52,7 +51,7 @@ def test_required_populated_from_all_property_keys(self) -> None: assert "b" in required def test_required_omitted_when_properties_empty(self) -> None: - """§2.x.5 — required must not appear in schema when properties is empty. + """Required must not appear in schema when properties is empty. When ``outputFormat`` has a ``properties`` key that maps to ``{}``, the resulting schema must not include ``"required": []``. An empty diff --git a/packages/openai-messages/src/launchdarkly_ai_openai_messages/handler.py b/packages/openai-messages/src/launchdarkly_ai_openai_messages/handler.py index 5e804e41..897b5fc8 100644 --- a/packages/openai-messages/src/launchdarkly_ai_openai_messages/handler.py +++ b/packages/openai-messages/src/launchdarkly_ai_openai_messages/handler.py @@ -69,7 +69,7 @@ #: so ``reasoning`` is forwarded with its ``effort`` and ``summary`` (see :func:`_rebuild_reasoning`) #: and no top-level ``reasoning_effort`` is read. #: -#: This is the cross-SDK list for OpenAI Messages (TESTING.md section 1.12). The same list serves +#: This is the cross-SDK list for OpenAI Messages. The same list serves #: ``invoke`` and ``stream`` (both read :func:`_forwarded_parameters`), so one config behaves the #: same whichever is called. ``max_tool_calls`` is marked not supported in TypeScript there: the JS #: ``openai`` SDK's ``responses.create`` has no such parameter, while this SDK's ``create`` and diff --git a/packages/openai-messages/tests/test_handler.py b/packages/openai-messages/tests/test_handler.py index b4bdb69a..fb4d2035 100644 --- a/packages/openai-messages/tests/test_handler.py +++ b/packages/openai-messages/tests/test_handler.py @@ -1,7 +1,7 @@ """ Tests for launchdarkly-ai-openai-messages handler. -Covers §1.1-1.9. -Reference: TESTING.md §1, TELEMETRY-CONTRACT.md +Covers the generic handler tests. +Reference: TELEMETRY-CONTRACT.md """ from __future__ import annotations @@ -105,7 +105,7 @@ def mock_openai(mocker): # --------------------------------------------------------------------------- -# §1.1 Factory function and metadata +# Factory function and metadata # --------------------------------------------------------------------------- @@ -139,7 +139,7 @@ def test_multiple_calls_return_independent_instances( # --------------------------------------------------------------------------- -# §1.2 Prompt construction +# Prompt construction # --------------------------------------------------------------------------- @@ -276,7 +276,7 @@ async def test_path_c_both_instructions_and_messages_messages_wins( # --------------------------------------------------------------------------- -# §1.3 Tool conversion +# Tool conversion # --------------------------------------------------------------------------- @@ -331,7 +331,7 @@ async def test_empty_tools_no_tools_sent(self, mock_openai: MagicMock) -> None: # --------------------------------------------------------------------------- -# §1.4 Tool execution loop +# Tool execution loop # --------------------------------------------------------------------------- @@ -439,7 +439,7 @@ async def test_object_tool_result_reaches_the_model_as_json( # --------------------------------------------------------------------------- -# §1.5 Telemetry — span recording +# Telemetry — span recording # --------------------------------------------------------------------------- @@ -1054,7 +1054,7 @@ async def test_still_writes_the_legacy_content_events_when_enabled( # --------------------------------------------------------------------------- -# §1.6 Error handling +# Error handling # --------------------------------------------------------------------------- @@ -1160,7 +1160,7 @@ async def test_rethrows_error(self, mock_openai: MagicMock) -> None: # --------------------------------------------------------------------------- -# §1.9 Structured output (outputFormat) +# Structured output (outputFormat) # --------------------------------------------------------------------------- @@ -1516,7 +1516,7 @@ async def test_output_format_uses_text_format_json_schema( # --------------------------------------------------------------------------- -# §1.7 Convenience export +# Convenience export # --------------------------------------------------------------------------- @@ -1564,7 +1564,7 @@ def test_callable_without_extra_kwargs(self, mock_openai: MagicMock) -> None: # --------------------------------------------------------------------------- -# §1.8 Streaming +# Streaming # --------------------------------------------------------------------------- @@ -1973,7 +1973,7 @@ async def _bad_ctx() -> AsyncGenerator[Any, None]: async def test_tools_forwarded_on_second_streaming_turn( self, mock_openai: MagicMock ) -> None: - """§1.8 - tools must appear in stream_params on every streaming turn. + """Tools must appear in stream_params on every streaming turn. This is a pre-existing Python-only behaviour that diverges from the TypeScript SDK (which does not resend tools after the first turn). It changes what the model is offered, not what @@ -2066,12 +2066,12 @@ async def _stream_ctx(**kwargs: Any) -> AsyncGenerator[Any, None]: # --------------------------------------------------------------------------- -# §1.2 Path C — None user_input must not produce None content +# None user_input must not produce None content # --------------------------------------------------------------------------- class TestNoneUserInput: - """TESTING.md §1.2 Path C: When user_input is None, the user-role message + """When user_input is None, the user-role message content sent to the provider must be '' not None.""" async def test_none_user_input_instructions_path_no_none_content( @@ -2100,12 +2100,12 @@ async def _capture(**kwargs: Any) -> Any: # --------------------------------------------------------------------------- -# §1.10 MAX_STEPS cap +# MAX_STEPS cap # --------------------------------------------------------------------------- class TestMaxStepsCap: - """TESTING.md §1.10: The tool loop must break with an error after MAX_STEPS (5) iterations.""" + """The tool loop must break with an error after MAX_STEPS (5) iterations.""" def _tool_response(self) -> MagicMock: return _make_response( @@ -2189,7 +2189,7 @@ async def _iter() -> AsyncGenerator: # --------------------------------------------------------------------------- -# §1.5 Streaming telemetry (do not patch _HAS_OTEL=False) +# Streaming telemetry (do not patch _HAS_OTEL=False) # --------------------------------------------------------------------------- diff --git a/packages/openai-messages/tests/test_sdk_usage.py b/packages/openai-messages/tests/test_sdk_usage.py index ec3ab5fc..e8e1674f 100644 --- a/packages/openai-messages/tests/test_sdk_usage.py +++ b/packages/openai-messages/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — openai-messages.""" +"""Tests for SDK helper usage events — openai-messages.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/opencode-agents/agents.md b/packages/opencode-agents/agents.md index b5cc71f2..7a027df9 100644 --- a/packages/opencode-agents/agents.md +++ b/packages/opencode-agents/agents.md @@ -6,7 +6,7 @@ provides_for = ('*', 'agent') Each call starts `opencode serve`, speaks the stable HTTP API (`/session`, `/session/{id}/message`, `/event`, `/session/{id}/abort`), and stops the process when the stream finishes or the caller closes it. The package does not import or construct `anomalyco/opencode-sdk-python`. -This package is Python-only. There is no JS counterpart in this slice. The spec section, API surface list, and `usage-ids.txt` row live in `launchdarkly/ai-sdks-monorepo`, which is not this repository. The usage helper ids are `opencode-agents.createOpenCodeHandler` and `opencode-agents.opencodeAgents`. The package is a normal pre-v1 package, same as the other handlers. +This package is Python-only. There is no JS counterpart in this slice. The usage helper ids are `opencode-agents.createOpenCodeHandler` and `opencode-agents.opencodeAgents`. The package is a normal pre-v1 package, same as the other handlers. ## File map diff --git a/packages/opencode-agents/tests/test_sdk_usage.py b/packages/opencode-agents/tests/test_sdk_usage.py index 7667eb90..5167b07e 100644 --- a/packages/opencode-agents/tests/test_sdk_usage.py +++ b/packages/opencode-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — opencode-agents.""" +"""Tests for SDK helper usage events — opencode-agents.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/typesafe/tests/test_sdk_usage.py b/packages/typesafe/tests/test_sdk_usage.py index 2b300bf6..e12ef6e2 100644 --- a/packages/typesafe/tests/test_sdk_usage.py +++ b/packages/typesafe/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — typesafe.""" +"""Tests for SDK helper usage events — typesafe.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/vercel-agents/agents.md b/packages/vercel-agents/agents.md index 8254ede2..c1401416 100644 --- a/packages/vercel-agents/agents.md +++ b/packages/vercel-agents/agents.md @@ -14,5 +14,3 @@ This Tier 1 package provides the wildcard `("*", "agent")` handler. - Native graph spans end in `finally`, including `CancelledError`. - Runtime calls are always async context-managed and never enable duplicate runtime telemetry. - -`TESTING.md` §2.x and Appendix A.14 are authoritative. diff --git a/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py b/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py index a4b91379..2ea72ea8 100644 --- a/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py +++ b/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py @@ -48,7 +48,7 @@ #: :func:`_renamed_parameters`. Every other key is dropped. The handler sets ``model``, #: ``messages``, ``tools`` and ``output_type`` itself, and runs the tool loop. #: -#: This is the Vercel AI SDK list from TESTING.md section 1.12 as the Python ``ai`` library can +#: This is the cross-SDK Vercel AI SDK list as the Python ``ai`` library can #: send it. ``stop_sequences`` is on the JS list but the Python library has no field for it. #: ``parallel_tool_calls``, ``max_tool_calls``, ``service_tier`` and ``prompt_cache_key`` are #: Python-only: the library takes them as typed request params, while the JS AI SDK can reach them diff --git a/packages/vercel-agents/tests/test_sdk_usage.py b/packages/vercel-agents/tests/test_sdk_usage.py index 032b3976..fb5be5e1 100644 --- a/packages/vercel-agents/tests/test_sdk_usage.py +++ b/packages/vercel-agents/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — vercel-agents.""" +"""Tests for SDK helper usage events — vercel-agents.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/packages/vercel-messages/agents.md b/packages/vercel-messages/agents.md index 8b03372f..3e15a844 100644 --- a/packages/vercel-messages/agents.md +++ b/packages/vercel-messages/agents.md @@ -15,5 +15,3 @@ This Tier 1 package provides the wildcard `("*", "messages")` handler. `ToolSpec.params`. - `ai.stream` must remain inside `async with`, including early consumer exit. - Do not add a provider-native client or enable duplicate runtime telemetry. - -`TESTING.md` §1.x and Appendix A.14 are authoritative. diff --git a/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py b/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py index 53e310c7..4671c40e 100644 --- a/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py +++ b/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py @@ -50,7 +50,7 @@ #: :func:`_renamed_parameters`. Every other key is dropped. The handler sets ``model``, #: ``messages``, ``tools`` and ``output_type`` itself, and runs the tool loop. #: -#: This is the Vercel AI SDK list from TESTING.md section 1.12 as the Python ``ai`` library can +#: This is the cross-SDK Vercel AI SDK list as the Python ``ai`` library can #: send it. ``stop_sequences`` is on the JS list but the Python library has no field for it. #: ``parallel_tool_calls``, ``max_tool_calls``, ``service_tier`` and ``prompt_cache_key`` are #: Python-only: the library takes them as typed request params, while the JS AI SDK can reach them diff --git a/packages/vercel-messages/tests/test_sdk_usage.py b/packages/vercel-messages/tests/test_sdk_usage.py index 6f96bd69..43526670 100644 --- a/packages/vercel-messages/tests/test_sdk_usage.py +++ b/packages/vercel-messages/tests/test_sdk_usage.py @@ -1,4 +1,4 @@ -"""Tests for TESTING.md §3.27 — vercel-messages.""" +"""Tests for SDK helper usage events — vercel-messages.""" from typing import Any from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/forwarding_spec.py b/tests/forwarding_spec.py index 3e79a890..027b6a27 100644 --- a/tests/forwarding_spec.py +++ b/tests/forwarding_spec.py @@ -1,4 +1,4 @@ -"""The cross-SDK forwarded-key lists (TESTING.md section 1.12), shared by every package's tests. +"""The cross-SDK forwarded-key lists, shared by every package's tests. Each list names the ``model.parameters`` keys a handler forwards, in the config's own spelling, so a key the handler renames (``effort``, ``max_tokens``, ``text``) is listed under the name the config diff --git a/tests/never_forwarded.py b/tests/never_forwarded.py index 429ae9ec..f2f144f5 100644 --- a/tests/never_forwarded.py +++ b/tests/never_forwarded.py @@ -10,7 +10,7 @@ from __future__ import annotations #: Credentials, endpoints and connection settings, raw request injection, remote tools, the Claude -#: Agents host-process settings, and the categories no cross-SDK allowlist (TESTING.md section 1.12) +#: Agents host-process settings, and the categories no cross-SDK allowlist #: may hold: data location and retention, server-side state, identity and attribution, runtime wiring and #: API-shape switches, prompt content beyond the instructions, and safety configuration. NEVER_FORWARDED_KEYS = frozenset( From d1cde8a0043023ab4d64968568f132253cac9bda Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Wed, 28 Oct 2026 15:47:52 -0700 Subject: [PATCH 5/5] fix(AIC-3568): drop the last three private-spec section references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The search for §N missed the spelled-out "section 1.12" in forwarding_spec.py and the two vercel _renamed_parameters docstrings. Co-Authored-By: Claude Opus 5.5 --- .../launchdarkly_ai_vercel_agents/handler.py | 2 +- .../handler.py | 2 +- tests/forwarding_spec.py | 2 +- uv.lock | 28 +++++++++---------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py b/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py index 2ea72ea8..1e4037ca 100644 --- a/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py +++ b/packages/vercel-agents/src/launchdarkly_ai_vercel_agents/handler.py @@ -120,7 +120,7 @@ def _renamed_parameters(params: dict[str, Any]) -> dict[str, Any]: - """Applies the section 1.12 renames: ``max_output_tokens`` wins over ``max_completion_tokens``, + """Applies the cross-SDK renames: ``max_output_tokens`` wins over ``max_completion_tokens``, which wins over ``max_tokens``, and an explicit ``reasoning`` wins over ``reasoning_effort``.""" for alias in ("max_completion_tokens", "max_tokens"): value = params.pop(alias, None) diff --git a/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py b/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py index 4671c40e..88cb7185 100644 --- a/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py +++ b/packages/vercel-messages/src/launchdarkly_ai_vercel_messages/handler.py @@ -122,7 +122,7 @@ def _renamed_parameters(params: dict[str, Any]) -> dict[str, Any]: - """Applies the section 1.12 renames: ``max_output_tokens`` wins over ``max_completion_tokens``, + """Applies the cross-SDK renames: ``max_output_tokens`` wins over ``max_completion_tokens``, which wins over ``max_tokens``, and an explicit ``reasoning`` wins over ``reasoning_effort``.""" for alias in ("max_completion_tokens", "max_tokens"): value = params.pop(alias, None) diff --git a/tests/forwarding_spec.py b/tests/forwarding_spec.py index 027b6a27..9a1f64a1 100644 --- a/tests/forwarding_spec.py +++ b/tests/forwarding_spec.py @@ -2,7 +2,7 @@ Each list names the ``model.parameters`` keys a handler forwards, in the config's own spelling, so a key the handler renames (``effort``, ``max_tokens``, ``text``) is listed under the name the config -uses. A key the Python library cannot accept is marked not supported in Python in section 1.12 and +uses. A key the Python library cannot accept is marked not supported in Python on the cross-SDK list and is absent here, and the JS SDK's lists differ the same way. Each package's tests probe its real call sites with :func:`probe_forwarded_keys` and assert the result equals its list here exactly. """ diff --git a/uv.lock b/uv.lock index ec6a31c7..6407d96f 100644 --- a/uv.lock +++ b/uv.lock @@ -1322,7 +1322,7 @@ wheels = [ [[package]] name = "launchdarkly-ai-claude-agents" -version = "0.2.6" +version = "0.3.0" source = { editable = "packages/claude-agents" } dependencies = [ { name = "anthropic" }, @@ -1341,7 +1341,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-claude-messages" -version = "0.2.5" +version = "0.3.0" source = { editable = "packages/claude-messages" } dependencies = [ { name = "anthropic" }, @@ -1358,7 +1358,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-langchain-agents" -version = "0.2.6" +version = "0.3.0" source = { editable = "packages/langchain-agents" } dependencies = [ { name = "langchain-core" }, @@ -1377,7 +1377,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-langchain-messages" -version = "0.2.5" +version = "0.3.0" source = { editable = "packages/langchain-messages" } dependencies = [ { name = "langchain-core" }, @@ -1394,7 +1394,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-litellm-agents" -version = "0.1.1" +version = "0.2.0" source = { editable = "packages/litellm-agents" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1411,7 +1411,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-litellm-messages" -version = "0.1.1" +version = "0.2.0" source = { editable = "packages/litellm-messages" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1428,7 +1428,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-openai-agents" -version = "0.2.6" +version = "0.3.0" source = { editable = "packages/openai-agents" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1447,7 +1447,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-openai-messages" -version = "0.2.5" +version = "0.3.0" source = { editable = "packages/openai-messages" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1464,7 +1464,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-opencode-agents" -version = "0.2.0" +version = "0.3.0" source = { editable = "packages/opencode-agents" } dependencies = [ { name = "httpx" }, @@ -1481,7 +1481,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-python" -version = "0.3.0" +version = "0.4.0" source = { editable = "packages/ai" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1501,7 +1501,7 @@ provides-extras = ["otel"] [[package]] name = "launchdarkly-ai-server" -version = "0.4.0" +version = "0.5.0" source = { editable = "packages/client" } dependencies = [ { name = "opentelemetry-api" }, @@ -1523,7 +1523,7 @@ provides-extras = ["otel"] [[package]] name = "launchdarkly-ai-typesafe" -version = "0.1.1" +version = "0.2.0" source = { editable = "packages/typesafe" } dependencies = [ { name = "launchdarkly-ai-server" }, @@ -1540,7 +1540,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-vercel-agents" -version = "0.1.1" +version = "0.2.0" source = { editable = "packages/vercel-agents" } dependencies = [ { name = "ai" }, @@ -1557,7 +1557,7 @@ requires-dist = [ [[package]] name = "launchdarkly-ai-vercel-messages" -version = "0.1.1" +version = "0.2.0" source = { editable = "packages/vercel-messages" } dependencies = [ { name = "ai" },