Compare commits

...
4 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.5 e15def607d fix: remove extra_body tool_choice hack for Claude backend
Build and Push / build (push) Successful in 1m57s
Build and Push / release (push) Successful in 3s
PydanticAI handles tool_choice natively for Anthropic. The extra_body
hack caused an infinite tool call loop where Claude kept calling the
same tool because tool_choice was forced to "any".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 11:51:42 +01:00
jpmschweitzerandClaude Opus 4.5 6dd1c2e2a9 fix: trigger CI on version tag push instead of release event
Changed workflow trigger from release:published to push:tags:v[0-9]*
so that pushing a version tag triggers the build pipeline.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:52:43 +01:00
jpmschweitzerandClaude Opus 4.5 3617218359 chore: release v2.0.1
Build and Push / release (release) Failing after 3s
Build and Push / build (release) Successful in 1m21s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:49:46 +01:00
jpmschweitzerandClaude Opus 4.5 c7a4012831 fix: use AnthropicProvider to pass api_key to PydanticAI model
AnthropicModel doesn't accept api_key directly; it must be passed
through an AnthropicProvider instance.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 09:47:02 +01:00
6 changed files with 43 additions and 9 deletions
+3 -2
View File
@@ -1,8 +1,9 @@
name: Build and Push
on:
release:
types: [published]
push:
tags:
- 'v[0-9]*'
jobs:
release:
+13
View File
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [2.0.2] - 2026-02-05
### Fixed
- **tool_choice format incompatibility** - Removed `extra_body` tool_choice hack for Claude backend; PydanticAI handles tool_choice natively for Anthropic, preventing infinite tool call loops
- **CI trigger** - Changed workflow trigger from `release:published` to `push:tags:v[0-9]*`
## [2.0.1] - 2026-02-05
### Fixed
- **Expert agent registration failure** - `AnthropicModel` does not accept `api_key` directly; now passes it via `AnthropicProvider`
## [2.0.0] - 2026-02-05
### Added
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tatlock"
version = "2.0.0"
version = "2.0.2"
description = "OpenAI-compatible API with Ollama backend"
requires-python = ">=3.12"
dependencies = []
+5 -5
View File
@@ -494,13 +494,13 @@ class TatlockAgent(AgentInterface):
)
# Run with scoped tools and tracker
# Force tool_choice: required to make LLM actually call tools
from pydantic_ai.settings import ModelSettings
# Force tool_choice to make LLM actually call tools
from src.anthropic.model_selector import get_tool_choice_settings
result = await scoped_agent.run(
enriched_message,
message_history=pydantic_history if pydantic_history else None,
deps=tool_tracker,
model_settings=ModelSettings(extra_body={"tool_choice": "required"})
model_settings=get_tool_choice_settings(),
)
logger.info(
@@ -624,7 +624,6 @@ class TatlockAgent(AgentInterface):
- tool_outputs: Dict mapping tool names to their outputs
- raw_output: The agent's raw text output
"""
from pydantic_ai.settings import ModelSettings
from pydantic_ai.messages import (
ModelRequest,
ModelResponse,
@@ -684,11 +683,12 @@ class TatlockAgent(AgentInterface):
)
# Run with scoped tools and tracker
from src.anthropic.model_selector import get_tool_choice_settings
result = await scoped_agent.run(
enriched_message,
message_history=pydantic_history if pydantic_history else None,
deps=tool_tracker,
model_settings=ModelSettings(extra_body={"tool_choice": "required"})
model_settings=get_tool_choice_settings(),
)
# Extract tool calls and results from the agent's messages
+2
View File
@@ -7,11 +7,13 @@ Provides model selection with automatic fallback between Claude and Ollama.
from src.anthropic.model_selector import (
check_claude_health,
get_model,
get_tool_choice_settings,
is_claude_available,
)
__all__ = [
"check_claude_health",
"get_model",
"get_tool_choice_settings",
"is_claude_available",
]
+19 -1
View File
@@ -9,6 +9,7 @@ from typing import Union
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.anthropic import AnthropicProvider
from src.core.config import config
from src.core.logging_config import get_logger
@@ -113,7 +114,7 @@ def get_model(prefer_cloud: bool | None = None) -> Union[AnthropicModel, OpenAIC
)
return AnthropicModel(
model_name=config.ANTHROPIC_MODEL,
api_key=config.ANTHROPIC_API_KEY,
provider=AnthropicProvider(api_key=config.ANTHROPIC_API_KEY),
)
# Fall back to Ollama
@@ -131,6 +132,23 @@ def get_model(prefer_cloud: bool | None = None) -> Union[AnthropicModel, OpenAIC
)
def get_tool_choice_settings() -> 'ModelSettings':
"""
Get model_settings for forcing tool calls on the first request.
For Claude: PydanticAI handles tool_choice natively, so no extra_body needed.
For Ollama: Pass tool_choice="required" via extra_body to force tool calling.
"""
from pydantic_ai.settings import ModelSettings
if is_claude_available() and config.PREFER_CLOUD_BACKEND:
# PydanticAI's Anthropic model handles tool_choice internally
return ModelSettings()
else:
# Ollama needs explicit tool_choice via extra_body
return ModelSettings(extra_body={"tool_choice": "required"})
def get_model_info() -> dict:
"""
Get information about the current model configuration.