Skip to content

Commit 00d359c

Browse files
chore: remove dependency on anthropic types
1 parent dfcda56 commit 00d359c

7 files changed

Lines changed: 132 additions & 63 deletions

File tree

src/askui/models/anthropic/messages_api.py

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import cast
1+
from typing import Tuple, cast
22

33
from anthropic import (
44
APIConnectionError,
@@ -13,6 +13,7 @@
1313
BetaMessageParam,
1414
BetaThinkingConfigParam,
1515
BetaToolChoiceParam,
16+
BetaToolUnionParam,
1617
)
1718
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
1819
from typing_extensions import override
@@ -23,7 +24,11 @@
2324
RETRYABLE_HTTP_STATUS_CODES,
2425
wait_for_retry_after_header,
2526
)
26-
from askui.models.shared.agent_message_param import MessageParam
27+
from askui.models.shared.agent_message_param import (
28+
MessageParam,
29+
ThinkingConfigParam,
30+
ToolChoiceParam,
31+
)
2732
from askui.models.shared.messages_api import MessagesApi
2833
from askui.models.shared.prompts import SystemPrompt
2934
from askui.models.shared.tools import ToolCollection
@@ -36,6 +41,47 @@ def _is_retryable_error(exception: BaseException) -> bool:
3641
return isinstance(exception, (APIConnectionError, APITimeoutError, APIError))
3742

3843

