agentle 0.9.22__py3-none-any.whl → 0.9.24__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/agent.py CHANGED
@@ -1733,6 +1733,9 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
1733
1733
  parsed=generation_chunk.parsed
1734
1734
  if hasattr(generation_chunk, "parsed")
1735
1735
  else cast(T_Schema, None),
1736
+ generation_text=generation_chunk.text
1737
+ if generation_chunk
1738
+ else "",
1736
1739
  is_streaming_chunk=True,
1737
1740
  is_final_chunk=False,
1738
1741
  performance_metrics=partial_metrics,
@@ -1837,6 +1840,9 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
1837
1840
  generation=final_generation,
1838
1841
  context=context,
1839
1842
  parsed=final_generation.parsed,
1843
+ generation_text=final_generation.text
1844
+ if final_generation
1845
+ else "",
1840
1846
  is_streaming_chunk=False,
1841
1847
  is_final_chunk=True,
1842
1848
  performance_metrics=performance_metrics,
@@ -1953,6 +1959,7 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
1953
1959
  generation=generation,
1954
1960
  context=context,
1955
1961
  parsed=generation.parsed,
1962
+ generation_text=generation.text if generation else "",
1956
1963
  performance_metrics=performance_metrics,
1957
1964
  )
1958
1965
 
@@ -2152,6 +2159,9 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
2152
2159
  parsed=generation_chunk.parsed
2153
2160
  if hasattr(generation_chunk, "parsed")
2154
2161
  else cast(T_Schema, None),
2162
+ generation_text=generation_chunk.text
2163
+ if generation_chunk
2164
+ else "",
2155
2165
  is_streaming_chunk=True,
2156
2166
  is_final_chunk=False,
2157
2167
  performance_metrics=partial_metrics,
@@ -2698,8 +2708,9 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
2698
2708
  generation=None,
2699
2709
  context=context,
2700
2710
  parsed=cast(T_Schema, None),
2711
+ generation_text="",
2701
2712
  is_suspended=True,
2702
- suspension_reason=suspension_error.reason,
2713
+ suspension_reason=suspension_reason,
2703
2714
  resumption_token=resumption_token,
2704
2715
  performance_metrics=performance_metrics,
2705
2716
  )
@@ -3065,7 +3076,8 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
3065
3076
  step_metrics=step_metrics,
3066
3077
  average_generation_time_ms=generation_time_total
3067
3078
  / max(
3068
- 1, len([s for s in step_metrics if s.step_type == "generation"])
3079
+ 1,
3080
+ len([s for s in step_metrics if s.step_type == "generation"]),
3069
3081
  ),
3070
3082
  average_tool_execution_time_ms=tool_execution_time_total
3071
3083
  / max(1, tool_calls_count),
@@ -3377,6 +3389,7 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
3377
3389
  generation=None,
3378
3390
  context=context,
3379
3391
  parsed=cast(T_Schema, None),
3392
+ generation_text="",
3380
3393
  is_suspended=True,
3381
3394
  suspension_reason=suspension_error.reason,
3382
3395
  resumption_token=resumption_token,
@@ -3802,7 +3815,9 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
3802
3815
  return Context(
3803
3816
  message_history=[
3804
3817
  developer_message,
3805
- UserMessage(parts=[TextPart(text=f"```json\n{text}\n```")]),
3818
+ UserMessage(
3819
+ parts=[TextPart(text=f"```json\n{text}\n```")],
3820
+ ),
3806
3821
  ]
3807
3822
  )
3808
3823
  except (ImportError, AttributeError):
@@ -3986,8 +4001,24 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
3986
4001
  generation=None,
3987
4002
  context=context,
3988
4003
  parsed=cast(T_Schema, None),
3989
- is_suspended=False,
3990
- suspension_reason=denial_message,
4004
+ generation_text="",
4005
+ performance_metrics=PerformanceMetrics(
4006
+ total_execution_time_ms=0.0,
4007
+ input_processing_time_ms=0.0,
4008
+ static_knowledge_processing_time_ms=0.0,
4009
+ mcp_tools_preparation_time_ms=0.0,
4010
+ generation_time_ms=0.0,
4011
+ tool_execution_time_ms=0.0,
4012
+ final_response_processing_time_ms=0.0,
4013
+ iteration_count=0,
4014
+ tool_calls_count=0,
4015
+ total_tokens_processed=0,
4016
+ cache_hit_rate=0.0,
4017
+ average_generation_time_ms=0.0,
4018
+ average_tool_execution_time_ms=0.0,
4019
+ longest_step_duration_ms=0.0,
4020
+ shortest_step_duration_ms=0.0,
4021
+ ),
3991
4022
  )
