stirrup 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
stirrup/__init__.py CHANGED
@@ -35,6 +35,7 @@ from stirrup.core.models import (
35
35
  AssistantMessage,
36
36
  AudioContentBlock,
37
37
  ChatMessage,
38
+ EmptyParams,
38
39
  ImageContentBlock,
39
40
  LLMClient,
40
41
  SubAgentMetadata,
@@ -58,6 +59,7 @@ __all__ = [
58
59
  "AudioContentBlock",
59
60
  "ChatMessage",
60
61
  "ContextOverflowError",
62
+ "EmptyParams",
61
63
  "ImageContentBlock",
62
64
  "LLMClient",
63
65
  "SubAgentMetadata",
@@ -67,7 +67,6 @@ class ChatCompletionsClient(LLMClient):
67
67
  *,
68
68
  base_url: str | None = None,
69
69
  api_key: str | None = None,
70
- supports_audio_input: bool = False,
71
70
  reasoning_effort: str | None = None,
72
71
  timeout: float | None = None,
73
72
  max_retries: int = 2,
@@ -82,7 +81,6 @@ class ChatCompletionsClient(LLMClient):
82
81
  Use for OpenAI-compatible providers (e.g., 'http://localhost:8000/v1').
83
82
  api_key: API key for authentication. If None, reads from OPENROUTER_API_KEY
84
83
  environment variable.
85
- supports_audio_input: Whether the model supports audio inputs. Defaults to False.
86
84
  reasoning_effort: Reasoning effort level for extended thinking models
87
85
  (e.g., 'low', 'medium', 'high'). Only used with o1/o3 style models.
88
86
  timeout: Request timeout in seconds. If None, uses OpenAI SDK default.
@@ -92,7 +90,6 @@ class ChatCompletionsClient(LLMClient):
92
90
  """
93
91
  self._model = model
94
92
  self._max_tokens = max_tokens
95
- self._supports_audio_input = supports_audio_input
96
93
  self._reasoning_effort = reasoning_effort
97
94
  self._kwargs = kwargs or {}
98
95
 
@@ -7,7 +7,7 @@ Requires the litellm extra: `pip install stirrup[litellm]`
7
7
  """
8
8
 
9
9
  import logging
10
- from typing import Any
10
+ from typing import Any, Literal
11
11
 
12
12
  try:
13
13
  from litellm import acompletion
@@ -38,6 +38,8 @@ __all__ = [
38
38
 
39
39
  LOGGER = logging.getLogger(__name__)
40
40
 
41
+ type ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "default"]
42
+
41
43
 
42
44
  class LiteLLMClient(LLMClient):
43
45
  """LiteLLM-based client supporting multiple LLM providers with unified interface.
@@ -49,8 +51,8 @@ class LiteLLMClient(LLMClient):
49
51
  self,
50
52
  model_slug: str,
51
53
  max_tokens: int,
52
- supports_audio_input: bool = False,
53
- reasoning_effort: str | None = None,
54
+ api_key: str | None = None,
55
+ reasoning_effort: ReasoningEffort | None = None,
54
56
  kwargs: dict[str, Any] | None = None,
55
57
  ) -> None:
56
58
  """Initialize LiteLLM client with model configuration and capabilities.
@@ -58,15 +60,13 @@ class LiteLLMClient(LLMClient):
58
60
  Args:
59
61
  model_slug: Model identifier for LiteLLM (e.g., 'anthropic/claude-3-5-sonnet-20241022')
60
62
  max_tokens: Maximum context window size in tokens
61
- supports_audio_input: Whether the model supports audio inputs
62
63
  reasoning_effort: Reasoning effort level for extended thinking models (e.g., 'medium', 'high')
63
64
  kwargs: Additional arguments to pass to LiteLLM completion calls
64
65
  """
65
66
  self._model_slug = model_slug
66
- self._supports_video_input = False
67
- self._supports_audio_input = supports_audio_input
68
67
  self._max_tokens = max_tokens
69
- self._reasoning_effort = reasoning_effort
68
+ self._reasoning_effort: ReasoningEffort | None = reasoning_effort
69
+ self._api_key = api_key
70
70
  self._kwargs = kwargs or {}
71
71
 
72
72
  @property
@@ -92,6 +92,8 @@ class LiteLLMClient(LLMClient):
92
92
  tools=to_openai_tools(tools) if tools else None,
93
93
  tool_choice="auto" if tools else None,
94
94
  max_tokens=self._max_tokens,
95
+ reasoning_effort=self._reasoning_effort,
96
+ api_key=self._api_key,
95
97
  **self._kwargs,
96
98
  )
97
99
 
@@ -103,14 +105,20 @@ class LiteLLMClient(LLMClient):
103
105
  )
104
106
 
105
107
  msg = choice["message"]
106
-
107
108
  reasoning: Reasoning | None = None
108
109
  if getattr(msg, "reasoning_content", None) is not None:
109
110
  reasoning = Reasoning(content=msg.reasoning_content)
110
111
  if getattr(msg, "thinking_blocks", None) is not None and len(msg.thinking_blocks) > 0:
111
- reasoning = Reasoning(
112
- signature=msg.thinking_blocks[0]["signature"], content=msg.thinking_blocks[0]["content"]
113
- )
112
+ if len(msg.thinking_blocks) > 1:
113
+ raise ValueError("Found multiple thinking blocks in the response")
114
+
115
+ signature = msg.thinking_blocks[0].get("thinking_signature", None)
116
+ content = msg.thinking_blocks[0].get("thinking", None)
117
+
118
+ if signature is None and content is None:
119
+ raise ValueError("Signature and content not found in the thinking block response")
120
+
121
+ reasoning = Reasoning(signature=signature, content=content)
114
122
 
115
123
  usage = r["usage"]
116
124
 
@@ -119,6 +127,7 @@ class LiteLLMClient(LLMClient):
119
127
  tool_call_id=tc.get("id"),
120
128
  name=tc["function"]["name"],
121
129
  arguments=tc["function"].get("arguments", "") or "",
130
+ signature=tc.get("provider_specific_fields", {}).get("thought_signature", None),
122
131
  )
123
132
  for tc in (msg.get("tool_calls") or [])
124
133
  ]
stirrup/clients/utils.py CHANGED
@@ -12,6 +12,7 @@ from stirrup.core.models import (
12
12
  AudioContentBlock,
13
13
  ChatMessage,
14
14
  Content,
15
+ EmptyParams,
15
16
  ImageContentBlock,
16
17
  SystemMessage,
17
18
  Tool,
@@ -47,7 +48,7 @@ def to_openai_tools(tools: dict[str, Tool]) -> list[dict[str, Any]]:
47
48
  "name": t.name,
48
49
  "description": t.description,
49
50
  }
50
- if t.parameters is not None:
51
+ if t.parameters is not EmptyParams:
51
52
  function["parameters"] = t.parameters.model_json_schema()
52
53
  tool_payload: dict[str, Any] = {
53
54
  "type": "function",
@@ -139,6 +140,10 @@ def to_openai_messages(msgs: list[ChatMessage]) -> list[dict[str, Any]]:
139
140
  tool_dict = tool.model_dump()
140
141
  tool_dict["id"] = tool.tool_call_id
141
142
  tool_dict["type"] = "function"
143
+ if tool.signature is not None:
144
+ tool_dict["provider_specific_fields"] = {
145
+ "thought_signature": tool.signature,
146
+ }
142
147
  tool_dict["function"] = {
143
148
  "name": tool.name,
144
149
  "arguments": tool.arguments,
stirrup/constants.py CHANGED
@@ -1,14 +1,18 @@
1
+ from typing import Literal
2
+
1
3
  # Tool naming
2
- FINISH_TOOL_NAME = "finish"
4
+ FINISH_TOOL_NAME: Literal["finish"] = "finish"
3
5
 
4
6
  # Agent execution limits
5
7
  AGENT_MAX_TURNS = 30 # Maximum agent turns before forced termination
6
8
  CONTEXT_SUMMARIZATION_CUTOFF = 0.7 # Context window usage threshold (0.0-1.0) that triggers message summarization
9
+ TURNS_REMAINING_WARNING_THRESHOLD = 20
7
10
 
8
11
  # Media resolution limits
9
12
  RESOLUTION_1MP = 1_000_000 # 1 megapixel - default max resolution for images
10
13
  RESOLUTION_480P = 640 * 480 # 480p video resolution
11
14
 
12
15
  # Code execution
13
- SUBMISSION_SANDBOX_TIMEOUT = 60 * 10 # 10 minutes
16
+ SANDBOX_TIMEOUT = 60 * 10 # 10 minutes
17
+ SANDBOX_REQUEST_TIMEOUT = 60 * 3 # 3 minutes
14
18
  E2B_SANDBOX_TEMPLATE_ALIAS = "e2b-sandbox"