44+
def _parse_to_anthropic_types(
45+
tools: ToolCollection | None,
46+
betas: list[str] | None = None,
47+
system: SystemPrompt | None = None,
48+
thinking: ThinkingConfigParam | None = None,
49+
tool_choice: ToolChoiceParam | None = None,
50+
temperature: float | None = None,
51+
) -> Tuple[
52+
list[BetaToolUnionParam] | Omit,
53+
list[AnthropicBetaParam] | Omit,
54+
str | Omit,
55+
BetaThinkingConfigParam | Omit,
56+
BetaToolChoiceParam | Omit,
57+
float | Omit,
58+
]:
59+
"""Convert provider-agnostic types to Anthropic-specific types.
60+
61+
This function bridges the gap between the generic MessagesApi interface
62+
and Anthropic's specific type requirements. The input dicts should match
63+
Anthropic's expected structure (see Anthropic SDK documentation).
64+
"""
65+
_tools = (
66+
cast("list[BetaToolUnionParam]", tools.to_params())
67+
if tools is not None
68+
else omit
69+
)
70+
_betas = cast("list[AnthropicBetaParam]", betas) or omit
71+
_system: str | Omit = omit if system is None else str(system)
72+
# Cast dicts to Anthropic's TypedDict types
73+
# Runtime validation happens in Anthropic SDK
74+
_thinking = (
75+
cast("BetaThinkingConfigParam", thinking) if thinking is not None else omit
76+
)
77+
_tool_choice = (
78+
cast("BetaToolChoiceParam", tool_choice) if tool_choice is not None else omit
79+
)
80+
_temperature = temperature or omit
81+
82+
return (_tools, _betas, _system, _thinking, _tool_choice, _temperature)
83+
84+
3985
class AnthropicMessagesApi(MessagesApi):
4086
def __init__(
4187
self,
@@ -45,7 +91,6 @@ def __init__(
4591
self._client = client
4692
self._locator_serializer = locator_serializer
4793

48-
@override
4994
@retry(
5095
stop=stop_after_attempt(4), # 3 retries
5196
wait=wait_for_retry_after_header(
@@ -59,30 +104,35 @@ def create_message(
59104
self,
60105
messages: list[MessageParam],
61106
model_id: str,
62-
tools: ToolCollection | Omit = omit,
63-
max_tokens: int | Omit = omit,
64-
betas: list[AnthropicBetaParam] | Omit = omit,
107+
tools: ToolCollection | None = None,
108+
max_tokens: int | None = None,
109+
betas: list[str] | None = None,
65110
system: SystemPrompt | None = None,
66-
thinking: BetaThinkingConfigParam | Omit = omit,
67-
tool_choice: BetaToolChoiceParam | Omit = omit,
68-
temperature: float | Omit = omit,
111+
thinking: ThinkingConfigParam | None = None,
112+
tool_choice: ToolChoiceParam | None = None,
113+
temperature: float | None = None,
69114
) -> MessageParam:
70115
_messages = [
71116
cast("BetaMessageParam", message.model_dump(exclude={"stop_reason"}))
72117
for message in messages
73118
]
74-
_system: str | Omit = omit if system is None else str(system)
119+
120+
_tools, _betas, _system, _thinking, _tool_choice, _temperature = (
121+
_parse_to_anthropic_types(
122+
tools, betas, system, thinking, tool_choice, temperature
123+
)
124+
)
75125

76126
response = self._client.beta.messages.create( # type: ignore[misc]
77127
messages=_messages,
78128
max_tokens=max_tokens or 8192,
79129
model=model_id,
80-
tools=tools.to_params() if not isinstance(tools, Omit) else omit,
81-
betas=betas,
130+
tools=_tools,
131+
betas=_betas,
82132
system=_system,
83-
thinking=thinking,
84-
tool_choice=tool_choice,
85-
temperature=temperature,
133+
thinking=_thinking,
134+
tool_choice=_tool_choice,
135+
temperature=_temperature,
86136
timeout=300.0,
87137
)
88138
return MessageParam.model_validate(response.model_dump())

src/askui/models/shared/agent_message_param.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from pydantic import BaseModel
24
from typing_extensions import Literal
35

@@ -104,6 +106,13 @@ class BetaRedactedThinkingBlock(BaseModel):
104106
"end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"
105107
]
106108

109+
# Generic type aliases for provider-agnostic API
110+
# Different providers (Anthropic, OpenAI, etc.) have different structures
111+
# Using dict[str, Any] allows flexibility for different provider requirements
112+
ThinkingConfigParam = dict[str, Any]
113+
ToolChoiceParam = dict[str, Any]
114+
ToolParam = dict[str, Any]
115+
107116

108117
class MessageParam(BaseModel):
109118
role: Literal["user", "assistant"]
@@ -125,4 +134,6 @@ class MessageParam(BaseModel):
125134
"ToolResultBlockParam",
126135
"ToolUseBlockParam",
127136
"UrlImageSourceParam",
137+
"ThinkingConfigParam",
138+
"ToolChoiceParam",
128139
]
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from abc import ABC, abstractmethod
22

3-
from askui.models.shared.agent_message_param import MessageParam
3+
from askui.models.shared.agent_message_param import (
4+
MessageParam,
5+
ThinkingConfigParam,
6+
ToolChoiceParam,
7+
)
48
from askui.models.shared.prompts import SystemPrompt
59
from askui.models.shared.tools import ToolCollection
610

@@ -17,24 +21,30 @@ def create_message(
1721
max_tokens: int | None = None,
1822
betas: list[str] | None = None,
1923
system: SystemPrompt | None = None,
20-
thinking: dict[str, str] | None = None,
21-
tool_choice: dict[str, str] | None = None,
24+
thinking: ThinkingConfigParam | None = None,
25+
tool_choice: ToolChoiceParam | None = None,
2226
temperature: float | None = None,
2327
) -> MessageParam:
24-
"""Create a message using the Anthropic API.
28+
"""Create a message using a Messages API (provider-agnostic).
2529
2630
Args:
27-
messages (list[MessageParam]): The messages to create a message.
31+
messages (list[MessageParam]): The message history.
2832
model_id (str): The model identifier to use.
29-
tools (ToolCollection | None): The tools to use.
33+
tools (ToolCollection | None): The tools available to the model.
3034
max_tokens (int | None): The maximum number of tokens to generate.
31-
betas (list[str] | None): The betas to use.
32-
system (SystemPrompt | None): The system to use.
33-
thinking (dict[str, str] | None): The thinking to use.
34-
tool_choice (dict[str, str] | None): The tool choice to use.
35-
temperature (float | None): The temperature to use.
35+
betas (list[str] | None): Beta features to enable (provider-specific).
36+
system (SystemPrompt | None): The system prompt.
37+
thinking (ThinkingConfigParam | None): Thinking configuration (provider-specific).
38+
tool_choice (ToolChoiceParam | None): Tool choice configuration (provider-specific).
39+
temperature (float | None): The sampling temperature (0-1).
3640
3741
Returns:
38-
MessageParam: The created message.
42+
MessageParam: The created message from the model.
43+
44+
Note:
45+
The `thinking` and `tool_choice` parameters are provider-specific
46+
dictionaries. See the specific MessagesApi implementation for details.
47+
For Anthropic: see anthropic.types.beta.BetaThinkingConfigParam and
48+
BetaToolChoiceParam.
3949
"""
4050
raise NotImplementedError

src/askui/models/shared/settings.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
from anthropic import Omit, omit
2-
from anthropic.types import AnthropicBetaParam
3-
from anthropic.types.beta import (
4-
BetaThinkingConfigParam,
5-
BetaToolChoiceParam,
6-
)
71
from pydantic import BaseModel, ConfigDict, Field
82
from typing_extensions import Literal
93

4+
from askui.models.shared.agent_message_param import ThinkingConfigParam, ToolChoiceParam
105
from askui.models.shared.prompts import (
116
ActSystemPrompt,
127
GetSystemPrompt,
@@ -20,14 +15,21 @@
2015

2116

2217
class MessageSettings(BaseModel):
18+
"""Settings for message creation in ActModel operations.
19+
20+
The `thinking` and `tool_choice` fields are provider-specific dictionaries.
21+
For Anthropic models, see: anthropic.types.beta.BetaThinkingConfigParam
22+
and anthropic.types.beta.BetaToolChoiceParam
23+
"""
24+
2325
model_config = ConfigDict(arbitrary_types_allowed=True)
2426

25-
betas: list[AnthropicBetaParam] | Omit = omit
27+
betas: list[str] | None = None
2628
max_tokens: int = 8192
2729
system: ActSystemPrompt | None = None
28-
thinking: BetaThinkingConfigParam | Omit = omit
29-
tool_choice: BetaToolChoiceParam | Omit = omit
30-
temperature: float | Omit = Field(default=omit, ge=0.0, le=1.0)
30+
thinking: ThinkingConfigParam | None = None
31+
tool_choice: ToolChoiceParam | None = None
32+
temperature: float | None = Field(default=None, ge=0.0, le=1.0)
3133

3234

3335
class ActSettings(BaseModel):

src/askui/models/shared/token_counter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from abc import ABC, abstractmethod
44

55
import httpx
6-
from anthropic.types.beta import BetaToolUnionParam
76
from typing_extensions import override
87

98
from askui.models.shared.agent_message_param import (
109
Base64ImageSourceParam,
1110
ContentBlockParam,
1211
ImageBlockParam,
1312
MessageParam,
13+
ToolParam,
1414
ToolResultBlockParam,
1515
)
1616
from askui.models.shared.prompts import SystemPrompt
@@ -57,14 +57,14 @@ class TokenCounter(ABC):
5757
@abstractmethod
5858
def count_tokens(
5959
self,
60-
tools: list[BetaToolUnionParam] | None = None,
60+
tools: list[ToolParam] | None = None,
6161
system: ActSystemPrompt | None = None,
6262
messages: list[MessageParam] | None = None,
6363
) -> TokenCounts:
6464
"""Count total tokens (estimated) using simple string length estimation.
6565
6666
Args:
67-
tools (list[BetaToolUnionParam] | None, optional): The tools to count
67+
tools (list[ToolParam] | None, optional): The tools to count
6868
tokens for. Defaults to `None`.
6969
system (ActSystemPrompt | None, optional): The system
7070
prompt or system blocks to count tokens for. Defaults to `None`.
@@ -137,7 +137,7 @@ def _get_image_dimensions_from_url(self, url: str) -> tuple[int, int] | None:
137137
@override
138138
def count_tokens(
139139
self,
140-
tools: list[BetaToolUnionParam] | None = None,
140+
tools: list[ToolParam] | None = None,
141141
system: SystemPrompt | None = None,
142142
messages: list[MessageParam] | None = None,
143143
model: str | None = None, # noqa: ARG002

src/askui/models/shared/tools.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@
77

88
import jsonref
99
import mcp
10-
from anthropic.types.beta import (
11-
BetaCacheControlEphemeralParam,
12-
BetaToolParam,
13-
BetaToolUnionParam,
14-
)
15-
from anthropic.types.beta.beta_tool_param import InputSchema
1610
from asyncer import syncify
1711
from fastmcp.client.client import CallToolResult, ProgressHandler
1812
from fastmcp.tools import Tool as FastMcpTool
@@ -24,9 +18,11 @@
2418

2519
from askui.models.shared.agent_message_param import (
2620
Base64ImageSourceParam,
21+
CacheControlEphemeralParam,
2722
ContentBlockParam,
2823
ImageBlockParam,
2924
TextBlockParam,
25+
ToolParam,
3026
ToolResultBlockParam,
3127
ToolUseBlockParam,
3228
)
@@ -109,7 +105,7 @@ def _convert_to_content(
109105
]
110106

111107

112-
def _default_input_schema() -> InputSchema:
108+
def _default_input_schema() -> dict[str, Any]:
113109
return {"type": "object", "properties": {}, "required": []}
114110

115111

@@ -163,7 +159,7 @@ def _create_tool_result_block_param_for_playwright_error(
163159
class Tool(BaseModel, ABC):
164160
name: str = Field(description="Name of the tool")
165161
description: str = Field(description="Description of what the tool does")
166-
input_schema: InputSchema = Field(
162+
input_schema: dict[str, Any] = Field(
167163
default_factory=_default_input_schema,
168164
description="JSON schema for tool parameters",
169165
)
@@ -179,8 +175,8 @@ def __call__(self, *args: Any, **kwargs: Any) -> ToolCallResult:
179175

180176
def to_params(
181177
self,
182-
) -> BetaToolUnionParam:
183-
return BetaToolParam(
178+
) -> ToolParam:
179+
return ToolParam(
184180
name=self.name,
185181
description=self.description,
186182
input_schema=self.input_schema,
@@ -277,7 +273,7 @@ async def __aexit__(
277273
) -> None: ...
278274

279275

280-
def _replace_refs(tool_name: str, input_schema: InputSchema) -> InputSchema:
276+
def _replace_refs(tool_name: str, input_schema: dict[str, Any]) -> dict[str, Any]:
281277
try:
282278
return jsonref.replace_refs( # type: ignore[no-any-return]
283279
input_schema,
@@ -352,7 +348,7 @@ def retrieve_tool_beta_flags(self) -> list[str]:
352348
result.add(beta_flag)
353349
return list(result)
354350

355-
def to_params(self) -> list[BetaToolUnionParam]:
351+
def to_params(self) -> list[ToolParam]:
356352
tool_map = {
357353
**self._get_mcp_tool_params(),
358354
**{
@@ -366,22 +362,22 @@ def to_params(self) -> list[BetaToolUnionParam]:
366362
}
367363
result = list(filtered_tool_map.values())
368364
if result:
369-
result[-1]["cache_control"] = BetaCacheControlEphemeralParam(
365+
result[-1]["cache_control"] = CacheControlEphemeralParam(
370366
type="ephemeral",
371367
)
372368
return result
373369

374-
def _get_mcp_tool_params(self) -> dict[str, BetaToolUnionParam]:
370+
def _get_mcp_tool_params(self) -> dict[str, ToolParam]:
375371
if not self._mcp_client:
376372
return {}
377373
mcp_tools = self._get_mcp_tools()
378-
result: dict[str, BetaToolUnionParam] = {}
374+
result: dict[str, ToolParam] = {}
379375
for tool_name, tool in mcp_tools.items():
380376
if params := (tool.meta or {}).get("params"):
381377
# validation missing
382378
result[tool_name] = params
383379
continue
384-
result[tool_name] = BetaToolParam(
380+
result[tool_name] = ToolParam(
385381
name=tool_name,
386382
description=tool.description or "",
387383
input_schema=_replace_refs(tool_name, tool.inputSchema),

0 commit comments

Comments
 (0)