3992
4023
 
3993
4024
  try:
@@ -4010,7 +4041,24 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4010
4041
  generation=None,
4011
4042
  context=context,
4012
4043
  parsed=cast(T_Schema, None),
4013
- is_suspended=False,
4044
+ generation_text="",
4045
+ performance_metrics=PerformanceMetrics(
4046
+ total_execution_time_ms=0.0,
4047
+ input_processing_time_ms=0.0,
4048
+ static_knowledge_processing_time_ms=0.0,
4049
+ mcp_tools_preparation_time_ms=0.0,
4050
+ generation_time_ms=0.0,
4051
+ tool_execution_time_ms=0.0,
4052
+ final_response_processing_time_ms=0.0,
4053
+ iteration_count=0,
4054
+ tool_calls_count=0,
4055
+ total_tokens_processed=0,
4056
+ cache_hit_rate=0.0,
4057
+ average_generation_time_ms=0.0,
4058
+ average_tool_execution_time_ms=0.0,
4059
+ longest_step_duration_ms=0.0,
4060
+ shortest_step_duration_ms=0.0,
4061
+ ),
4014
4062
  )
4015
4063
 
4016
4064
  suspension_type = suspension_state.get("type", "unknown")
@@ -4050,8 +4098,24 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4050
4098
  generation=None,
4051
4099
  context=context,
4052
4100
  parsed=cast(T_Schema, None),
4053
- is_suspended=False,
4054
- suspension_reason=error_message,
4101
+ generation_text="",
4102
+ performance_metrics=PerformanceMetrics(
4103
+ total_execution_time_ms=0.0,
4104
+ input_processing_time_ms=0.0,
4105
+ static_knowledge_processing_time_ms=0.0,
4106
+ mcp_tools_preparation_time_ms=0.0,
4107
+ generation_time_ms=0.0,
4108
+ tool_execution_time_ms=0.0,
4109
+ final_response_processing_time_ms=0.0,
4110
+ iteration_count=0,
4111
+ tool_calls_count=0,
4112
+ total_tokens_processed=0,
4113
+ cache_hit_rate=0.0,
4114
+ average_generation_time_ms=0.0,
4115
+ average_tool_execution_time_ms=0.0,
4116
+ longest_step_duration_ms=0.0,
4117
+ shortest_step_duration_ms=0.0,
4118
+ ),
4055
4119
  )
4056
4120
 
