agentle 0.9.34__py3-none-any.whl → 0.9.36__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.
- agentle/agents/whatsapp/whatsapp_bot.py +52 -22
- agentle/generations/providers/openrouter/_adapters/agentle_message_to_openrouter_message_adapter.py +127 -7
- agentle/generations/providers/openrouter/openrouter_generation_provider.py +17 -8
- {agentle-0.9.34.dist-info → agentle-0.9.36.dist-info}/METADATA +1 -1
- {agentle-0.9.34.dist-info → agentle-0.9.36.dist-info}/RECORD +7 -7
- {agentle-0.9.34.dist-info → agentle-0.9.36.dist-info}/WHEEL +0 -0
- {agentle-0.9.34.dist-info → agentle-0.9.36.dist-info}/licenses/LICENSE +0 -0
|
@@ -2679,11 +2679,25 @@ class WhatsAppBot[T_Schema: WhatsAppResponseBase = WhatsAppResponseBase](BaseMod
|
|
|
2679
2679
|
return "mp3" # default
|
|
2680
2680
|
|
|
2681
2681
|
def _split_message_by_line_breaks(self, text: str) -> Sequence[str]:
|
|
2682
|
-
"""Split message by line breaks first, then by length if needed with enhanced validation.
|
|
2682
|
+
"""Split message by line breaks first, then by length if needed with enhanced validation.
|
|
2683
|
+
|
|
2684
|
+
CRITICAL: This method must preserve line breaks within messages for proper WhatsApp formatting.
|
|
2685
|
+
"""
|
|
2683
2686
|
if not text or not text.strip():
|
|
2684
2687
|
return ["[Mensagem vazia]"] # Portuguese: "Empty message"
|
|
2685
2688
|
|
|
2686
2689
|
try:
|
|
2690
|
+
# Check if entire text fits in one message - if so, return it as-is
|
|
2691
|
+
if len(text) <= self.config.max_message_length:
|
|
2692
|
+
logger.debug(
|
|
2693
|
+
f"[SPLIT_MESSAGE] Message fits in single message ({len(text)} chars), returning as-is"
|
|
2694
|
+
)
|
|
2695
|
+
return [text]
|
|
2696
|
+
|
|
2697
|
+
logger.info(
|
|
2698
|
+
f"[SPLIT_MESSAGE] Message too long ({len(text)} chars), splitting into multiple messages"
|
|
2699
|
+
)
|
|
2700
|
+
|
|
2687
2701
|
# First split by double line breaks (paragraphs)
|
|
2688
2702
|
paragraphs = text.split("\n\n")
|
|
2689
2703
|
messages: MutableSequence[str] = []
|
|
@@ -2692,38 +2706,54 @@ class WhatsAppBot[T_Schema: WhatsAppResponseBase = WhatsAppResponseBase](BaseMod
|
|
|
2692
2706
|
if not paragraph.strip():
|
|
2693
2707
|
continue
|
|
2694
2708
|
|
|
2709
|
+
# If paragraph fits, keep it intact with all line breaks
|
|
2710
|
+
if len(paragraph) <= self.config.max_message_length:
|
|
2711
|
+
messages.append(paragraph)
|
|
2712
|
+
continue
|
|
2713
|
+
|
|
2714
|
+
# Paragraph is too long - need to split it
|
|
2695
2715
|
# Check if paragraph is a list (has list markers)
|
|
2696
2716
|
lines = paragraph.split("\n")
|
|
2697
2717
|
is_list_paragraph = self._is_list_content(lines)
|
|
2698
2718
|
|
|
2699
2719
|
if is_list_paragraph:
|
|
2700
|
-
# Group list items together
|
|
2701
|
-
# IMPORTANT: Keep line breaks intact for list formatting
|
|
2720
|
+
# Group list items together, preserving line breaks
|
|
2702
2721
|
grouped_list = self._group_list_items(lines)
|
|
2703
2722
|
messages.extend(grouped_list)
|
|
2704
2723
|
else:
|
|
2705
|
-
# For non-list paragraphs,
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
#
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
continue
|
|
2724
|
+
# For non-list paragraphs, try to keep lines together
|
|
2725
|
+
current_chunk = ""
|
|
2726
|
+
for line in lines:
|
|
2727
|
+
if not line.strip():
|
|
2728
|
+
# Preserve empty lines for spacing
|
|
2729
|
+
if current_chunk:
|
|
2730
|
+
current_chunk += "\n"
|
|
2731
|
+
continue
|
|
2732
|
+
|
|
2733
|
+
# Try adding this line to current chunk
|
|
2734
|
+
test_chunk = (
|
|
2735
|
+
current_chunk + ("\n" if current_chunk else "") + line
|
|
2736
|
+
)
|
|
2719
2737
|
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2738
|
+
if len(test_chunk) <= self.config.max_message_length:
|
|
2739
|
+
current_chunk = test_chunk
|
|
2740
|
+
else:
|
|
2741
|
+
# Current chunk is full, save it
|
|
2742
|
+
if current_chunk:
|
|
2743
|
+
messages.append(current_chunk)
|
|
2744
|
+
|
|
2745
|
+
# Check if single line is too long
|
|
2746
|
+
if len(line) > self.config.max_message_length:
|
|
2747
|
+
# Split long line by length
|
|
2725
2748
|
split_lines = self._split_long_line(line)
|
|
2726
2749
|
messages.extend(split_lines)
|
|
2750
|
+
current_chunk = ""
|
|
2751
|
+
else:
|
|
2752
|
+
current_chunk = line
|
|
2753
|
+
|
|
2754
|
+
# Add remaining chunk
|
|
2755
|
+
if current_chunk:
|
|
2756
|
+
messages.append(current_chunk)
|
|
2727
2757
|
|
|
2728
2758
|
# Filter out empty messages and validate
|
|
2729
2759
|
final_messages = []
|
agentle/generations/providers/openrouter/_adapters/agentle_message_to_openrouter_message_adapter.py
CHANGED
|
@@ -29,14 +29,17 @@ from agentle.generations.providers.openrouter._types import (
|
|
|
29
29
|
OpenRouterMessage,
|
|
30
30
|
OpenRouterSystemMessage,
|
|
31
31
|
OpenRouterToolCall,
|
|
32
|
+
OpenRouterToolMessage,
|
|
32
33
|
OpenRouterUserMessage,
|
|
33
34
|
)
|
|
35
|
+
from agentle.generations.tools.tool_execution_result import ToolExecutionResult
|
|
36
|
+
import json
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
class AgentleMessageToOpenRouterMessageAdapter(
|
|
37
40
|
Adapter[
|
|
38
41
|
AssistantMessage | DeveloperMessage | UserMessage,
|
|
39
|
-
OpenRouterMessage,
|
|
42
|
+
OpenRouterMessage | list[OpenRouterMessage],
|
|
40
43
|
]
|
|
41
44
|
):
|
|
42
45
|
"""
|
|
@@ -44,15 +47,18 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
44
47
|
|
|
45
48
|
Handles conversion of:
|
|
46
49
|
- DeveloperMessage -> OpenRouterSystemMessage
|
|
47
|
-
- UserMessage -> OpenRouterUserMessage
|
|
50
|
+
- UserMessage -> OpenRouterUserMessage (or OpenRouterToolMessage if contains tool results)
|
|
48
51
|
- AssistantMessage -> OpenRouterAssistantMessage (with tool calls)
|
|
52
|
+
|
|
53
|
+
Note: When a message contains ToolExecutionResult parts, they are extracted
|
|
54
|
+
and returned as separate OpenRouterToolMessage objects.
|
|
49
55
|
"""
|
|
50
56
|
|
|
51
57
|
@override
|
|
52
58
|
def adapt(
|
|
53
59
|
self,
|
|
54
60
|
_f: AssistantMessage | DeveloperMessage | UserMessage,
|
|
55
|
-
) -> OpenRouterMessage:
|
|
61
|
+
) -> OpenRouterMessage | list[OpenRouterMessage]:
|
|
56
62
|
"""
|
|
57
63
|
Convert an Agentle message to OpenRouter format.
|
|
58
64
|
|
|
@@ -60,7 +66,9 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
60
66
|
_f: The Agentle message to convert.
|
|
61
67
|
|
|
62
68
|
Returns:
|
|
63
|
-
The corresponding OpenRouter message.
|
|
69
|
+
The corresponding OpenRouter message(s). Returns a list when the message
|
|
70
|
+
contains ToolExecutionResult parts that need to be split into separate
|
|
71
|
+
tool messages.
|
|
64
72
|
"""
|
|
65
73
|
message = _f
|
|
66
74
|
part_adapter = AgentlePartToOpenRouterPartAdapter()
|
|
@@ -76,12 +84,28 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
76
84
|
)
|
|
77
85
|
|
|
78
86
|
case UserMessage():
|
|
87
|
+
# Check if this message contains tool execution results
|
|
88
|
+
tool_results = [
|
|
89
|
+
p for p in message.parts if isinstance(p, ToolExecutionResult)
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
if tool_results:
|
|
93
|
+
# Convert each tool result to a separate tool message
|
|
94
|
+
return [
|
|
95
|
+
OpenRouterToolMessage(
|
|
96
|
+
role="tool",
|
|
97
|
+
tool_call_id=result.suggestion.id,
|
|
98
|
+
content=self._serialize_tool_result(result.result),
|
|
99
|
+
)
|
|
100
|
+
for result in tool_results
|
|
101
|
+
]
|
|
102
|
+
|
|
79
103
|
# User messages can have multimodal content
|
|
80
|
-
# Filter out non-content parts (like tool execution suggestions)
|
|
104
|
+
# Filter out non-content parts (like tool execution suggestions and results)
|
|
81
105
|
content_parts = [
|
|
82
106
|
p
|
|
83
107
|
for p in message.parts
|
|
84
|
-
if not isinstance(p, ToolExecutionSuggestion)
|
|
108
|
+
if not isinstance(p, (ToolExecutionSuggestion, ToolExecutionResult))
|
|
85
109
|
]
|
|
86
110
|
|
|
87
111
|
# If only text parts, concatenate into a string
|
|
@@ -102,6 +126,69 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
102
126
|
)
|
|
103
127
|
|
|
104
128
|
case AssistantMessage():
|
|
129
|
+
# Check if this message contains tool execution results
|
|
130
|
+
tool_results = [
|
|
131
|
+
p for p in message.parts if isinstance(p, ToolExecutionResult)
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if tool_results:
|
|
135
|
+
# If assistant message has tool results, we need to split it
|
|
136
|
+
# First, create the assistant message with tool calls (if any)
|
|
137
|
+
messages: list[OpenRouterMessage] = []
|
|
138
|
+
|
|
139
|
+
# Separate text content from tool calls
|
|
140
|
+
text_parts = [p for p in message.parts if isinstance(p, TextPart)]
|
|
141
|
+
tool_suggestions = [
|
|
142
|
+
p
|
|
143
|
+
for p in message.parts
|
|
144
|
+
if isinstance(p, ToolExecutionSuggestion)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# Only create assistant message if there's content or tool calls
|
|
148
|
+
if text_parts or tool_suggestions:
|
|
149
|
+
content = (
|
|
150
|
+
"".join(str(p) for p in text_parts) if text_parts else None
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
tool_calls: list[OpenRouterToolCall] = [
|
|
154
|
+
OpenRouterToolCall(
|
|
155
|
+
id=suggestion.id,
|
|
156
|
+
type="function",
|
|
157
|
+
function={
|
|
158
|
+
"name": suggestion.tool_name,
|
|
159
|
+
"arguments": self._serialize_tool_arguments(
|
|
160
|
+
suggestion.args
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
for suggestion in tool_suggestions
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
assistant_msg = OpenRouterAssistantMessage(
|
|
168
|
+
role="assistant",
|
|
169
|
+
content=content,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if tool_calls:
|
|
173
|
+
assistant_msg["tool_calls"] = tool_calls
|
|
174
|
+
|
|
175
|
+
if hasattr(message, "reasoning") and message.reasoning:
|
|
176
|
+
assistant_msg["reasoning"] = message.reasoning
|
|
177
|
+
|
|
178
|
+
messages.append(assistant_msg)
|
|
179
|
+
|
|
180
|
+
# Add tool result messages
|
|
181
|
+
for result in tool_results:
|
|
182
|
+
messages.append(
|
|
183
|
+
OpenRouterToolMessage(
|
|
184
|
+
role="tool",
|
|
185
|
+
tool_call_id=result.suggestion.id,
|
|
186
|
+
content=self._serialize_tool_result(result.result),
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return messages
|
|
191
|
+
|
|
105
192
|
# Separate text content from tool calls
|
|
106
193
|
text_parts = [p for p in message.parts if isinstance(p, TextPart)]
|
|
107
194
|
tool_suggestions = [
|
|
@@ -118,7 +205,9 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
118
205
|
type="function",
|
|
119
206
|
function={
|
|
120
207
|
"name": suggestion.tool_name,
|
|
121
|
-
"arguments":
|
|
208
|
+
"arguments": self._serialize_tool_arguments(
|
|
209
|
+
suggestion.args
|
|
210
|
+
),
|
|
122
211
|
},
|
|
123
212
|
)
|
|
124
213
|
for suggestion in tool_suggestions
|
|
@@ -137,3 +226,34 @@ class AgentleMessageToOpenRouterMessageAdapter(
|
|
|
137
226
|
result["reasoning"] = message.reasoning
|
|
138
227
|
|
|
139
228
|
return result
|
|
229
|
+
|
|
230
|
+
def _serialize_tool_arguments(self, args: object) -> str:
|
|
231
|
+
"""
|
|
232
|
+
Serialize tool arguments to JSON string.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
args: The arguments to serialize.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
JSON string representation of the arguments.
|
|
239
|
+
"""
|
|
240
|
+
if isinstance(args, str):
|
|
241
|
+
return args
|
|
242
|
+
return json.dumps(args)
|
|
243
|
+
|
|
244
|
+
def _serialize_tool_result(self, result: object) -> str:
|
|
245
|
+
"""
|
|
246
|
+
Serialize tool execution result to string.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
result: The result to serialize.
|
|
250
|
+
|
|
251
|
+
returns:
|
|
252
|
+
String representation of the result.
|
|
253
|
+
"""
|
|
254
|
+
if isinstance(result, str):
|
|
255
|
+
return result
|
|
256
|
+
try:
|
|
257
|
+
return json.dumps(result)
|
|
258
|
+
except (TypeError, ValueError):
|
|
259
|
+
return str(result)
|
|
@@ -71,6 +71,7 @@ from agentle.generations.providers.openrouter._types import (
|
|
|
71
71
|
OpenRouterFileParserPlugin,
|
|
72
72
|
OpenRouterModelsResponse,
|
|
73
73
|
OpenRouterModel,
|
|
74
|
+
OpenRouterMessage,
|
|
74
75
|
)
|
|
75
76
|
from agentle.generations.providers.openrouter.error_handler import (
|
|
76
77
|
parse_and_raise_openrouter_error,
|
|
@@ -1247,10 +1248,14 @@ class OpenRouterGenerationProvider(GenerationProvider):
|
|
|
1247
1248
|
),
|
|
1248
1249
|
)
|
|
1249
1250
|
|
|
1250
|
-
# Convert messages
|
|
1251
|
-
openrouter_messages = [
|
|
1252
|
-
|
|
1253
|
-
|
|
1251
|
+
# Convert messages - adapter may return single message or list of messages
|
|
1252
|
+
openrouter_messages: list[OpenRouterMessage] = []
|
|
1253
|
+
for message in messages_list:
|
|
1254
|
+
adapted = self.message_adapter.adapt(message)
|
|
1255
|
+
if isinstance(adapted, list):
|
|
1256
|
+
openrouter_messages.extend(adapted)
|
|
1257
|
+
else:
|
|
1258
|
+
openrouter_messages.append(adapted)
|
|
1254
1259
|
|
|
1255
1260
|
# Convert tools if provided
|
|
1256
1261
|
openrouter_tools = (
|
|
@@ -1404,10 +1409,14 @@ class OpenRouterGenerationProvider(GenerationProvider):
|
|
|
1404
1409
|
"""
|
|
1405
1410
|
_generation_config = self._normalize_generation_config(generation_config)
|
|
1406
1411
|
|
|
1407
|
-
# Convert messages
|
|
1408
|
-
openrouter_messages = [
|
|
1409
|
-
|
|
1410
|
-
|
|
1412
|
+
# Convert messages - adapter may return single message or list of messages
|
|
1413
|
+
openrouter_messages: list[OpenRouterMessage] = []
|
|
1414
|
+
for message in messages:
|
|
1415
|
+
adapted = self.message_adapter.adapt(message)
|
|
1416
|
+
if isinstance(adapted, list):
|
|
1417
|
+
openrouter_messages.extend(adapted)
|
|
1418
|
+
else:
|
|
1419
|
+
openrouter_messages.append(adapted)
|
|
1411
1420
|
|
|
1412
1421
|
# Convert tools if provided
|
|
1413
1422
|
openrouter_tools = (
|
|
@@ -137,7 +137,7 @@ agentle/agents/ui/__init__.py,sha256=IjHRV0k2DNwvFrEHebmsXiBvmITE8nQUnsR07h9tVkU
|
|
|
137
137
|
agentle/agents/ui/streamlit.py,sha256=9afICL0cxtG1o2pWh6vH39-NdKiVfADKiXo405F2aB0,42829
|
|
138
138
|
agentle/agents/whatsapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
139
139
|
agentle/agents/whatsapp/human_delay_calculator.py,sha256=BGCDeoNTPsMn4d_QYmG0BWGCG8SiUJC6Fk295ulAsAk,18268
|
|
140
|
-
agentle/agents/whatsapp/whatsapp_bot.py,sha256=
|
|
140
|
+
agentle/agents/whatsapp/whatsapp_bot.py,sha256=tF35s2c4G9Fo0bLVmePYXWSnSNEgg-Rpi9V0MrrRCCA,164948
|
|
141
141
|
agentle/agents/whatsapp/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
142
|
agentle/agents/whatsapp/models/audio_message.py,sha256=kUqG1HdNW6DCYD-CqscJ6WHlAyv9ufmTSKMdjio9XWk,2705
|
|
143
143
|
agentle/agents/whatsapp/models/context_info.py,sha256=sk80KuNE36S6VRnLh7n6UXmzZCXIB4E4lNxnRyVizg8,563
|
|
@@ -355,9 +355,9 @@ agentle/generations/providers/openrouter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
|
|
|
355
355
|
agentle/generations/providers/openrouter/_types.py,sha256=VSAf1auxopTqhYwLHPWl_Q0-okWUfLKJa0JmWlAFuGg,10557
|
|
356
356
|
agentle/generations/providers/openrouter/error_handler.py,sha256=4qm8v_cjdrB-59UXyCJLnIkOxzIfmsltZY8Q137-8Qg,8075
|
|
357
357
|
agentle/generations/providers/openrouter/exceptions.py,sha256=o3_-tuyhewc0v5L2cAXH0f9ixHyyDgZbHq0KX5cUyPE,23179
|
|
358
|
-
agentle/generations/providers/openrouter/openrouter_generation_provider.py,sha256=
|
|
358
|
+
agentle/generations/providers/openrouter/openrouter_generation_provider.py,sha256=XhXU_Ix10jmWl2SzRUHwsVSPRIWq6zHnia7IMaY6Yy4,67129
|
|
359
359
|
agentle/generations/providers/openrouter/_adapters/__init__.py,sha256=orgZeEBqH4X_cpyOMiClvfZHY5cLwLNqhAYLqNjGIH4,1826
|
|
360
|
-
agentle/generations/providers/openrouter/_adapters/agentle_message_to_openrouter_message_adapter.py,sha256=
|
|
360
|
+
agentle/generations/providers/openrouter/_adapters/agentle_message_to_openrouter_message_adapter.py,sha256=IvaovVP0ChYEreysiDyqF8SXdXxnD_Y9MjA2raEoncw,9790
|
|
361
361
|
agentle/generations/providers/openrouter/_adapters/agentle_part_to_openrouter_part_adapter.py,sha256=eIfwQQ9BokHy3FQ90GJ4i_J3L46XCiSBd1RWnzu-gAo,5967
|
|
362
362
|
agentle/generations/providers/openrouter/_adapters/agentle_tool_to_openrouter_tool_adapter.py,sha256=41i3B6awaTNZsdQ46Oi1e10cuNhQqWL-KBJ5V_sHkiI,9547
|
|
363
363
|
agentle/generations/providers/openrouter/_adapters/openrouter_message_to_generated_assistant_message_adapter.py,sha256=uB4TYc0fuwsoYdRnEnLhbwQISyaZ2Z2RWkPFGQXUc80,5295
|
|
@@ -1018,7 +1018,7 @@ agentle/web/actions/scroll.py,sha256=WqVVAORNDK3BL1oASZBPmXJYeSVkPgAOmWA8ibYO82I
|
|
|
1018
1018
|
agentle/web/actions/viewport.py,sha256=KCwm88Pri19Qc6GLHC69HsRxmdJz1gEEAODfggC_fHo,287
|
|
1019
1019
|
agentle/web/actions/wait.py,sha256=IKEywjf-KC4ni9Gkkv4wgc7bY-hk7HwD4F-OFWlyf2w,571
|
|
1020
1020
|
agentle/web/actions/write_text.py,sha256=9mxfHcpKs_L7BsDnJvOYHQwG8M0GWe61SRJAsKk3xQ8,748
|
|
1021
|
-
agentle-0.9.
|
|
1022
|
-
agentle-0.9.
|
|
1023
|
-
agentle-0.9.
|
|
1024
|
-
agentle-0.9.
|
|
1021
|
+
agentle-0.9.36.dist-info/METADATA,sha256=hSUSyQ9vk4kdTs91_EW7Yb1tEi41flD2JGVlqugY4YA,86849
|
|
1022
|
+
agentle-0.9.36.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1023
|
+
agentle-0.9.36.dist-info/licenses/LICENSE,sha256=T90S9vqRS6qP-voULxAcvwEs558wRRo6dHuZrjgcOUI,1085
|
|
1024
|
+
agentle-0.9.36.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|