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 +2 -0
- stirrup/clients/chat_completions_client.py +0 -3
- stirrup/clients/litellm_client.py +20 -11
- stirrup/clients/utils.py +6 -1
- stirrup/constants.py +6 -2
- stirrup/core/agent.py +206 -57
- stirrup/core/cache.py +479 -0
- stirrup/core/models.py +53 -7
- stirrup/prompts/base_system_prompt.txt +1 -1
- stirrup/skills/__init__.py +24 -0
- stirrup/skills/skills.py +145 -0
- stirrup/tools/__init__.py +2 -0
- stirrup/tools/calculator.py +1 -1
- stirrup/tools/code_backends/base.py +7 -0
- stirrup/tools/code_backends/docker.py +16 -4
- stirrup/tools/code_backends/e2b.py +32 -13
- stirrup/tools/code_backends/local.py +16 -4
- stirrup/tools/finish.py +1 -1
- stirrup/tools/user_input.py +130 -0
- stirrup/tools/web.py +1 -0
- stirrup/utils/logging.py +24 -0
- {stirrup-0.1.1.dist-info → stirrup-0.1.3.dist-info}/METADATA +36 -16
- stirrup-0.1.3.dist-info/RECORD +36 -0
- {stirrup-0.1.1.dist-info → stirrup-0.1.3.dist-info}/WHEEL +1 -1
- stirrup-0.1.1.dist-info/RECORD +0 -32
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
|
-
|
|
53
|
-
reasoning_effort:
|
|
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
|
-
|
|
112
|
-
|
|
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
|
|
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
|
-
|
|
16
|
+
SANDBOX_TIMEOUT = 60 * 10 # 10 minutes
|
|
17
|
+
SANDBOX_REQUEST_TIMEOUT = 60 * 3 # 3 minutes
|
|
14
18
|
E2B_SANDBOX_TEMPLATE_ALIAS = "e2b-sandbox"
|