4057
4121
  async def _resume_from_tool_suspension(
@@ -4063,6 +4127,7 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4063
4127
  This handles the most common suspension scenario where a tool
4064
4128
  raised ToolSuspensionError and required approval.
4065
4129
  """
4130
+ execution_start_time = time.perf_counter()
4066
4131
  _logger = Maybe(logger if self.debug else None)
4067
4132
 
4068
4133
  # Extract suspension state
@@ -4198,9 +4263,30 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4198
4263
  generation=None,
4199
4264
  context=context,
4200
4265
  parsed=cast(T_Schema, None),
4266
+ generation_text="",
4201
4267
  is_suspended=True,
4202
4268
  suspension_reason=suspension_error.reason,
4203
4269
  resumption_token=resumption_token,
4270
+ performance_metrics=PerformanceMetrics(
4271
+ total_execution_time_ms=(time.perf_counter() - execution_start_time)
4272
+ * 1000
4273
+ if execution_start_time
4274
+ else 24,
4275
+ input_processing_time_ms=0.0,
4276
+ static_knowledge_processing_time_ms=0.0,
4277
+ mcp_tools_preparation_time_ms=0.0,
4278
+ generation_time_ms=0.0,
4279
+ tool_execution_time_ms=0.0,
4280
+ final_response_processing_time_ms=0.0,
4281
+ iteration_count=0,
4282
+ tool_calls_count=0,
4283
+ total_tokens_processed=0,
4284
+ cache_hit_rate=0.0,
4285
+ average_generation_time_ms=0.0,
4286
+ average_tool_execution_time_ms=0.0,
4287
+ longest_step_duration_ms=0.0,
4288
+ shortest_step_duration_ms=0.0,
4289
+ ),
4204
4290
  )
4205
4291
  except Exception as e:
4206
4292
  # Tool execution failed
@@ -4232,8 +4318,24 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4232
4318
  generation=None,
4233
4319
  context=context,
4234
4320
  parsed=cast(T_Schema, None),
4235
- is_suspended=False,
4236
- suspension_reason=error_message,
4321
+ generation_text="",
4322
+ performance_metrics=PerformanceMetrics(
4323
+ total_execution_time_ms=0.0,
4324
+ input_processing_time_ms=0.0,
4325
+ static_knowledge_processing_time_ms=0.0,
4326
+ mcp_tools_preparation_time_ms=0.0,
4327
+ generation_time_ms=0.0,
4328
+ tool_execution_time_ms=0.0,
4329
+ final_response_processing_time_ms=0.0,
4330
+ iteration_count=0,
4331
+ tool_calls_count=0,
4332
+ total_tokens_processed=0,
4333
+ cache_hit_rate=0.0,
4334
+ average_generation_time_ms=0.0,
4335
+ average_tool_execution_time_ms=0.0,
4336
+ longest_step_duration_ms=0.0,
4337
+ shortest_step_duration_ms=0.0,
4338
+ ),
4237
4339
  )
4238
4340
 
4239
4341
  # Complete the step and add to context
@@ -4319,6 +4421,26 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4319
4421
  generation=generation,
4320
4422
  context=context,
4321
4423
  parsed=generation.parsed,
4424
+ generation_text=generation.text if generation else "",
4425
+ performance_metrics=PerformanceMetrics(
4426
+ total_execution_time_ms=0.0,
4427
+ input_processing_time_ms=0.0,
4428
+ static_knowledge_processing_time_ms=0.0,
4429
+ mcp_tools_preparation_time_ms=0.0,
4430
+ generation_time_ms=0.0,
4431
+ tool_execution_time_ms=0.0,
4432
+ final_response_processing_time_ms=0.0,
4433
+ iteration_count=1,
4434
+ tool_calls_count=0,
4435
+ total_tokens_processed=generation.usage.total_tokens
4436
+ if generation
4437
+ else 0,
4438
+ cache_hit_rate=0.0,
4439
+ average_generation_time_ms=0.0,
4440
+ average_tool_execution_time_ms=0.0,
4441
+ longest_step_duration_ms=0.0,
4442
+ shortest_step_duration_ms=0.0,
4443
+ ),
4322
4444
  )
4323
4445
 
4324
4446
  # Has tools, continue with tool execution loop
@@ -4349,6 +4471,7 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4349
4471
  This method continues the standard tool execution loop from where
4350
4472
  the agent left off, handling iterations and tool calls.
4351
4473
  """
4474
+ execution_start_time = time.perf_counter()
4352
4475
  _logger = Maybe(logger if self.debug else None)
4353
4476
  generation_provider = self.generation_provider
4354
4477
 
@@ -4473,6 +4596,26 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4473
4596
  generation=generation,
4474
4597
  context=context,
4475
4598
  parsed=generation.parsed,
4599
+ generation_text=generation.text if generation else "",
4600
+ performance_metrics=PerformanceMetrics(
4601
+ total_execution_time_ms=0.0,
4602
+ input_processing_time_ms=0.0,
4603
+ static_knowledge_processing_time_ms=0.0,
4604
+ mcp_tools_preparation_time_ms=0.0,
4605
+ generation_time_ms=0.0,
4606
+ tool_execution_time_ms=0.0,
4607
+ final_response_processing_time_ms=0.0,
4608
+ iteration_count=1,
4609
+ tool_calls_count=0,
4610
+ total_tokens_processed=generation.usage.total_tokens
4611
+ if generation
4612
+ else 0,
4613
+ cache_hit_rate=0.0,
4614
+ average_generation_time_ms=0.0,
4615
+ average_tool_execution_time_ms=0.0,
4616
+ longest_step_duration_ms=0.0,
4617
+ shortest_step_duration_ms=0.0,
4618
+ ),
4476
4619
  )
4477
4620
 
4478
4621
  # Execute tools
@@ -4538,9 +4681,30 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4538
4681
  generation=None,
4539
4682
  context=context,
4540
4683
  parsed=cast(T_Schema, None),
4684
+ generation_text="",
4541
4685
  is_suspended=True,
4542
4686
  suspension_reason=suspension_error.reason,
4543
4687
  resumption_token=resumption_token,
4688
+ performance_metrics=PerformanceMetrics(
4689
+ total_execution_time_ms=(
4690
+ time.perf_counter() - execution_start_time
4691
+ )
4692
+ * 1000,
4693
+ input_processing_time_ms=0.0,
4694
+ static_knowledge_processing_time_ms=0.0,
4695
+ mcp_tools_preparation_time_ms=0.0,
4696
+ generation_time_ms=0.0,
4697
+ tool_execution_time_ms=0.0,
4698
+ final_response_processing_time_ms=0.0,
4699
+ iteration_count=current_iteration,
4700
+ tool_calls_count=0,
4701
+ total_tokens_processed=0,
4702
+ cache_hit_rate=0.0,
4703
+ average_generation_time_ms=0.0,
4704
+ average_tool_execution_time_ms=0.0,
4705
+ longest_step_duration_ms=0.0,
4706
+ shortest_step_duration_ms=0.0,
4707
+ ),
4544
4708
  )
4545
4709
 
4546
4710
  # Complete step and continue
@@ -4625,6 +4789,7 @@ class Agent[T_Schema = WithoutStructuredOutput](BaseModel):
4625
4789
  generation=generation,
4626
4790
  context=context,
4627
4791
  parsed=parsed,
4792
+ generation_text=generation.text if generation else "",
4628
4793
  performance_metrics=performance_metrics,
4629
4794
  )
4630
4795
 
@@ -87,7 +87,14 @@ class AgentRunOutput[T_StructuredOutput](BaseModel):
87
87
  parsed: T_StructuredOutput
88
88
  """
89
89
  Structured data extracted from the agent's response.
90
- In streaming mode, only available in the final chunk.
90
+ In streaming mode, contains incrementally parsed partial data in each chunk,
91
+ with complete data available in the final chunk.
92
+ """
93
+
94
+ generation_text: str = Field(default="")
95
+ """
96
+ The text response from the agent.
97
+ Returns empty string if execution is suspended or generation is None.
91
98
  """
92
99
 
93
100
  is_suspended: bool = Field(default=False)
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import datetime
4
4
  import logging
5
5
  import uuid
6
- from collections.abc import AsyncIterator
6
+ from collections.abc import AsyncGenerator, AsyncIterator
7
7
  from logging import Logger
8
8
  from typing import TYPE_CHECKING, Any, Literal, cast, overload
9
9
 
@@ -88,11 +88,11 @@ class GenerateGenerateContentResponseToGenerationAdapter[T](
88
88
  @overload
89
89
  def adapt(
90
90
  self, _f: AsyncIterator["GenerateContentResponse"]
91
- ) -> AsyncIterator[Generation[T]]: ...
91
+ ) -> AsyncGenerator[Generation[T], None]: ...
92
92
 
