From 26da24c8bf010fecd0b35a37b4aee3b080866e13 Mon Sep 17 00:00:00 2001 From: doria <93405247+dr-frmr@users.noreply.github.com> Date: Tue, 13 Jan 2026 16:54:17 -0500 Subject: [PATCH] feat: add tool_choice to dialectic level settings, fix: `extra-high` label to `max` (#326) Co-authored-by: Claude Opus 4.5 --- .env.template | 14 +++++++------- .github/workflows/unittest.yml | 8 ++++---- config.toml.example | 2 +- sdks/python/pyproject.toml | 2 +- sdks/python/src/honcho/async_client/peer.py | 4 ++-- sdks/python/src/honcho/peer.py | 4 ++-- sdks/typescript/bun.lock | 4 ++-- sdks/typescript/package.json | 2 +- sdks/typescript/src/peer.ts | 2 +- sdks/typescript/src/validation.ts | 2 +- src/config.py | 12 ++++++++---- src/dialectic/core.py | 4 ++-- src/schemas.py | 2 +- uv.lock | 8 ++++---- 14 files changed, 37 insertions(+), 33 deletions(-) diff --git a/.env.template b/.env.template index 7062b17d..a709b361 100644 --- a/.env.template +++ b/.env.template @@ -147,14 +147,14 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here # DIALECTIC_LEVELS__high__THINKING_BUDGET_TOKENS=0 # DIALECTIC_LEVELS__high__MAX_TOOL_ITERATIONS=4 -# Extra-high level -# DIALECTIC_LEVELS__extra-high__PROVIDER=anthropic -# DIALECTIC_LEVELS__extra-high__MODEL=claude-opus-4-5 -# DIALECTIC_LEVELS__extra-high__THINKING_BUDGET_TOKENS=512 -# DIALECTIC_LEVELS__extra-high__MAX_TOOL_ITERATIONS=10 +# Max level +# DIALECTIC_LEVELS__max__PROVIDER=anthropic +# DIALECTIC_LEVELS__max__MODEL=claude-opus-4-5 +# DIALECTIC_LEVELS__max__THINKING_BUDGET_TOKENS=512 +# DIALECTIC_LEVELS__max__MAX_TOOL_ITERATIONS=10 # Optional backup per level (must set both or neither): -# DIALECTIC_LEVELS__extra-high__BACKUP_PROVIDER=google -# DIALECTIC_LEVELS__extra-high__BACKUP_MODEL=gemini-2.5-pro +# DIALECTIC_LEVELS__max__BACKUP_PROVIDER=google +# DIALECTIC_LEVELS__max__BACKUP_MODEL=gemini-2.5-pro # ============================================================================= # Summary Settings diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 3ec03188..fc3df8b1 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -121,10 +121,10 @@ jobs: DIALECTIC_LEVELS__high__MODEL: test DIALECTIC_LEVELS__high__THINKING_BUDGET_TOKENS: 0 DIALECTIC_LEVELS__high__MAX_TOOL_ITERATIONS: 4 - DIALECTIC_LEVELS__extra-high__PROVIDER: openai - DIALECTIC_LEVELS__extra-high__MODEL: test - DIALECTIC_LEVELS__extra-high__THINKING_BUDGET_TOKENS: 0 - DIALECTIC_LEVELS__extra-high__MAX_TOOL_ITERATIONS: 10 + DIALECTIC_LEVELS__max__PROVIDER: openai + DIALECTIC_LEVELS__max__MODEL: test + DIALECTIC_LEVELS__max__THINKING_BUDGET_TOKENS: 0 + DIALECTIC_LEVELS__max__MAX_TOOL_ITERATIONS: 10 DIALECTIC_QUERY_GENERATION_PROVIDER: openai DIALECTIC_QUERY_GENERATION_MODEL: test SUMMARY_PROVIDER: openai diff --git a/config.toml.example b/config.toml.example index 36e02948..16f5c1e6 100644 --- a/config.toml.example +++ b/config.toml.example @@ -120,7 +120,7 @@ MODEL = "claude-opus-4-5" THINKING_BUDGET_TOKENS = 0 MAX_TOOL_ITERATIONS = 4 -[dialectic.levels.extra-high] +[dialectic.levels.max] PROVIDER = "anthropic" MODEL = "claude-opus-4-5" THINKING_BUDGET_TOKENS = 512 diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 2fe3b509..5c6094bc 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -8,7 +8,7 @@ authors = [ { name = "Plastic Labs", email = "hello@plasticlabs.ai" }, ] dependencies = [ - "honcho-core==1.10.0", + "honcho-core==1.11.0", "httpx>=0.28.0, <1", "pydantic>=2.0.0, <3", "typing-extensions>=4.12.0; python_version < \"3.12\"", diff --git a/sdks/python/src/honcho/async_client/peer.py b/sdks/python/src/honcho/async_client/peer.py index ce2893b9..22c8256f 100644 --- a/sdks/python/src/honcho/async_client/peer.py +++ b/sdks/python/src/honcho/async_client/peer.py @@ -145,7 +145,7 @@ class AsyncPeer(PeerBase): stream: bool = False, target: str | PeerBase | None = None, session: str | SessionBase | None = None, - reasoning_level: Literal["minimal", "low", "medium", "high", "extra-high"] + reasoning_level: Literal["minimal", "low", "medium", "high", "max"] | None = None, ) -> str | DialecticStreamResponse | None: """ @@ -166,7 +166,7 @@ class AsyncPeer(PeerBase): information from that session is considered. Can be a session ID string or an AsyncSession object. reasoning_level: Optional reasoning level for the query: "minimal", "low", "medium", - "high", or "extra-high". Defaults to "low" if not provided. + "high", or "max". Defaults to "low" if not provided. Returns: For non-streaming: Response string containing the answer, or None if no relevant information diff --git a/sdks/python/src/honcho/peer.py b/sdks/python/src/honcho/peer.py index da0d90f2..84372788 100644 --- a/sdks/python/src/honcho/peer.py +++ b/sdks/python/src/honcho/peer.py @@ -122,7 +122,7 @@ class Peer(PeerBase): stream: bool = False, target: str | PeerBase | None = None, session: str | SessionBase | None = None, - reasoning_level: Literal["minimal", "low", "medium", "high", "extra-high"] + reasoning_level: Literal["minimal", "low", "medium", "high", "max"] | None = None, ) -> str | DialecticStreamResponse | None: """ @@ -143,7 +143,7 @@ class Peer(PeerBase): information from that session is considered. Can be a session ID string or a Session object. reasoning_level: Optional reasoning level for the query: "minimal", "low", "medium", - "high", or "extra-high". Defaults to "low" if not provided. + "high", or "max". Defaults to "low" if not provided. Returns: For non-streaming: Response string containing the answer, or None if no relevant information diff --git a/sdks/typescript/bun.lock b/sdks/typescript/bun.lock index 86fba967..353fdc6b 100644 --- a/sdks/typescript/bun.lock +++ b/sdks/typescript/bun.lock @@ -4,7 +4,7 @@ "": { "name": "@honcho-ai/sdk", "dependencies": { - "@honcho-ai/core": "2.1.0", + "@honcho-ai/core": "2.2.0", "@types/node": "^24.0.1", "zod": "4.0.0", }, @@ -106,7 +106,7 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w=="], - "@honcho-ai/core": ["@honcho-ai/core@2.1.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-BssURYjiBmFF/guQQkr6Dpiz1ZOzhJsf/rdp6Ek/A3T2vNz3pop0fNgHK6SjCFcqjSKF8SHWJMF2VqqH0y4J6Q=="], + "@honcho-ai/core": ["@honcho-ai/core@2.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-3lDBdruXlg2m8OEGe5mSD6pt4EgZulgvT0tbaM7k61Mi+m8H56aQNzlCv5yPLdw5G/FIEjyJJcFfHknqdxoyrg=="], "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 9613a7ff..fa89528a 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -20,7 +20,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "@honcho-ai/core": "2.1.0", + "@honcho-ai/core": "2.2.0", "@types/node": "^24.0.1", "zod": "4.0.0" }, diff --git a/sdks/typescript/src/peer.ts b/sdks/typescript/src/peer.ts index 8623f749..c8968b62 100644 --- a/sdks/typescript/src/peer.ts +++ b/sdks/typescript/src/peer.ts @@ -112,7 +112,7 @@ export class Peer { * information from that session is considered. Can be a session * ID string or a Session object. * @param reasoningLevel - Optional reasoning level for the query: "minimal", "low", "medium", - * "high", or "extra-high". Defaults to "low" if not provided. + * "high", or "max". Defaults to "low" if not provided. * @returns Promise resolving to: * - For non-streaming: response string or null if no relevant information * - For streaming: DialecticStreamResponse that can be iterated over diff --git a/sdks/typescript/src/validation.ts b/sdks/typescript/src/validation.ts index f8e84510..530b9b8a 100644 --- a/sdks/typescript/src/validation.ts +++ b/sdks/typescript/src/validation.ts @@ -143,7 +143,7 @@ export const ChatQuerySchema = z.object({ val ? (typeof val === 'string' ? val : val.id) : undefined ), reasoningLevel: z - .enum(['minimal', 'low', 'medium', 'high', 'extra-high']) + .enum(['minimal', 'low', 'medium', 'high', 'max']) .optional(), }) diff --git a/src/config.py b/src/config.py index bb0e0faf..01741573 100644 --- a/src/config.py +++ b/src/config.py @@ -286,13 +286,13 @@ class PeerCardSettings(HonchoSettings): # Reasoning levels for dialectic - defined here to avoid circular imports with schemas -ReasoningLevel = Literal["minimal", "low", "medium", "high", "extra-high"] +ReasoningLevel = Literal["minimal", "low", "medium", "high", "max"] REASONING_LEVELS: list[ReasoningLevel] = [ "minimal", "low", "medium", "high", - "extra-high", + "max", ] @@ -313,6 +313,9 @@ class DialecticLevelSettings(BaseModel): MAX_TOOL_ITERATIONS: Annotated[ int, Field(ge=0, le=50, validation_alias="max_tool_iterations") ] + TOOL_CHOICE: Annotated[str | None, Field(validation_alias="tool_choice")] = ( + None # None/auto lets model decide, "any"/"required" forces tool use + ) @model_validator(mode="after") def _validate_backup_configuration(self) -> "DialecticLevelSettings": @@ -350,7 +353,8 @@ class DialecticSettings(HonchoSettings): PROVIDER="google", MODEL="gemini-2.5-flash-lite", THINKING_BUDGET_TOKENS=0, - MAX_TOOL_ITERATIONS=2, + MAX_TOOL_ITERATIONS=5, + TOOL_CHOICE="any", ), "low": DialecticLevelSettings( PROVIDER="google", @@ -370,7 +374,7 @@ class DialecticSettings(HonchoSettings): THINKING_BUDGET_TOKENS=0, MAX_TOOL_ITERATIONS=4, ), - "extra-high": DialecticLevelSettings( + "max": DialecticLevelSettings( PROVIDER="anthropic", MODEL="claude-opus-4-5", THINKING_BUDGET_TOKENS=2048, diff --git a/src/dialectic/core.py b/src/dialectic/core.py index 69aebf48..6e8826a2 100644 --- a/src/dialectic/core.py +++ b/src/dialectic/core.py @@ -347,7 +347,7 @@ class DialecticAgent: prompt="", # Ignored since we pass messages max_tokens=settings.DIALECTIC.MAX_OUTPUT_TOKENS, tools=DIALECTIC_TOOLS, - tool_choice=None, + tool_choice=level_settings.TOOL_CHOICE, tool_executor=tool_executor, max_tool_iterations=level_settings.MAX_TOOL_ITERATIONS, messages=self.messages, @@ -401,7 +401,7 @@ class DialecticAgent: stream=True, stream_final_only=True, tools=DIALECTIC_TOOLS, - tool_choice=None, + tool_choice=level_settings.TOOL_CHOICE, tool_executor=tool_executor, max_tool_iterations=level_settings.MAX_TOOL_ITERATIONS, messages=self.messages, diff --git a/src/schemas.py b/src/schemas.py index 9b482c7f..6218bd42 100644 --- a/src/schemas.py +++ b/src/schemas.py @@ -653,7 +653,7 @@ class DialecticOptions(BaseModel): stream: bool = False reasoning_level: ReasoningLevel = Field( default="low", - description="Level of reasoning to apply: minimal, low, medium, high, or extra-high", + description="Level of reasoning to apply: minimal, low, medium, high, or max", ) diff --git a/uv.lock b/uv.lock index d9df1526..aa512f34 100644 --- a/uv.lock +++ b/uv.lock @@ -859,7 +859,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "honcho-core", specifier = "==1.10.0" }, + { name = "honcho-core", specifier = "==1.11.0" }, { name = "httpx", specifier = ">=0.28.0,<1" }, { name = "pydantic", specifier = ">=2.0.0,<3" }, { name = "typing-extensions", marker = "python_full_version < '3.12'", specifier = ">=4.12.0" }, @@ -870,7 +870,7 @@ dev = [{ name = "ruff", specifier = ">=0.11.13" }] [[package]] name = "honcho-core" -version = "1.10.0" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -880,9 +880,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/5f/912716e911966d8cefb34a23f9b3a452348f147e040c1025113a202363a2/honcho_core-1.10.0.tar.gz", hash = "sha256:1dc139f6199a7589781636b4535cc97da6016477fbaeeaee53376e7a227a5c51", size = 144284, upload-time = "2026-01-12T23:30:30.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/82/7c7db37556930ecb9b908f911100159abc46e103aa252b3ec318c720b413/honcho_core-1.11.0.tar.gz", hash = "sha256:2898b18633930cda6d8414c7e001e4cb9da17327f448000a6c312b51372975f3", size = 144331, upload-time = "2026-01-13T19:53:59.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/27/7cc9c5d0a91921339b843bed3d520225731199156c3410604375e5faf487/honcho_core-1.10.0-py3-none-any.whl", hash = "sha256:d0c7a1285b8e75c106b540773c0077e563c9bf6abda040da583bb7566828877d", size = 138588, upload-time = "2026-01-12T23:30:29.351Z" }, + { url = "https://files.pythonhosted.org/packages/ad/08/4ada41526b74b11a332db0e48d75f4d93b4ed474d82a48c7251d78656dce/honcho_core-1.11.0-py3-none-any.whl", hash = "sha256:519c6a7759badfae1b1a3587fbf577cb5ab8188e5093dc0f4fd62602e8dfb523", size = 138588, upload-time = "2026-01-13T19:53:58.369Z" }, ] [[package]]