diff --git a/CLAUDE.md b/CLAUDE.md index 12403ae..b2dce36 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ Async SQLAlchemy + aiosqlite. `MemoryStore`: schema init (+ lightweight SQLite ` - **`ChatClient`** Protocol for `chat_completions` (agent history stays chat-shaped). - **OpenAI Responses integration**: `ResponsesChatClient` adapts platform `OpenAIClient.responses` to `ChatClient`; selected automatically for `OpenAIProvider` in `create_client`. Compat/DeepSeek stay on chat completions. -- **Provider-side tools**: `OpenAIProvider.provider_tools` (list of dicts, e.g. `{type="web_search"}`) merged into Responses `tools` alongside local function tools; never executed by `ToolRegistry`. +- **Provider-side tools**: `OpenAIProvider.provider_tools` (list of dicts; default `[{type="web_search"}]` when omitted; `[]` disables) merged into Responses `tools` alongside local function tools; never executed by `ToolRegistry`. - **`@tool` / `ToolRegistry`**: decorator infers JSON Schema from type hints; execute tools by name. - **`run_chat_loop`**: multi-round tool loop; default **streaming** text deltas + stream tool-call merge; parallel tools; tool-result char budget; soft context compact on request (**API-calibrated** after first usage when available); cooperative cancel points; optional `on_limit`. - **`ChatAgent`**: optional `MemoryStore` (user message persisted immediately; **completed tool batches checkpointed** mid-turn; unfinished assistant suffix rolled back on failure); `stream`; system prompt; `retry()` continues incomplete turns (user-only **or** after committed tools — does not re-run those tools). diff --git a/doc/plyngent.example.toml b/doc/plyngent.example.toml index fa5926f..0873dd8 100644 --- a/doc/plyngent.example.toml +++ b/doc/plyngent.example.toml @@ -39,6 +39,7 @@ access_key_or_token = "sk-replace-me" # access_key_or_token = "sk-replace-me" # url = "https://api.openai.com/v1" # # Hosted/provider-side tools (OpenAI runs them; not local ToolRegistry). +# # Default if omitted: [{ type = "web_search" }]. Use [] to disable. # # Examples: web_search, file_search (+ vector_store_ids), code_interpreter, mcp. # provider_tools = [ # { type = "web_search" }, diff --git a/src/plyngent/config/models.py b/src/plyngent/config/models.py index 48e84f3..cdc89c5 100644 --- a/src/plyngent/config/models.py +++ b/src/plyngent/config/models.py @@ -62,6 +62,11 @@ def _default_openai_models() -> dict[str, ModelConfig]: } +def _default_openai_provider_tools() -> list[dict[str, Any]]: + """Hosted tools when TOML omits ``provider_tools``.""" + return [{"type": "web_search"}] + + class OpenAIProvider(ProviderConfig, tag="openai"): """OpenAI platform provider (agent uses Responses API). @@ -71,10 +76,12 @@ class OpenAIProvider(ProviderConfig, tag="openai"): When ``models`` is omitted in TOML, seeds a small default catalog (same idea as DeepSeek). Explicit ``models = {}`` stays empty (recoverable / free-form). + When ``provider_tools`` is omitted, defaults to web_search; use + ``provider_tools = []`` to disable hosted tools. """ models: dict[str, ModelConfig] = field(default_factory=_default_openai_models) - provider_tools: list[dict[str, Any]] = field(default_factory=list) + provider_tools: list[dict[str, Any]] = field(default_factory=_default_openai_provider_tools) class OpenAICompatibleProvider(ProviderConfig, tag="openai-compatible"): diff --git a/tests/test_config/test_config.py b/tests/test_config/test_config.py index 92ae82d..c402f54 100644 --- a/tests/test_config/test_config.py +++ b/tests/test_config/test_config.py @@ -61,6 +61,12 @@ def test_deepseek_default_models_on_construct() -> None: def test_openai_default_models_on_construct() -> None: provider = OpenAIProvider(access_key_or_token="sk-test") assert set(provider.models) == {"gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"} + assert provider.provider_tools == [{"type": "web_search"}] + + +def test_openai_explicit_empty_provider_tools() -> None: + provider = OpenAIProvider(access_key_or_token="sk-test", provider_tools=[]) + assert provider.provider_tools == [] def test_openai_omitted_preset_and_models_from_toml(tmp_path: Path) -> None: diff --git a/tests/test_runtime/test_client_factory.py b/tests/test_runtime/test_client_factory.py index 8eb4558..b8f199b 100644 --- a/tests/test_runtime/test_client_factory.py +++ b/tests/test_runtime/test_client_factory.py @@ -24,7 +24,7 @@ def test_openai_provider_defaults_base_url() -> None: client = create_client(provider) assert isinstance(client, ResponsesChatClient) assert hasattr(client, "chat_completions") - assert client._provider_tools == [] + assert client._provider_tools == [{"type": "web_search"}] def test_openai_provider_tools_passed_to_wrapper() -> None: