From d2cf6ba6df0c6f795fea6346c5b93dc8d8e88adc Mon Sep 17 00:00:00 2001 From: Jeff Dupont Date: Tue, 27 Oct 2026 09:34:55 -0700 Subject: [PATCH] feat(AIC-3685)!: make the internal provider helpers private build_prompt, build_tool_mcp and partition_tools (launchdarkly_ai_claude_agents) and build_output_type (launchdarkly_ai_openai_agents) are no longer exported. They are renamed with a leading underscore where they are defined. No deprecated aliases: the packages are 0.x, so this ships as a minor bump. Co-Authored-By: Claude Opus 5.5 --- packages/claude-agents/agents.md | 6 +- .../launchdarkly_ai_claude_agents.txt | 3 - .../launchdarkly_ai_claude_agents/__init__.py | 6 -- .../launchdarkly_ai_claude_agents/handler.py | 20 +++--- .../native_graph.py | 12 ++-- packages/claude-agents/tests/test_handler.py | 70 +++++++++---------- .../claude-agents/tests/test_native_graph.py | 6 +- .../launchdarkly_ai_openai_agents.txt | 1 - .../launchdarkly_ai_openai_agents/__init__.py | 2 - .../launchdarkly_ai_openai_agents/handler.py | 2 +- .../launchdarkly_ai_openai_agents/utils.py | 2 +- packages/openai-agents/tests/test_handler.py | 12 ++-- packages/openai-agents/tests/test_utils.py | 20 +++--- 13 files changed, 75 insertions(+), 87 deletions(-) diff --git a/packages/claude-agents/agents.md b/packages/claude-agents/agents.md index fa16260d..82caf6f6 100644 --- a/packages/claude-agents/agents.md +++ b/packages/claude-agents/agents.md @@ -74,7 +74,7 @@ ClaudeNotebookEdit: NativeTool ## Implementation Details -### 1. Prompt Construction (`build_prompt`) +### 1. Prompt Construction (`_build_prompt`) The handler uses a flat `prompt` + optional `system_prompt` shape that the `claude_agent_sdk.query()` call accepts: @@ -94,7 +94,7 @@ neither? Note: the `messages` path collapses conversation history into a single flat string — roles are not individually structured. This is a limitation of the agent SDK's `query()` interface. -### 2. Tool Wiring (`build_tool_mcp`) +### 2. Tool Wiring (`_build_tool_mcp`) Tools are delivered via an in-process MCP server, not as raw JSON schema defs. The pipeline: @@ -238,4 +238,4 @@ Span names and attributes are described in [Implementation Details → Telemetry - **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. - **`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. +- **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/api-report/launchdarkly_ai_claude_agents.txt b/packages/claude-agents/api-report/launchdarkly_ai_claude_agents.txt index 3dbfa346..a77540a1 100644 --- a/packages/claude-agents/api-report/launchdarkly_ai_claude_agents.txt +++ b/packages/claude-agents/api-report/launchdarkly_ai_claude_agents.txt @@ -1,6 +1,4 @@ # Public API of launchdarkly_ai_claude_agents. Generated by `make api-report`; do not edit. -build_prompt(config: AiConfigRep, user_input: str | None, variables: dict[str, Any], history: list[dict[str, Any]] | None = None) -> tuple[str, str | None] -build_tool_mcp(config_tools: dict[str, Any], handlers: dict[str, Any]) -> Any [async] claude_agents(config_key: str, user_input: str, context: LDContext, *, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, **kwargs: Any) -> Any claude_graph(key: str, *, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, **options: Any) -> Any ClaudeBash = NativeTool('Bash') @@ -15,5 +13,4 @@ ClaudeWebSearch = NativeTool('WebSearch') ClaudeWrite = NativeTool('Write') create_claude_agents_handler(*, capture_content: bool | None = None, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None) -> ProviderHandler create_claude_agents_skills_handler(*, capture_content: bool | None = None, cwd: str | os.PathLike[str] | None = None, setting_sources: list[SettingSource] | None = None, skills_temp_dir: str | os.PathLike[str] | None = None) -> ProviderHandler -partition_tools(config_tools: dict[str, Any] | None, tool_handlers: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], list[str]] to_claude_agents(def_promise: Any, opts: dict[str, Any] | None = None) -> Any diff --git a/packages/claude-agents/src/launchdarkly_ai_claude_agents/__init__.py b/packages/claude-agents/src/launchdarkly_ai_claude_agents/__init__.py index d3048218..e4917673 100644 --- a/packages/claude-agents/src/launchdarkly_ai_claude_agents/__init__.py +++ b/packages/claude-agents/src/launchdarkly_ai_claude_agents/__init__.py @@ -21,11 +21,8 @@ ) from .graph import claude_graph from .handler import ( - build_prompt, - build_tool_mcp, claude_agents, create_claude_agents_handler, - partition_tools, ) from .native_graph import to_claude_agents from .skills import create_claude_agents_skills_handler @@ -41,13 +38,10 @@ "ClaudeWebFetch", "ClaudeWebSearch", "ClaudeWrite", - "build_prompt", - "build_tool_mcp", "claude_agents", "claude_graph", "create_claude_agents_handler", "create_claude_agents_skills_handler", - "partition_tools", "to_claude_agents", ] 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 7dd92792..664b1dbf 100644 --- a/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py +++ b/packages/claude-agents/src/launchdarkly_ai_claude_agents/handler.py @@ -174,7 +174,7 @@ # --------------------------------------------------------------------------- -async def build_tool_mcp( +async def _build_tool_mcp( config_tools: dict[str, Any], handlers: dict[str, Any], ) -> Any: @@ -211,7 +211,7 @@ async def _execute(args: Any, _name: str = tool_name) -> dict[str, Any]: return create_server(name=TOOL_MCP_NAME, version="1.0.0", tools=tool_objs) -def partition_tools( +def _partition_tools( config_tools: dict[str, Any] | None, tool_handlers: dict[str, Any], ) -> tuple[dict[str, Any], dict[str, Any], list[str]]: @@ -242,7 +242,7 @@ def partition_tools( def _native_tool_aliases(tool_handlers: dict[str, Any]) -> dict[str, str]: """AI Config key → provider tool name, for the config's native tools only. - ``partition_tools``'s ``native_tool_map`` is keyed the other way round, by provider name, which + ``_partition_tools``'s ``native_tool_map`` is keyed the other way round, by provider name, which loses the AI Config key, and the key is what a config's declared schema is filed under. :class:`~.spans.ToolCatalog` needs both ends to report one tool once, under the name the model saw, with the schema the config gave it. @@ -438,7 +438,7 @@ def cancel_open_spans() -> None: # --------------------------------------------------------------------------- -def build_prompt( +def _build_prompt( config: AiConfigRep, user_input: str | None, variables: dict[str, Any], @@ -794,7 +794,7 @@ async def _call_impl( # truthily, and this handler's own suite is built on mock spans in places. open_root_span: Any = span - prompt, system_prompt = build_prompt(config, user_input, vs, history) + prompt, system_prompt = _build_prompt(config, user_input, vs, history) query_prompt = build_query_prompt(config, user_input, vs, history, prompt) if config.get("outputFormat"): schema_instr = f"Respond with valid JSON matching this schema:\n{json.dumps(config['outputFormat'])}" @@ -803,7 +803,7 @@ async def _call_impl( ) opening = _opening_of(prompt, system_prompt) - native_tool_map, user_config_tools, native_tool_names = partition_tools( + native_tool_map, user_config_tools, native_tool_names = _partition_tools( config.get("tools"), th ) catalog = ToolCatalog(config.get("tools"), _native_tool_aliases(th)) @@ -831,7 +831,7 @@ async def _call_impl( prepared = await _prepare(config) if _prepare is not None else None extra_builtins = prepared.extra_builtins if prepared is not None else [] tool_mcp = ( - await build_tool_mcp(user_config_tools, th) + await _build_tool_mcp(user_config_tools, th) if user_config_tools else None ) @@ -1055,11 +1055,11 @@ async def _stream_gen( span = start_root_span(config, variables) parent = parent_context_of(span) - prompt, system_prompt = build_prompt(config, user_input, variables, history) + prompt, system_prompt = _build_prompt(config, user_input, variables, history) query_prompt = build_query_prompt(config, user_input, variables, history, prompt) opening = _opening_of(prompt, system_prompt) - native_tool_map, user_config_tools, native_tool_names = partition_tools( + native_tool_map, user_config_tools, native_tool_names = _partition_tools( config.get("tools"), tool_handlers ) catalog = ToolCatalog(config.get("tools"), _native_tool_aliases(tool_handlers)) @@ -1094,7 +1094,7 @@ async def _stream_gen( prepared = await prepare(config) if prepare is not None else None extra_builtins = prepared.extra_builtins if prepared is not None else [] tool_mcp = ( - await build_tool_mcp(user_config_tools, tool_handlers) + await _build_tool_mcp(user_config_tools, tool_handlers) if user_config_tools else None ) diff --git a/packages/claude-agents/src/launchdarkly_ai_claude_agents/native_graph.py b/packages/claude-agents/src/launchdarkly_ai_claude_agents/native_graph.py index a1c811f2..3a675a44 100644 --- a/packages/claude-agents/src/launchdarkly_ai_claude_agents/native_graph.py +++ b/packages/claude-agents/src/launchdarkly_ai_claude_agents/native_graph.py @@ -37,13 +37,13 @@ from launchdarkly_ai_claude_agents.handler import ( _build_hooks, + _build_prompt, + _build_tool_mcp, _host_query_options, _options_parameters, + _partition_tools, _raise_if_run_failed, - build_prompt, build_query_prompt, - build_tool_mcp, - partition_tools, ) TOOL_MCP_NAME = "tool-mcp" @@ -129,16 +129,16 @@ async def _run_query( track_data = make_track_data(node, graph_key, run_id) wrapped = _wrap_native_tools(tool_handlers, ld_context, track_data) - prompt, system_prompt = build_prompt(node.config, input_text, variables) + prompt, system_prompt = _build_prompt(node.config, input_text, variables) query_prompt = build_query_prompt( node.config, input_text, variables, history, prompt ) - native_tool_map, user_config_tools, native_tool_names = partition_tools( + native_tool_map, user_config_tools, native_tool_names = _partition_tools( node.config.get("tools"), wrapped ) tool_mcp = ( - await build_tool_mcp(user_config_tools, wrapped) if user_config_tools else None + await _build_tool_mcp(user_config_tools, wrapped) if user_config_tools else None ) child_mcp = ( create_server( diff --git a/packages/claude-agents/tests/test_handler.py b/packages/claude-agents/tests/test_handler.py index 0878f3f7..ea8838ca 100644 --- a/packages/claude-agents/tests/test_handler.py +++ b/packages/claude-agents/tests/test_handler.py @@ -38,10 +38,10 @@ import launchdarkly_ai_claude_agents.handler as handler_mod from launchdarkly_ai_claude_agents.handler import ( + _build_prompt, _native_tool_aliases, - build_prompt, + _partition_tools, create_claude_agents_handler, - partition_tools, ) from launchdarkly_ai_server import ConversationIdSpanProcessor, conversation_id from tests.forwarding_spec import ( @@ -274,18 +274,18 @@ def test_multiple_calls_return_independent_instances(self) -> None: class TestPromptConstruction: def test_instructions_become_system_prompt(self) -> None: - prompt, system = build_prompt(BASE_CONFIG, "hi", {}) + prompt, system = _build_prompt(BASE_CONFIG, "hi", {}) assert prompt == "hi" assert system == "You are helpful." def test_no_instructions_no_system_prompt(self) -> None: - prompt, system = build_prompt({"model": {"name": "m"}}, "hi", {}) + prompt, system = _build_prompt({"model": {"name": "m"}}, "hi", {}) assert system is None assert prompt == "hi" def test_variable_substitution(self) -> None: cfg = {**BASE_CONFIG, "instructions": "Hello {{name}}."} - _, system = build_prompt(cfg, "hi", {"name": "Ada"}) + _, system = _build_prompt(cfg, "hi", {"name": "Ada"}) assert system == "Hello Ada." # --- restored from the pre-rewrite file (not telemetry); byte-for-byte, only @@ -293,18 +293,18 @@ def test_variable_substitution(self) -> None: def test_path_a_instructions(self) -> None: config = _make_config(instructions="You are a helper.") - prompt, system = build_prompt(config, "hi", {}) + prompt, system = _build_prompt(config, "hi", {}) assert prompt == "hi" assert system == "You are a helper." def test_path_a_variable_substitution(self) -> None: config = _make_config(instructions="Hello {{name}}!") - _, system = build_prompt(config, "hi", {"name": "Alice"}) + _, system = _build_prompt(config, "hi", {"name": "Alice"}) assert system == "Hello Alice!" def test_path_a_unresolved_placeholder_preserved(self) -> None: config = _make_config(instructions="Hello {{name}}!") - _, system = build_prompt(config, "hi", {}) + _, system = _build_prompt(config, "hi", {}) assert "{{name}}" in (system or "") def test_path_b_messages_system_extracted(self) -> None: @@ -314,7 +314,7 @@ def test_path_b_messages_system_extracted(self) -> None: {"role": "user", "content": "hey"}, ] ) - prompt, system = build_prompt(config, "question", {}) + prompt, system = _build_prompt(config, "question", {}) assert system == "Be concise." assert "hey" in prompt assert "question" in prompt @@ -325,7 +325,7 @@ def test_path_b_variable_substitution_in_messages(self) -> None: {"role": "user", "content": "my name is {{name}}"}, ] ) - prompt, _ = build_prompt(config, "q", {"name": "Alice"}) + prompt, _ = _build_prompt(config, "q", {"name": "Alice"}) assert "Alice" in prompt def test_path_b_user_input_appended_as_final_turn(self) -> None: @@ -334,12 +334,12 @@ def test_path_b_user_input_appended_as_final_turn(self) -> None: {"role": "user", "content": "old message"}, ] ) - prompt, _ = build_prompt(config, "new input", {}) + prompt, _ = _build_prompt(config, "new input", {}) assert "new input" in prompt def test_path_c_empty_user_input_no_throw(self) -> None: config = _make_config(instructions="be helpful") - prompt, system = build_prompt(config, "", {}) + prompt, system = _build_prompt(config, "", {}) assert prompt == "" assert system is not None @@ -348,7 +348,7 @@ def test_path_c_instructions_takes_priority_over_messages(self) -> None: instructions="Use instructions.", messages=[{"role": "system", "content": "Use messages."}], ) - _prompt, system = build_prompt(config, "q", {}) + _prompt, system = _build_prompt(config, "q", {}) assert system == "Use instructions." def test_messages_mode_extracts_system_and_flattens_history_into_prompt( @@ -361,30 +361,30 @@ def test_messages_mode_extracts_system_and_flattens_history_into_prompt( {"role": "user", "content": "context line"}, ], } - prompt, system = build_prompt(cfg, "final question", {}) + prompt, system = _build_prompt(cfg, "final question", {}) assert system == "Be terse." assert "context line" in prompt assert prompt.endswith("final question") def test_history_not_appended_to_system_prompt(self) -> None: history = [{"role": "user", "content": "earlier"}] - _, system = build_prompt(BASE_CONFIG, "hi", {}, history=history) + _, system = _build_prompt(BASE_CONFIG, "hi", {}, history=history) assert system == "You are helpful." assert "You are helpful." in (system or "") def test_no_user_input_defaults_to_empty_string(self) -> None: - prompt, _ = build_prompt(BASE_CONFIG, None, {}) + prompt, _ = _build_prompt(BASE_CONFIG, None, {}) assert prompt == "" # --------------------------------------------------------------------------- -# partition_tools / native tool aliases +# _partition_tools / native tool aliases # --------------------------------------------------------------------------- class TestPartitionTools: def test_user_tool_goes_to_mcp_bucket(self) -> None: - native_map, user_tools, native_names = partition_tools( + native_map, user_tools, native_names = _partition_tools( TOOL_CONFIG["tools"], {"search": lambda _: "r"} ) assert native_map == {} @@ -399,7 +399,7 @@ def test_native_tool_goes_to_native_bucket(self) -> None: # stashed under NATIVE_TOOL_KEY, not the NativeTool instance itself. stub = lambda: None # noqa: E731 setattr(stub, NATIVE_TOOL_KEY, ClaudeWebSearch) - _native_map, user_tools, native_names = partition_tools( + _native_map, user_tools, native_names = _partition_tools( {"webSearch": {"name": "webSearch"}}, {"webSearch": stub} ) assert native_names == ["WebSearch"] @@ -417,7 +417,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). -# ``partition_tools`` kept its 3-tuple return, so these run byte-for-byte. +# ``_partition_tools`` kept its 3-tuple return, so these run byte-for-byte. # --------------------------------------------------------------------------- @@ -427,25 +427,25 @@ def test_all_fields_forwarded(self) -> None: "my-tool": {"description": "does stuff", "parameters": {"type": "object"}} } handlers = {"my-tool": AsyncMock(return_value="ok")} - _, user_tools, _ = partition_tools(config_tools, handlers) + _, user_tools, _ = _partition_tools(config_tools, handlers) assert "my-tool" in user_tools def test_multiple_tools_all_included(self) -> None: config_tools = {"tool-a": {}, "tool-b": {}} handlers = {"tool-a": AsyncMock(), "tool-b": AsyncMock()} - _, user_tools, _ = partition_tools(config_tools, handlers) + _, user_tools, _ = _partition_tools(config_tools, handlers) assert "tool-a" in user_tools assert "tool-b" in user_tools def test_empty_tools_no_tools_sent(self) -> None: - _, user_tools, native_names = partition_tools({}, {}) + _, user_tools, native_names = _partition_tools({}, {}) assert not user_tools assert not native_names # --------------------------------------------------------------------------- -# §1.4 Tool execution loop (via build_tool_mcp) — restored from the pre-rewrite -# file. ``build_tool_mcp`` kept its lazy ``importlib.import_module`` pattern +# §1.4 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. # --------------------------------------------------------------------------- @@ -464,7 +464,7 @@ def __init__( def _patch_query_for_tool_mcp(messages: list[Any]) -> Any: """Patches ``claude_agent_sdk`` for the one restored test that exercises - ``build_tool_mcp`` directly, which still resolves the SDK lazily. + ``_build_tool_mcp`` directly, which still resolves the SDK lazily. """ async def _query(**kwargs: Any) -> AsyncIterator[Any]: @@ -486,14 +486,14 @@ async def _query(**kwargs: Any) -> AsyncIterator[Any]: class TestToolExecutionLoop: async def test_tool_not_found_throws(self) -> None: - from launchdarkly_ai_claude_agents.handler import build_tool_mcp + from launchdarkly_ai_claude_agents.handler import _build_tool_mcp config_tools = {"my-tool": {"description": "d", "parameters": {}}} handlers: dict[str, Any] = {} # no handler registered - # The execute closure inside build_tool_mcp raises if handler missing + # The execute closure inside _build_tool_mcp raises if handler missing with _patch_query_for_tool_mcp([_MockResultMessageForToolMcp()]): - mcp = await build_tool_mcp(config_tools, handlers) + mcp = await _build_tool_mcp(config_tools, handlers) # Directly call the stored execute fn for t in getattr(mcp, "tools", None) or []: fn = getattr(t, "_fn", getattr(t, "fn", None)) @@ -2043,8 +2043,8 @@ async def _spy_query(**kwargs: Any) -> AsyncIterator[Any]: # --------------------------------------------------------------------------- -# History parameter (build_prompt) — restored from the pre-rewrite file -# (not telemetry); byte-for-byte, calling build_prompt directly. +# History parameter (_build_prompt) — restored from the pre-rewrite file +# (not telemetry); byte-for-byte, calling _build_prompt directly. # --------------------------------------------------------------------------- @@ -2056,21 +2056,21 @@ class TestHistory: def test_history_not_stuffed_into_system_prompt(self) -> None: config = _make_config(instructions="Be concise.") - _, system = build_prompt(config, "hi", {}, self.SAMPLE_HISTORY) + _, system = _build_prompt(config, "hi", {}, self.SAMPLE_HISTORY) assert system is not None assert "Be concise." in system assert "Conversation History:" not in system def test_empty_history_treated_like_no_history(self) -> None: config = _make_config(instructions="Be concise.") - _, system_with_empty = build_prompt(config, "hi", {}, []) - _, system_without = build_prompt(config, "hi", {}) + _, system_with_empty = _build_prompt(config, "hi", {}, []) + _, system_without = _build_prompt(config, "hi", {}) assert system_with_empty == system_without assert "Conversation History:" not in (system_with_empty or "") def test_history_without_instructions_keeps_system_none(self) -> None: config = _make_config() - _, system = build_prompt(config, "hi", {}, self.SAMPLE_HISTORY) + _, system = _build_prompt(config, "hi", {}, self.SAMPLE_HISTORY) assert system is None or "Conversation History:" not in system diff --git a/packages/claude-agents/tests/test_native_graph.py b/packages/claude-agents/tests/test_native_graph.py index 06f1ec3f..e3dc332e 100644 --- a/packages/claude-agents/tests/test_native_graph.py +++ b/packages/claude-agents/tests/test_native_graph.py @@ -445,7 +445,7 @@ async def _my_handler(args: Any) -> str: opts={"tool_handlers": {"my-tool": _my_handler}}, ).invoke("input") - # create_sdk_mcp_server is called from build_tool_mcp for user config tools + # create_sdk_mcp_server is called from _build_tool_mcp for user config tools assert mock_sdk.create_sdk_mcp_server.called @pytest.mark.asyncio @@ -1247,7 +1247,7 @@ async def test_subagent_setup_error_fails_the_graph_span(self) -> None: @pytest.mark.asyncio async def test_build_tool_mcp_throws_when_tool_not_in_handlers(self) -> None: - from launchdarkly_ai_claude_agents.handler import build_tool_mcp + from launchdarkly_ai_claude_agents.handler import _build_tool_mcp mock_sdk = MagicMock() mock_sdk.tool = MagicMock(side_effect=lambda name, desc, schema: lambda fn: fn) @@ -1261,7 +1261,7 @@ async def test_build_tool_mcp_throws_when_tool_not_in_handlers(self) -> None: ): config_tools = {"missing-tool": {"description": "d", "parameters": {}}} handlers: dict[str, Any] = {} - await build_tool_mcp(config_tools, handlers) + await _build_tool_mcp(config_tools, handlers) # The actual error is raised when the fn is *called*, not when mcp is built diff --git a/packages/openai-agents/api-report/launchdarkly_ai_openai_agents.txt b/packages/openai-agents/api-report/launchdarkly_ai_openai_agents.txt index 6b977819..9ca624cf 100644 --- a/packages/openai-agents/api-report/launchdarkly_ai_openai_agents.txt +++ b/packages/openai-agents/api-report/launchdarkly_ai_openai_agents.txt @@ -1,5 +1,4 @@ # Public API of launchdarkly_ai_openai_agents. Generated by `make api-report`; do not edit. -build_output_type(output_format: dict[str, Any] | None) -> dict[str, Any] | None create_openai_agent_handler(*, capture_content: bool | None = None) -> ProviderHandler openai_agents(config_key: str, user_input: str, context: LDContext, **kwargs: Any) -> Any openai_graph(key: str, **options: Any) -> Any diff --git a/packages/openai-agents/src/launchdarkly_ai_openai_agents/__init__.py b/packages/openai-agents/src/launchdarkly_ai_openai_agents/__init__.py index 0475189c..67e87231 100644 --- a/packages/openai-agents/src/launchdarkly_ai_openai_agents/__init__.py +++ b/packages/openai-agents/src/launchdarkly_ai_openai_agents/__init__.py @@ -10,10 +10,8 @@ from .graph import openai_graph from .handler import create_openai_agent_handler, openai_agents from .native_graph import to_openai_agents -from .utils import build_output_type __all__ = [ - "build_output_type", "create_openai_agent_handler", "openai_agents", "openai_graph", 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 b0fe0942..7a8a0842 100644 --- a/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py +++ b/packages/openai-agents/src/launchdarkly_ai_openai_agents/handler.py @@ -341,7 +341,7 @@ def _build_agent_and_prompt( # `outputFormat` is not wired into `Agent(output_type=...)` here, matching this handler's # pre-existing behaviour (and unlike the TypeScript handler, which does wire it): the Python # Agents SDK requires a concrete Python type for `output_type`, not a raw JSON Schema dict (see - # `utils.build_output_type`'s docstring). Fixing that gap is a "what is sent to the model" + # `utils._build_output_type`'s docstring). Fixing that gap is a "what is sent to the model" # change, not a telemetry one, so it is left alone. `_call_impl` still returns the parsed # `final_output` object as-is when `outputFormat` is configured, matching the pre-existing # return-shape contract. diff --git a/packages/openai-agents/src/launchdarkly_ai_openai_agents/utils.py b/packages/openai-agents/src/launchdarkly_ai_openai_agents/utils.py index f88e04b9..ee519cee 100644 --- a/packages/openai-agents/src/launchdarkly_ai_openai_agents/utils.py +++ b/packages/openai-agents/src/launchdarkly_ai_openai_agents/utils.py @@ -8,7 +8,7 @@ from typing import Any -def build_output_type(output_format: dict[str, Any] | None) -> dict[str, Any] | None: +def _build_output_type(output_format: dict[str, Any] | None) -> dict[str, Any] | None: """ Converts the ``outputFormat`` from an LD AI config into the schema envelope the OpenAI Agents SDK expects as ``output_type`` on an ``Agent``. diff --git a/packages/openai-agents/tests/test_handler.py b/packages/openai-agents/tests/test_handler.py index 9a0d6ced..a250bec2 100644 --- a/packages/openai-agents/tests/test_handler.py +++ b/packages/openai-agents/tests/test_handler.py @@ -26,7 +26,7 @@ create_openai_agent_handler, openai_agents, ) -from launchdarkly_ai_openai_agents.utils import build_output_type +from launchdarkly_ai_openai_agents.utils import _build_output_type from tests.forwarding_spec import ( OPENAI_AGENTS, candidate_keys, @@ -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) +# §1.9 Output format (_build_output_type) # --------------------------------------------------------------------------- @@ -1839,22 +1839,22 @@ async def run(agent: Any, prompt: str, hooks: Any = None, **kw: Any) -> Any: class TestOutputFormat: def test_absent_output_format_no_change(self) -> None: - assert build_output_type(None) is None + assert _build_output_type(None) is None def test_output_format_sets_output_type_on_agent(self) -> None: schema = {"type": "object", "properties": {"name": {"type": "string"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["type"] == "json_schema" assert "schema" in result def test_absent_output_format_returns_plain_string(self) -> None: - result = build_output_type({}) + result = _build_output_type({}) assert result is None def test_output_format_returns_parsed_object(self) -> None: schema = {"type": "object", "properties": {"count": {"type": "integer"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["schema"]["properties"]["count"]["type"] == "integer" diff --git a/packages/openai-agents/tests/test_utils.py b/packages/openai-agents/tests/test_utils.py index 2586ddec..4e9ff1ec 100644 --- a/packages/openai-agents/tests/test_utils.py +++ b/packages/openai-agents/tests/test_utils.py @@ -1,21 +1,21 @@ """ -Tests for §2.x.5 build_output_type utility. +Tests for §2.x.5 _build_output_type utility. Reference: TESTING.md §2.x.5 """ -from launchdarkly_ai_openai_agents.utils import build_output_type +from launchdarkly_ai_openai_agents.utils import _build_output_type class TestBuildOutputType: def test_returns_none_when_output_format_none(self) -> None: - assert build_output_type(None) is None + assert _build_output_type(None) is None def test_returns_none_when_output_format_empty_object(self) -> None: - assert build_output_type({}) is None + assert _build_output_type({}) is None def test_wraps_schema_in_correct_envelope(self) -> None: schema = {"type": "object", "properties": {"x": {"type": "string"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["type"] == "json_schema" assert "schema" in result @@ -23,19 +23,19 @@ def test_wraps_schema_in_correct_envelope(self) -> None: def test_ensures_type_object_in_schema(self) -> None: schema = {"properties": {"name": {"type": "string"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["schema"]["type"] == "object" def test_preserves_existing_type_object(self) -> None: schema = {"type": "object", "properties": {"age": {"type": "integer"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["schema"]["type"] == "object" def test_name_is_output_and_strict_is_false(self) -> None: schema = {"type": "object", "properties": {"v": {"type": "number"}}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert result["name"] == "output" assert result["strict"] is False @@ -45,7 +45,7 @@ def test_required_populated_from_all_property_keys(self) -> None: "type": "object", "properties": {"a": {"type": "string"}, "b": {"type": "integer"}}, } - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None required = result["schema"]["required"] assert "a" in required @@ -60,7 +60,7 @@ def test_required_omitted_when_properties_empty(self) -> None: The condition must be ``if required`` (falsy check) not ``if required is not None``. """ schema = {"type": "object", "properties": {}} - result = build_output_type(schema) + result = _build_output_type(schema) assert result is not None assert "required" not in result["schema"], ( "Schema must not include 'required: []' when properties is empty. "