93
93
  def adapt(
94
94
  self, _f: "GenerateContentResponse | AsyncIterator[GenerateContentResponse]"
95
- ) -> Generation[T] | AsyncIterator[Generation[T]]:
95
+ ) -> Generation[T] | AsyncGenerator[Generation[T], None]:
96
96
  """
97
97
  Convert Google response(s) to Agentle Generation object(s).
98
98
 
@@ -214,7 +214,7 @@ class GenerateGenerateContentResponseToGenerationAdapter[T](
214
214
 
215
215
  async def _adapt_streaming(
216
216
  self, response_stream: AsyncIterator["GenerateContentResponse"]
217
- ) -> AsyncIterator[Generation[T]]:
217
+ ) -> AsyncGenerator[Generation[T], None]:
218
218
  """Adapt a streaming response with proper text accumulation."""
219
219
  generation_id = self.preferred_id or uuid.uuid4()
220
220
  created_time = datetime.datetime.now()
@@ -253,16 +253,19 @@ class GenerateGenerateContentResponseToGenerationAdapter[T](
253
253
  _all_parts.extend(_parts)
254
254
 
255
255
  if _optional_model is not None:
256
- optional_model = parse_streaming_json(
257
- "".join([str(p.text) for p in _all_parts]),
256
+ # Parse streaming JSON and update final_parsed
257
+ accumulated_json_text = "".join([str(p.text) for p in _all_parts])
258
+ parsed_optional_model = parse_streaming_json(
259
+ accumulated_json_text,
258
260
  model=_optional_model,
259
261
  )
260
- chunk.parsed = optional_model
262
+ # Cast the optional model back to T for use in the generation
263
+ final_parsed = cast(T, parsed_optional_model)
261
264
  else:
262
- chunk.parsed = None
265
+ final_parsed = None
263
266
 
264
- # Extract parsed data (usually only available in final chunk)
265
- if hasattr(chunk, "parsed") and chunk.parsed is not None:
267
+ # Also check if chunk has parsed attribute from Google API
268
+ elif hasattr(chunk, "parsed") and chunk.parsed is not None:
266
269
  final_parsed = cast(T | None, chunk.parsed)
267
270
 
268
271
  # Extract usage (usually only in final chunk)
@@ -182,6 +182,37 @@ class GoogleGenerationProvider(GenerationProvider):
182
182
  """
183
183
  return "google"
184
184
 
185
+ @overload
186
+ def stream_async[T](
187
+ self,
188
+ *,
189
+ model: str | ModelKind | None = None,
190
+ messages: Sequence[Message],
191
+ response_schema: type[T],
192
+ generation_config: GenerationConfig | GenerationConfigDict | None = None,
193
+ ) -> AsyncGenerator[Generation[T], None]: ...
194
+
195
+ @overload
196
+ def stream_async(
197
+ self,
198
+ *,
199
+ model: str | ModelKind | None = None,
200
+ messages: Sequence[Message],
201
+ response_schema: None = None,
202
+ generation_config: GenerationConfig | GenerationConfigDict | None = None,
203
+ tools: Sequence[Tool],
204
+ ) -> AsyncGenerator[Generation[WithoutStructuredOutput], None]: ...
205
+
206
+ @overload
207
+ def stream_async(
208
+ self,
209
+ *,
210
+ model: str | ModelKind | None = None,
211
+ messages: Sequence[Message],
212
+ response_schema: None = None,
213
+ generation_config: GenerationConfig | GenerationConfigDict | None = None,
214
+ ) -> AsyncGenerator[Generation[WithoutStructuredOutput], None]: ...
215
+
185
216
  async def stream_async[T = WithoutStructuredOutput](
186
217
  self,
187
218
  *,
@@ -190,7 +221,7 @@ class GoogleGenerationProvider(GenerationProvider):
190
221
  response_schema: type[T] | None = None,
191
222
  generation_config: GenerationConfig | GenerationConfigDict | None = None,
192
223
  tools: Sequence[Tool] | None = None,
193
- ) -> AsyncGenerator[Generation[WithoutStructuredOutput], None]:
224
+ ) -> AsyncGenerator[Generation[T], None]:
194
225
  from google.genai import types
195
226
 
196
227
  if self._normalize_generation_config(generation_config).n > 1:
@@ -260,6 +291,7 @@ class GoogleGenerationProvider(GenerationProvider):
260
291
  tools=_tools,
261
292
  max_output_tokens=_generation_config.max_output_tokens,
262
293
  response_schema=response_schema if bool(response_schema) else None,
294
+ response_mime_type="application/json" if bool(response_schema) else None,
263
295
  automatic_function_calling=types.AutomaticFunctionCallingConfig(
264
296
  disable=disable_function_calling,
265
297
  maximum_remote_calls=maximum_remote_calls,
@@ -295,10 +327,8 @@ class GoogleGenerationProvider(GenerationProvider):
295
327
  raise
296
328
 
297
329
  # Create the response
298
- response = GenerateGenerateContentResponseToGenerationAdapter[
299
- WithoutStructuredOutput
300
- ](
301
- response_schema=None,
330
+ response = GenerateGenerateContentResponseToGenerationAdapter[T](
331
+ response_schema=response_schema,
302
332
  model=used_model,
303
333
  ).adapt(generate_content_response_stream)
304
334
 
@@ -21,8 +21,6 @@ def parse_streaming_json[T: BaseModel](potential_json: str | None, model: type[T
21
21
  if potential_json is None:
22
22
  return model()
23
23
 
24
- # print(f"parsing: {potential_json}")
25
-
26
24
  def find_json_boundaries(text: str) -> tuple[int, int]:
27
25
  """Find the start and potential end of JSON in the text."""
28
26
 
@@ -95,17 +93,32 @@ def parse_streaming_json[T: BaseModel](potential_json: str | None, model: type[T
95
93
  # Remove any leading/trailing whitespace
96
94
  json_str = json_str.strip()
97
95
 
98
- # Fix missing closing quotes on string values (at the end)
99
- # Look for patterns like: "key": "value without closing quote
100
- json_str = re.sub(r':\s*"([^"]*?)(?:\s*[,}]|$)', r': "\1"', json_str)
101
-
102
- # Fix missing closing quotes for keys
103
- # Look for patterns like: "key without quotes:
104
- json_str = re.sub(r'"([^"]*?)(?=\s*:)', r'"\1"', json_str)
105
-
106
96
  # Remove trailing commas before closing braces/brackets
107
97
  json_str = re.sub(r",\s*([}\]])", r"\1", json_str)
108
98
 
99
+ # For streaming JSON, we need to handle incomplete strings carefully
100
+ # Check if we have an unclosed string at the end
101
+ in_string = False
102
+ escape_next = False
103
+ last_quote_pos = -1
104
+
105
+ for i, char in enumerate(json_str):
106
+ if escape_next:
107
+ escape_next = False
108
+ continue
109
+ if char == '\\':
110
+ escape_next = True
111
+ continue
112
+ if char == '"':
113
+ in_string = not in_string
114
+ if in_string:
115
+ last_quote_pos = i
116
+
117
+ # If we're in a string at the end (incomplete), close it properly
118
+ if in_string and last_quote_pos != -1:
119
+ # Add closing quote for the incomplete string
120
+ json_str += '"'
121
+
109
122
  # Ensure the JSON has proper closing braces if it appears incomplete
110
123
  open_braces = json_str.count("{") - json_str.count("}")
111
124
  open_brackets = json_str.count("[") - json_str.count("]")
@@ -124,12 +137,25 @@ def parse_streaming_json[T: BaseModel](potential_json: str | None, model: type[T
124
137
  data = {}
125
138
 
126
139
  # Extract string key-value pairs with quoted keys
127
- # Pattern: "key": "value" or 'key': 'value'
128
- string_pattern = r'["\']([^"\']+)["\']:\s*["\']([^"\']*)["\']?'
129
- string_matches = re.findall(string_pattern, json_str)
140
+ # IMPROVED: Handle long strings that may contain newlines, special chars, etc.
141
+ # Pattern: "key": "value..." - capture everything until the next unescaped quote or EOF
142
+ string_pattern = r'["\']([\w]+)["\']:\s*["\']([^"\']*?)(?:["\']|$)'
143
+ string_matches = re.findall(string_pattern, json_str, re.DOTALL)
144
+
145
+ # Also try to capture very long strings that span multiple lines
146
+ # This catches incomplete strings during streaming
147
+ long_string_pattern = r'["\']([\w_]+)["\']:\s*["\'](.+?)(?:["\'],?\s*["}]|$)'
148
+ long_matches = re.findall(long_string_pattern, json_str, re.DOTALL)
130
149
 
131
150
  for key, value in string_matches:
132
151
  data[key] = value
152
+
153
+ # Prefer long_matches for fields that might be truncated in string_matches
154
+ for key, value in long_matches:
155
+ # Only override if the long match has more content
156
+ existing = data.get(key, "")
157
+ if key not in data or (isinstance(existing, str) and len(value) > len(existing)):
158
+ data[key] = value
133
159
 
134
160
  # Extract string key-value pairs with unquoted keys
135
161
  # Pattern: key: "value" (no quotes around key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentle
3
- Version: 0.9.22
3
+ Version: 0.9.24
4
4
  Summary: ...
5
5
  Author-email: Arthur Brenno <64020210+arthurbrenno@users.noreply.github.com>
6
6
  License-File: LICENSE
@@ -1,13 +1,13 @@
1
1
  agentle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  agentle/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  agentle/agents/__init__.py,sha256=Y_Vh4954BlEehGOufH79lCvWlgDaVPN6TF9bo4OXioY,10
4
- agentle/agents/agent.py,sha256=jNMLHAQy8Ywv9ZXGzKkLP3uvcSyO3brDoYZxRipBCPw,205204
4
+ agentle/agents/agent.py,sha256=e5tiq87bBfGI8gp5FU8mIVlxEs81GUpg-JTkptA0EeU,213449
5
5
  agentle/agents/agent_config.py,sha256=tkssVo-cmldiQ6rDDkJ2xeK5NRIG3F-NgVhlXZGwCbM,4555
6
6
  agentle/agents/agent_config_dict.py,sha256=BzGyKdm0Hv86P0_r7xzK4HtzNVdwM0HtrtuDVCSGbI8,2813
7
7
  agentle/agents/agent_input.py,sha256=qckT3_pOGHclT8UViOjUeKCuGo0qigB_h6tYxOtwTXs,1447
8
8
  agentle/agents/agent_pipeline.py,sha256=YJAMoHUfcChiwYLwe_vTXaS2QCkgq3DAThsjb0WxA_A,17160
9
9
  agentle/agents/agent_protocol.py,sha256=7UTKSLwoM37zgWNWZxSPgEpBctPAxA4Zqiz4qiq6yCM,771
10
- agentle/agents/agent_run_output.py,sha256=O2WcdJW9rEBiQGvMAoTTpfiRtHQpOHWLkapWTEHeSE0,22419
10
+ agentle/agents/agent_run_output.py,sha256=UsFT48FsPlzWT8aQgNfajkt7IjXBbugI9EbRaXQ40tA,22669
11
11
  agentle/agents/agent_team.py,sha256=bUQzLMhOp2UXmj1XhevqB0ZBqqzkhrTAUNV3BwSv7hc,30193
12
12
  agentle/agents/context.py,sha256=ap8gSTqiBvYGrAegPOE5O2rz_dWPbiAgOJ5uahi1e0M,16391
13
13
  agentle/agents/context_state.py,sha256=4EMxfuRvmAkNbH1ImdoP7MDFQSm9NYORsaCSaAicus0,135
@@ -316,10 +316,10 @@ agentle/generations/providers/failover/__init__.py,sha256=VBv_ZZlBFwwBRD2PZUEKi2
316
316
  agentle/generations/providers/failover/failover_generation_provider.py,sha256=ixZk1zAP_Gu3HTf6nSo_ZDTl91U6x8vR0AyNHKB2b8k,24562
317
317
  agentle/generations/providers/google/__init__.py,sha256=6H9LKSrN7Xyz5ZJ3PI4DIwOsAi8A84YoLSO9ms67cYI,105
318
318
  agentle/generations/providers/google/function_calling_config.py,sha256=5ZL1FDCfeVVe9a04BpHC0_nBlLhmWg9nGoHVvCXNcI4,1226
319
- agentle/generations/providers/google/google_generation_provider.py,sha256=zWpD2JdaV75czsw8ngGcAiXbzYzTwXoJg3TBrUetm5w,24540
319
+ agentle/generations/providers/google/google_generation_provider.py,sha256=9Dl8D7npzpHL7qwO7Mgqiu6fzmWzPh-7jfvB6ZuUdYI,25590
320
320
  agentle/generations/providers/google/adapters/__init__.py,sha256=sUwLR-CDzvWHn6hwqSsHYztws6DGx6ZiKp32vYeVMfk,1003
321
321
  agentle/generations/providers/google/adapters/agentle_tool_to_google_tool_adapter.py,sha256=XQM_aL5zVfrRbNwtO_lDwXqvT8iX83trDNbEPSjZLmI,28482
322
- agentle/generations/providers/google/adapters/generate_generate_content_response_to_generation_adapter.py,sha256=PCm0imfvMFssMENrQGbHJKSOG1hOqMyilX3Eo2PO3Ls,16960
322
+ agentle/generations/providers/google/adapters/generate_generate_content_response_to_generation_adapter.py,sha256=akzh75PmFnRF3X4L0QmF-lRokOTB9PR0ZFBv7nPTB3A,17234
323
323
  agentle/generations/providers/google/adapters/google_content_to_generated_assistant_message_adapter.py,sha256=4sOcT9VLr58YfEnkOm392odhNzzmbTspnnmrz5K6bHc,7829
324
324
  agentle/generations/providers/google/adapters/google_part_to_part_adapter.py,sha256=infqHs_LVjpzIOipIVbRHQesXTKoOHVLiTUFaPhaBV8,6520
325
325
  agentle/generations/providers/google/adapters/message_to_google_content_adapter.py,sha256=sJ6MEn_R-KkLTEsNhxmKhBDbzlKwOUYqinydag6hqi4,6071
@@ -960,7 +960,7 @@ agentle/utils/file_validation.py,sha256=Rv6y_ylBrDoslscCk2lkfr3LRMarY0J8qWhKJjZj
960
960
  agentle/utils/fix_base64_padding.py,sha256=IWBlMP3FYcWs17Y72tVxG7w1lzEKnstaDC9HRKmI56Y,1109
961
961
  agentle/utils/make_fields_optional.py,sha256=pu2u-jJ1pNyoQRyGQzxwXnYbWSzZ2G4ssebJvjYsH8A,7800
962
962
  agentle/utils/needs.py,sha256=JRTVvgVP0w0bpq1_w2Jly2lxXiONdHOYAYMvReFQcJM,5110
963
- agentle/utils/parse_streaming_json.py,sha256=OTEfPhack0s-hgmYR2dNqTaE_SUG_bj9SbscjoIA96o,30710
963
+ agentle/utils/parse_streaming_json.py,sha256=9otgyfU37F-X9qbWHxgFmoYi9MSa9PPCarJ5CJ1Tcr0,31890
964
964
  agentle/utils/raise_error.py,sha256=EYX6WJS5gzJxgTyFYYOFHc-klEyU0tP20N5k_-ze5nQ,138
965
965
  agentle/utils/safe_b64decode.py,sha256=2nkdwUzkeVgtf3kb_zypjJY2KHe7dlLwa4ynjY2Yy1E,1272
966
966
  agentle/utils/safe_dill_dumps.py,sha256=yXQ51P05uSiWnREKJ6UWVjCKnRQTjgPVzoE1nylBbJI,237
@@ -1007,7 +1007,7 @@ agentle/web/actions/scroll.py,sha256=WqVVAORNDK3BL1oASZBPmXJYeSVkPgAOmWA8ibYO82I
1007
1007
  agentle/web/actions/viewport.py,sha256=KCwm88Pri19Qc6GLHC69HsRxmdJz1gEEAODfggC_fHo,287
1008
1008
  agentle/web/actions/wait.py,sha256=IKEywjf-KC4ni9Gkkv4wgc7bY-hk7HwD4F-OFWlyf2w,571
1009
1009
  agentle/web/actions/write_text.py,sha256=9mxfHcpKs_L7BsDnJvOYHQwG8M0GWe61SRJAsKk3xQ8,748
1010
- agentle-0.9.22.dist-info/METADATA,sha256=eUUPUWU9DqyKHa-Pixu0ooQVsOYfHMdGxG_ppF2C-VA,86849
1011
- agentle-0.9.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1012
- agentle-0.9.22.dist-info/licenses/LICENSE,sha256=T90S9vqRS6qP-voULxAcvwEs558wRRo6dHuZrjgcOUI,1085
1013
- agentle-0.9.22.dist-info/RECORD,,
1010
+ agentle-0.9.24.dist-info/METADATA,sha256=AiMToA7H-QGVxwlLP3F5mPygDmgFdS0R0_KcSX83NHo,86849
1011
+ agentle-0.9.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1012
+ agentle-0.9.24.dist-info/licenses/LICENSE,sha256=T90S9vqRS6qP-voULxAcvwEs558wRRo6dHuZrjgcOUI,1085
1013
+ agentle-0.9.24.dist-info/RECORD,,