grasp_agents 0.2.0__py3-none-any.whl → 0.2.2__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.
@@ -0,0 +1,38 @@
1
+ # pyright: reportUnusedImport=false
2
+
3
+
4
+ from .agent_message import AgentMessage
5
+ from .base_agent import BaseAgent
6
+ from .comm_agent import CommunicatingAgent
7
+ from .llm import LLM, LLMSettings
8
+ from .llm_agent import LLMAgent
9
+ from .run_context import RunArgs, RunContextWrapper
10
+ from .typing.completion import Completion
11
+ from .typing.content import Content, ImageData
12
+ from .typing.io import AgentID, AgentState, LLMFormattedArgs, LLMPrompt, LLMPromptArgs
13
+ from .typing.message import AssistantMessage, Conversation, SystemMessage, UserMessage
14
+ from .typing.tool import BaseTool
15
+
16
+ __all__ = [
17
+ "LLM",
18
+ "AgentID",
19
+ "AgentMessage",
20
+ "AgentState",
21
+ "AssistantMessage",
22
+ "BaseAgent",
23
+ "BaseTool",
24
+ "CommunicatingAgent",
25
+ "Completion",
26
+ "Content",
27
+ "Conversation",
28
+ "ImageData",
29
+ "LLMAgent",
30
+ "LLMFormattedArgs",
31
+ "LLMPrompt",
32
+ "LLMPromptArgs",
33
+ "LLMSettings",
34
+ "RunArgs",
35
+ "RunContextWrapper",
36
+ "SystemMessage",
37
+ "UserMessage",
38
+ ]
grasp_agents/cloud_llm.py CHANGED
@@ -286,6 +286,7 @@ class CloudLLM(LLM[SettingsT, ConvertT], Generic[SettingsT, ConvertT]):
286
286
  validate_obj_from_json_or_py_string(
287
287
  message.content,
288
288
  adapter=self._response_format_pyd,
289
+ from_substring=True,
289
290
  )
290
291
 
291
292
  async def generate_completion_stream(
grasp_agents/llm_agent.py CHANGED
@@ -10,7 +10,7 @@ from .comm_agent import CommunicatingAgent
10
10
  from .llm import LLM, LLMSettings
11
11
  from .llm_agent_state import (
12
12
  LLMAgentState,
13
- MakeCustomAgentState,
13
+ SetAgentState,
14
14
  SetAgentStateStrategy,
15
15
  )
16
16
  from .prompt_builder import (
@@ -25,7 +25,11 @@ from .run_context import (
25
25
  SystemRunArgs,
26
26
  UserRunArgs,
27
27
  )
28
- from .tool_orchestrator import ExitToolCallLoopHandler, ToolOrchestrator
28
+ from .tool_orchestrator import (
29
+ ExitToolCallLoopHandler,
30
+ ManageAgentStateHandler,
31
+ ToolOrchestrator,
32
+ )
29
33
  from .typing.content import ImageData
30
34
  from .typing.converters import Converters
31
35
  from .typing.io import (
@@ -43,9 +47,14 @@ from .typing.tool import BaseTool
43
47
  from .utils import get_prompt, validate_obj_from_json_or_py_string
44
48
 
45
49
 
46
- class ParseOutputHandler(Protocol[OutT, CtxT]):
50
+ class ParseOutputHandler(Protocol[InT, OutT, CtxT]):
47
51
  def __call__(
48
- self, *args: Any, ctx: RunContextWrapper[CtxT] | None, **kwargs: Any
52
+ self,
53
+ conversation: Conversation,
54
+ *,
55
+ rcv_args: InT | None,
56
+ batch_idx: int,
57
+ ctx: RunContextWrapper[CtxT] | None,
49
58
  ) -> OutT: ...
50
59
 
51
60
 
@@ -91,9 +100,15 @@ class LLMAgent(
91
100
  # Agent state
92
101
  self._state: LLMAgentState = LLMAgentState()
93
102
  self.set_state_strategy: SetAgentStateStrategy = set_state_strategy
94
- self._make_custom_agent_state_impl: MakeCustomAgentState | None = None
103
+ self._set_agent_state_impl: SetAgentState | None = None
95
104
 
96
105
  # Tool orchestrator
106
+
107
+ self._using_default_llm_response_format: bool = False
108
+ if llm.response_format is None and tools is None:
109
+ llm.response_format = self.out_type
110
+ self._using_default_llm_response_format = True
111
+
97
112
  self._tool_orchestrator: ToolOrchestrator[CtxT] = ToolOrchestrator[CtxT](
98
113
  agent_id=self.agent_id,
99
114
  llm=llm,
@@ -152,16 +167,28 @@ class LLMAgent(
152
167
  conversation: Conversation,
153
168
  *,
154
169
  rcv_args: InT | None = None,
170
+ batch_idx: int = 0,
155
171
  ctx: RunContextWrapper[CtxT] | None = None,
156
- **kwargs: Any,
157
172
  ) -> OutT:
158
173
  if self._parse_output_impl:
174
+ if self._using_default_llm_response_format:
175
+ # When using custom output parsing, the required LLM response format
176
+ # can differ from the final agent output type ->
177
+ # set it back to None unless it was specified explicitly at init.
178
+ self._tool_orchestrator.llm.response_format = None
179
+ self._using_default_llm_response_format = False
180
+
159
181
  return self._parse_output_impl(
160
- conversation=conversation, rcv_args=rcv_args, ctx=ctx, **kwargs
182
+ conversation=conversation,
183
+ rcv_args=rcv_args,
184
+ batch_idx=batch_idx,
185
+ ctx=ctx,
161
186
  )
162
187
 
163
188
  return validate_obj_from_json_or_py_string(
164
- str(conversation[-1].content), self._out_type_adapter
189
+ str(conversation[-1].content),
190
+ adapter=self._out_type_adapter,
191
+ from_substring=True,
165
192
  )
166
193
 
167
194
  @final
@@ -210,7 +237,7 @@ class LLMAgent(
210
237
  rcv_state=rcv_state,
211
238
  sys_prompt=formatted_sys_prompt,
212
239
  strategy=self.set_state_strategy,
213
- make_custom_state_impl=self._make_custom_agent_state_impl,
240
+ set_agent_state_impl=self._set_agent_state_impl,
214
241
  ctx=ctx,
215
242
  )
216
243
 
@@ -234,7 +261,7 @@ class LLMAgent(
234
261
  else:
235
262
  # 4. Run tool call loop (new messages are added to the message
236
263
  # history inside the loop)
237
- await self._tool_orchestrator.run_loop(agent_state=state, ctx=ctx)
264
+ await self._tool_orchestrator.run_loop(state=state, ctx=ctx)
238
265
 
239
266
  # 5. Parse outputs
240
267
  batch_size = state.message_history.batch_size
@@ -324,15 +351,13 @@ class LLMAgent(
324
351
  return func
325
352
 
326
353
  def parse_output_handler(
327
- self, func: ParseOutputHandler[OutT, CtxT]
328
- ) -> ParseOutputHandler[OutT, CtxT]:
354
+ self, func: ParseOutputHandler[InT, OutT, CtxT]
355
+ ) -> ParseOutputHandler[InT, OutT, CtxT]:
329
356
  self._parse_output_impl = func
330
357
 
331
358
  return func
332
359
 
333
- def make_custom_agent_state_handler(
334
- self, func: MakeCustomAgentState
335
- ) -> MakeCustomAgentState:
360
+ def set_agent_state_handler(self, func: SetAgentState) -> SetAgentState:
336
361
  self._make_custom_agent_state_impl = func
337
362
 
338
363
  return func
@@ -344,6 +369,13 @@ class LLMAgent(
344
369
 
345
370
  return func
346
371
 
372
+ def manage_agent_state_handler(
373
+ self, func: ManageAgentStateHandler[CtxT]
374
+ ) -> ManageAgentStateHandler[CtxT]:
375
+ self._tool_orchestrator.manage_agent_state_impl = func
376
+
377
+ return func
378
+
347
379
  # -- Override these methods in subclasses if needed --
348
380
 
349
381
  def _register_overridden_handlers(self) -> None:
@@ -356,15 +388,18 @@ class LLMAgent(
356
388
  if cur_cls._format_inp_args is not base_cls._format_inp_args: # noqa: SLF001
357
389
  self._prompt_builder.format_inp_args_impl = self._format_inp_args
358
390
 
359
- if cur_cls._make_custom_agent_state is not base_cls._make_custom_agent_state: # noqa: SLF001
360
- self._make_custom_agent_state_impl = self._make_custom_agent_state
391
+ if cur_cls._set_agent_state is not base_cls._set_agent_state: # noqa: SLF001
392
+ self._set_agent_state_impl = self._set_agent_state
393
+
394
+ if cur_cls._manage_agent_state is not base_cls._manage_agent_state: # noqa: SLF001
395
+ self._tool_orchestrator.manage_agent_state_impl = self._manage_agent_state
361
396
 
362
397
  if (
363
- cur_cls._tool_call_loop_exit is not base_cls._tool_call_loop_exit # noqa: SLF001
398
+ cur_cls._exit_tool_call_loop is not base_cls._exit_tool_call_loop # noqa: SLF001
364
399
  ):
365
- self._tool_orchestrator.exit_tool_call_loop_impl = self._tool_call_loop_exit
400
+ self._tool_orchestrator.exit_tool_call_loop_impl = self._exit_tool_call_loop
366
401
 
367
- self._parse_output_impl: ParseOutputHandler[OutT, CtxT] | None = None
402
+ self._parse_output_impl: ParseOutputHandler[InT, OutT, CtxT] | None = None
368
403
 
369
404
  def _format_sys_args(
370
405
  self,
@@ -379,26 +414,29 @@ class LLMAgent(
379
414
 
380
415
  def _format_inp_args(
381
416
  self,
417
+ *,
382
418
  usr_args: LLMPromptArgs,
383
419
  rcv_args: InT,
420
+ batch_idx: int = 0,
384
421
  ctx: RunContextWrapper[CtxT] | None = None,
385
422
  ) -> LLMFormattedArgs:
386
423
  raise NotImplementedError(
387
424
  "LLMAgent._format_inp_args must be overridden by a subclass"
388
425
  )
389
426
 
390
- def _make_custom_agent_state(
427
+ def _set_agent_state(
391
428
  self,
392
- cur_state: LLMAgentState | None,
429
+ cur_state: LLMAgentState,
430
+ *,
393
431
  rcv_state: AgentState | None,
394
432
  sys_prompt: LLMPrompt | None,
395
433
  ctx: RunContextWrapper[Any] | None,
396
434
  ) -> LLMAgentState:
397
435
  raise NotImplementedError(
398
- "LLMAgent._make_custom_agent_state_handler must be overridden by a subclass"
436
+ "LLMAgent._set_agent_state_handler must be overridden by a subclass"
399
437
  )
400
438
 
401
- def _tool_call_loop_exit(
439
+ def _exit_tool_call_loop(
402
440
  self,
403
441
  conversation: Conversation,
404
442
  *,
@@ -408,3 +446,14 @@ class LLMAgent(
408
446
  raise NotImplementedError(
409
447
  "LLMAgent._tool_call_loop_exit must be overridden by a subclass"
410
448
  )
449
+
450
+ def _manage_agent_state(
451
+ self,
452
+ state: LLMAgentState,
453
+ *,
454
+ ctx: RunContextWrapper[CtxT] | None = None,
455
+ **kwargs: Any,
456
+ ) -> None:
457
+ raise NotImplementedError(
458
+ "LLMAgent._manage_agent_state must be overridden by a subclass"
459
+ )
@@ -10,10 +10,11 @@ from .typing.io import AgentState, LLMPrompt
10
10
  SetAgentStateStrategy = Literal["keep", "reset", "from_sender", "custom"]
11
11
 
12
12
 
13
- class MakeCustomAgentState(Protocol):
13
+ class SetAgentState(Protocol):
14
14
  def __call__(
15
15
  self,
16
- cur_state: Optional["LLMAgentState"],
16
+ cur_state: "LLMAgentState",
17
+ *,
17
18
  rcv_state: AgentState | None,
18
19
  sys_prompt: LLMPrompt | None,
19
20
  ctx: RunContextWrapper[Any] | None,
@@ -30,11 +31,12 @@ class LLMAgentState(AgentState):
30
31
  @classmethod
31
32
  def from_cur_and_rcv_states(
32
33
  cls,
33
- cur_state: Optional["LLMAgentState"] = None,
34
+ cur_state: "LLMAgentState",
35
+ *,
34
36
  rcv_state: Optional["AgentState"] = None,
35
37
  sys_prompt: LLMPrompt | None = None,
36
38
  strategy: SetAgentStateStrategy = "from_sender",
37
- make_custom_state_impl: MakeCustomAgentState | None = None,
39
+ set_agent_state_impl: SetAgentState | None = None,
38
40
  ctx: RunContextWrapper[Any] | None = None,
39
41
  ) -> "LLMAgentState":
40
42
  upd_mh = cur_state.message_history if cur_state else None
@@ -59,10 +61,10 @@ class LLMAgentState(AgentState):
59
61
  upd_mh.reset(sys_prompt)
60
62
 
61
63
  elif strategy == "custom":
62
- assert make_custom_state_impl is not None, (
63
- "Custom message history handler implementation is not provided."
64
+ assert set_agent_state_impl is not None, (
65
+ "Agent state setter implementation is not provided."
64
66
  )
65
- return make_custom_state_impl(
67
+ return set_agent_state_impl(
66
68
  cur_state=cur_state,
67
69
  rcv_state=rcv_state,
68
70
  sys_prompt=sys_prompt,
@@ -81,3 +81,7 @@ from openai.types.shared_params.response_format_json_schema import (
81
81
  from openai.types.shared_params.response_format_text import (
82
82
  ResponseFormatText,
83
83
  )
84
+
85
+ from .openai_llm import OpenAILLM, OpenAILLMSettings
86
+
87
+ __all__ = ["OpenAILLM", "OpenAILLMSettings"]
@@ -3,10 +3,9 @@ from collections.abc import Iterable
3
3
  from copy import deepcopy
4
4
  from typing import Any, Literal
5
5
 
6
- from pydantic import BaseModel
7
-
8
6
  from openai import AsyncOpenAI
9
7
  from openai._types import NOT_GIVEN # type: ignore[import]
8
+ from pydantic import BaseModel
10
9
 
11
10
  from ..cloud_llm import APIProvider, CloudLLM, CloudLLMSettings
12
11
  from ..http_client import AsyncHTTPClientParams
@@ -33,8 +33,10 @@ class FormatSystemArgsHandler(Protocol[CtxT]):
33
33
  class FormatInputArgsHandler(Protocol[InT, CtxT]):
34
34
  def __call__(
35
35
  self,
36
+ *,
36
37
  usr_args: LLMPromptArgs,
37
38
  rcv_args: InT,
39
+ batch_idx: int,
38
40
  ctx: RunContextWrapper[CtxT] | None,
39
41
  ) -> LLMFormattedArgs: ...
40
42
 
@@ -75,13 +77,15 @@ class PromptBuilder(AutoInstanceAttributesMixin, Generic[InT, CtxT]):
75
77
 
76
78
  def _format_inp_args(
77
79
  self,
80
+ *,
78
81
  usr_args: LLMPromptArgs,
79
82
  rcv_args: InT,
83
+ batch_idx: int = 0,
80
84
  ctx: RunContextWrapper[CtxT] | None = None,
81
85
  ) -> LLMFormattedArgs:
82
86
  if self.format_inp_args_impl:
83
87
  return self.format_inp_args_impl(
84
- usr_args=usr_args, rcv_args=rcv_args, ctx=ctx
88
+ usr_args=usr_args, rcv_args=rcv_args, batch_idx=batch_idx, ctx=ctx
85
89
  )
86
90
 
87
91
  if not isinstance(rcv_args, BaseModel) and rcv_args is not None:
@@ -100,6 +104,7 @@ class PromptBuilder(AutoInstanceAttributesMixin, Generic[InT, CtxT]):
100
104
  def make_sys_prompt(
101
105
  self,
102
106
  sys_args: LLMPromptArgs,
107
+ *,
103
108
  ctx: RunContextWrapper[CtxT] | None,
104
109
  ) -> LLMPrompt | None:
105
110
  if self.sys_prompt is None:
@@ -151,9 +156,11 @@ class PromptBuilder(AutoInstanceAttributesMixin, Generic[InT, CtxT]):
151
156
  ]
152
157
 
153
158
  formatted_inp_args_batch = [
154
- self._format_inp_args(usr_args=val_usr_args, rcv_args=val_rcv_args, ctx=ctx)
155
- for val_usr_args, val_rcv_args in zip(
156
- val_usr_args_batch_, val_rcv_args_batch_, strict=False
159
+ self._format_inp_args(
160
+ usr_args=val_usr_args, rcv_args=val_rcv_args, batch_idx=i, ctx=ctx
161
+ )
162
+ for i, (val_usr_args, val_rcv_args) in enumerate(
163
+ zip(val_usr_args_batch_, val_rcv_args_batch_, strict=False)
157
164
  )
158
165
  ]
159
166
 
@@ -29,7 +29,7 @@ class ExitToolCallLoopHandler(Protocol[CtxT]):
29
29
  class ManageAgentStateHandler(Protocol[CtxT]):
30
30
  def __call__(
31
31
  self,
32
- agent_state: LLMAgentState,
32
+ state: LLMAgentState,
33
33
  *,
34
34
  ctx: RunContextWrapper[CtxT] | None,
35
35
  **kwargs: Any,
@@ -93,13 +93,13 @@ class ToolOrchestrator(Generic[CtxT]):
93
93
 
94
94
  def _manage_agent_state(
95
95
  self,
96
- agent_state: LLMAgentState,
96
+ state: LLMAgentState,
97
97
  *,
98
98
  ctx: RunContextWrapper[CtxT] | None = None,
99
99
  **kwargs: Any,
100
100
  ) -> None:
101
101
  if self.manage_agent_state_impl:
102
- self.manage_agent_state_impl(agent_state=agent_state, ctx=ctx, **kwargs)
102
+ self.manage_agent_state_impl(state=state, ctx=ctx, **kwargs)
103
103
 
104
104
  async def generate_once(
105
105
  self,
@@ -119,10 +119,10 @@ class ToolOrchestrator(Generic[CtxT]):
119
119
 
120
120
  async def run_loop(
121
121
  self,
122
- agent_state: LLMAgentState,
122
+ state: LLMAgentState,
123
123
  ctx: RunContextWrapper[CtxT] | None = None,
124
124
  ) -> None:
125
- message_history = agent_state.message_history
125
+ message_history = state.message_history
126
126
  assert message_history.batch_size == 1, (
127
127
  "Batch size must be 1 for tool call loop"
128
128
  )
@@ -131,13 +131,13 @@ class ToolOrchestrator(Generic[CtxT]):
131
131
 
132
132
  tool_choice = "none" if self._react_mode else "auto"
133
133
  gen_message_batch = await self.generate_once(
134
- agent_state, tool_choice=tool_choice, ctx=ctx
134
+ state, tool_choice=tool_choice, ctx=ctx
135
135
  )
136
136
 
137
137
  turns = 0
138
138
 
139
139
  while True:
140
- self._manage_agent_state(agent_state=agent_state, ctx=ctx, num_turns=turns)
140
+ self._manage_agent_state(state=state, ctx=ctx, num_turns=turns)
141
141
 
142
142
  if self._exit_tool_call_loop(
143
143
  message_history.batched_conversations[0], ctx=ctx, num_turns=turns
@@ -156,7 +156,7 @@ class ToolOrchestrator(Generic[CtxT]):
156
156
 
157
157
  tool_choice = "none" if (self._react_mode and msg.tool_calls) else "auto"
158
158
  gen_message_batch = await self.generate_once(
159
- agent_state, tool_choice=tool_choice, ctx=ctx
159
+ state, tool_choice=tool_choice, ctx=ctx
160
160
  )
161
161
 
162
162
  turns += 1
@@ -1,6 +1,6 @@
1
1
  import base64
2
2
  import re
3
- from collections.abc import Iterable
3
+ from collections.abc import Iterable, Mapping
4
4
  from enum import StrEnum
5
5
  from pathlib import Path
6
6
  from typing import Annotated, Any, Literal, TypeAlias
@@ -66,7 +66,7 @@ class Content(BaseModel):
66
66
  def from_formatted_prompt(
67
67
  cls,
68
68
  prompt_template: str,
69
- prompt_args: dict[str, str | ImageData] | None = None,
69
+ prompt_args: Mapping[str, str | int | bool | ImageData] | None = None,
70
70
  ) -> "Content":
71
71
  prompt_args = prompt_args or {}
72
72
  image_args = {
grasp_agents/typing/io.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections.abc import Mapping
1
2
  from typing import TypeAlias, TypeVar
2
3
 
3
4
  from pydantic import BaseModel
@@ -20,5 +21,5 @@ OutT = TypeVar("OutT", covariant=True) # noqa: PLC0105
20
21
  StateT = TypeVar("StateT", bound=AgentState, covariant=True) # noqa: PLC0105
21
22
 
22
23
  LLMPrompt: TypeAlias = str
23
- LLMFormattedSystemArgs: TypeAlias = dict[str, str]
24
- LLMFormattedArgs: TypeAlias = dict[str, str | ImageData]
24
+ LLMFormattedSystemArgs: TypeAlias = Mapping[str, str | int | bool]
25
+ LLMFormattedArgs: TypeAlias = Mapping[str, str | int | bool | ImageData]
@@ -1,5 +1,5 @@
1
1
  import json
2
- from collections.abc import Hashable, Sequence
2
+ from collections.abc import Hashable, Mapping, Sequence
3
3
  from enum import StrEnum
4
4
  from typing import Annotated, Any, Literal, TypeAlias
5
5
  from uuid import uuid4
@@ -79,7 +79,7 @@ class UserMessage(MessageBase):
79
79
  def from_formatted_prompt(
80
80
  cls,
81
81
  prompt_template: str,
82
- prompt_args: dict[str, str | ImageData] | None = None,
82
+ prompt_args: Mapping[str, str | int | bool | ImageData] | None = None,
83
83
  model_id: str | None = None,
84
84
  ) -> "UserMessage":
85
85
  content = Content.from_formatted_prompt(
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  from abc import ABC, abstractmethod
5
- from collections.abc import Sequence
6
4
  from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
7
5
 
8
6
  from pydantic import BaseModel, PrivateAttr, TypeAdapter
@@ -60,11 +58,6 @@ class BaseTool(
60
58
  ) -> _ToolOutT:
61
59
  pass
62
60
 
63
- async def run_batch(
64
- self, inp_batch: Sequence[_ToolInT], ctx: RunContextWrapper[CtxT] | None = None
65
- ) -> Sequence[_ToolOutT]:
66
- return await asyncio.gather(*[self.run(inp, ctx=ctx) for inp in inp_batch])
67
-
68
61
  async def __call__(
69
62
  self, ctx: RunContextWrapper[CtxT] | None = None, **kwargs: Any
70
63
  ) -> _ToolOutT:
grasp_agents/utils.py CHANGED
@@ -2,14 +2,13 @@ import ast
2
2
  import asyncio
3
3
  import json
4
4
  import re
5
- from collections.abc import Coroutine
5
+ from collections.abc import Coroutine, Mapping
6
6
  from datetime import datetime
7
7
  from logging import getLogger
8
8
  from pathlib import Path
9
9
  from typing import Any, TypeVar
10
10
 
11
11
  from pydantic import (
12
- BaseModel,
13
12
  GetCoreSchemaHandler,
14
13
  TypeAdapter,
15
14
  ValidationError,
@@ -19,72 +18,65 @@ from tqdm.autonotebook import tqdm
19
18
 
20
19
  logger = getLogger(__name__)
21
20
 
22
- T = TypeVar("T")
23
-
24
-
25
- def filter_fields(data: dict[str, Any], model: type[BaseModel]) -> dict[str, Any]:
26
- return {key: data[key] for key in model.model_fields if key in data}
21
+ _JSON_START_RE = re.compile(r"[{\[]")
27
22
 
28
-
29
- def read_txt(file_path: str) -> str:
30
- return Path(file_path).read_text()
23
+ T = TypeVar("T")
31
24
 
32
25
 
33
- def format_json_string(text: str) -> str:
26
+ def extract_json_substring(text: str) -> str | None:
34
27
  decoder = json.JSONDecoder()
35
- text = text.replace("\n", "")
36
- length = len(text)
37
- i = 0
38
- while i < length:
39
- ch = text[i]
40
- if ch in "{[":
41
- try:
42
- _, end = decoder.raw_decode(text[i:])
43
- return text[i : i + end]
44
- except ValueError:
45
- pass
46
- i += 1
47
-
48
- return text
28
+ for match in _JSON_START_RE.finditer(text):
29
+ start = match.start()
30
+ try:
31
+ _, end = decoder.raw_decode(text, idx=start)
32
+ return text[start:end]
33
+ except ValueError:
34
+ continue
35
+
36
+ return None
49
37
 
50
38
 
51
39
  def parse_json_or_py_string(
52
- json_str: str, return_none_on_failure: bool = False
40
+ s: str, return_none_on_failure: bool = False
53
41
  ) -> dict[str, Any] | list[Any] | None:
42
+ s_fmt = re.sub(r"```[a-zA-Z0-9]*\n|```", "", s).strip()
54
43
  try:
55
- json_response = ast.literal_eval(json_str)
44
+ return ast.literal_eval(s_fmt)
56
45
  except (ValueError, SyntaxError):
57
46
  try:
58
- json_response = json.loads(json_str)
47
+ return json.loads(s_fmt)
59
48
  except json.JSONDecodeError as exc:
60
49
  if return_none_on_failure:
61
50
  return None
62
51
  raise ValueError(
63
- "Invalid JSON - Both ast.literal_eval and json.loads "
64
- f"failed to parse the following response:\n{json_str}"
52
+ "Invalid JSON/Python string - Both ast.literal_eval and json.loads "
53
+ f"failed to parse the following response:\n{s}"
65
54
  ) from exc
66
55
 
67
- return json_response
68
56
 
69
-
70
- def extract_json(
57
+ def parse_json_or_py_substring(
71
58
  json_str: str, return_none_on_failure: bool = False
72
59
  ) -> dict[str, Any] | list[Any] | None:
73
- return parse_json_or_py_string(format_json_string(json_str), return_none_on_failure)
60
+ return parse_json_or_py_string(
61
+ extract_json_substring(json_str) or "", return_none_on_failure
62
+ )
74
63
 
75
64
 
76
- def validate_obj_from_json_or_py_string(s: str, adapter: TypeAdapter[T]) -> T:
77
- s_fmt = re.sub(r"```[a-zA-Z0-9]*\n|```", "", s).strip()
65
+ def validate_obj_from_json_or_py_string(
66
+ s: str, adapter: TypeAdapter[T], from_substring: bool = False
67
+ ) -> T:
78
68
  try:
79
- parsed = json.loads(s_fmt)
69
+ if from_substring:
70
+ parsed = parse_json_or_py_substring(s, return_none_on_failure=True)
71
+ else:
72
+ parsed = parse_json_or_py_string(s, return_none_on_failure=True)
73
+ if parsed is None:
74
+ parsed = s
80
75
  return adapter.validate_python(parsed)
81
- except (json.JSONDecodeError, ValidationError):
82
- try:
83
- return adapter.validate_python(s_fmt)
84
- except ValidationError as exc:
85
- raise ValueError(
86
- f"Invalid JSON or Python string:\n{s}\nExpected type: {adapter._type}", # type: ignore[arg-type]
87
- ) from exc
76
+ except (json.JSONDecodeError, ValidationError) as exc:
77
+ raise ValueError(
78
+ f"Invalid JSON or Python string:\n{s}\nExpected type: {adapter._type}", # type: ignore[arg-type]
79
+ ) from exc
88
80
 
89
81
 
90
82
  def extract_xml_list(text: str) -> list[str]:
@@ -97,16 +89,26 @@ def extract_xml_list(text: str) -> list[str]:
97
89
  return chunks
98
90
 
99
91
 
100
- def make_conditional_parsed_output_type(
101
- response_format: type, marker: str = "<DONE>"
92
+ def build_marker_json_parser_type(
93
+ marker_to_model: Mapping[str, type],
102
94
  ) -> type:
103
- class ParsedOutput:
104
- """
105
- * Accepts any **str**.
106
- * If the string contains `marker`, it must contain a valid JSON for
107
- `response_format` → we return that a response_format instance.
108
- * Otherwise we leave the string untouched.
109
- """
95
+ """
96
+ Return a Pydantic-compatible *type* that, when given a **str**, searches for
97
+ the first marker substring and validates the JSON that follows with the
98
+ corresponding Pydantic model.
99
+
100
+ If no marker is found, the raw string is returned unchanged.
101
+
102
+ Example:
103
+ -------
104
+ >>> Todo = build_marker_json_parser_type({'```json': MyModel})
105
+ >>> Todo.validate('```json {"a": 1}')
106
+ MyModel(a=1)
107
+
108
+ """
109
+
110
+ class MarkerParsedOutput:
111
+ """String → (Model | str) parser generated by build_marker_json_parser_type."""
110
112
 
111
113
  @classmethod
112
114
  def __get_pydantic_core_schema__(
@@ -114,40 +116,49 @@ def make_conditional_parsed_output_type(
114
116
  _source_type: Any,
115
117
  _handler: GetCoreSchemaHandler,
116
118
  ) -> core_schema.CoreSchema:
117
- def validator(v: Any) -> Any:
118
- if isinstance(v, str) and marker in v:
119
- v_json_str = format_json_string(v)
120
- response_format_adapter = TypeAdapter[Any](response_format)
119
+ def _validate(value: Any) -> Any:
120
+ if not isinstance(value, str):
121
+ raise TypeError("MarkerParsedOutput expects a string")
121
122
 
122
- return response_format_adapter.validate_json(v_json_str)
123
+ for marker, model in marker_to_model.items():
124
+ if marker in value:
125
+ adapter = TypeAdapter[Any](model)
126
+ return validate_obj_from_json_or_py_string(
127
+ value, adapter=adapter, from_substring=True
128
+ )
123
129
 
124
- return v
130
+ return value
125
131
 
126
132
  return core_schema.no_info_after_validator_function(
127
- validator, core_schema.any_schema()
133
+ _validate, core_schema.any_schema()
128
134
  )
129
135
 
130
136
  @classmethod
131
137
  def __get_pydantic_json_schema__(
132
- cls, core_schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler
138
+ cls,
139
+ schema: core_schema.CoreSchema,
140
+ handler: GetCoreSchemaHandler,
133
141
  ):
134
- return handler(core_schema)
142
+ return handler(schema)
143
+
144
+ unique_suffix = "_".join(sorted(marker_to_model))[:40]
145
+ MarkerParsedOutput.__name__ = f"MarkerParsedOutput_{unique_suffix}"
135
146
 
136
- return ParsedOutput
147
+ return MarkerParsedOutput
148
+
149
+
150
+ def read_txt(file_path: str | Path, encoding: str = "utf-8") -> str:
151
+ return Path(file_path).read_text(encoding=encoding)
137
152
 
138
153
 
139
154
  def read_contents_from_file(
140
155
  file_path: str | Path,
141
156
  binary_mode: bool = False,
142
157
  ) -> str | bytes:
143
- """Reads and returns contents of file"""
144
158
  try:
145
159
  if binary_mode:
146
- with open(file_path, "rb") as file:
147
- return file.read()
148
- else:
149
- with open(file_path) as file:
150
- return file.read()
160
+ return Path(file_path).read_bytes()
161
+ return Path(file_path).read_text()
151
162
  except FileNotFoundError:
152
163
  logger.error(f"File {file_path} not found.")
153
164
  return ""
@@ -155,13 +166,9 @@ def read_contents_from_file(
155
166
 
156
167
  def get_prompt(prompt_text: str | None, prompt_path: str | Path | None) -> str | None:
157
168
  if prompt_text is None:
158
- prompt = (
159
- read_contents_from_file(prompt_path) if prompt_path is not None else None
160
- )
161
- else:
162
- prompt = prompt_text
169
+ return read_contents_from_file(prompt_path) if prompt_path is not None else None # type: ignore[arg-type]
163
170
 
164
- return prompt # type: ignore[assignment]
171
+ return prompt_text
165
172
 
166
173
 
167
174
  async def asyncio_gather_with_pbar(
@@ -13,10 +13,10 @@ logger = getLogger(__name__)
13
13
  _EH_OutT = TypeVar("_EH_OutT", contravariant=True) # noqa: PLC0105
14
14
 
15
15
 
16
- class WorkflowLoopExitHandler(Protocol[_EH_OutT, CtxT]):
16
+ class ExitWorkflowLoopHandler(Protocol[_EH_OutT, CtxT]):
17
17
  def __call__(
18
18
  self,
19
- output_message: AgentMessage[_EH_OutT, AgentState],
19
+ output_message: AgentMessage[_EH_OutT, Any],
20
20
  ctx: RunContextWrapper[CtxT] | None,
21
21
  **kwargs: Any,
22
22
  ) -> bool: ...
@@ -31,8 +31,8 @@ class LoopedWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT, Ctx
31
31
  def __init__(
32
32
  self,
33
33
  agent_id: AgentID,
34
- subagents: Sequence[CommunicatingAgent[Any, Any, AgentState, CtxT]],
35
- exit_agent: CommunicatingAgent[Any, OutT, AgentState, CtxT],
34
+ subagents: Sequence[CommunicatingAgent[Any, Any, Any, CtxT]],
35
+ exit_agent: CommunicatingAgent[Any, OutT, Any, CtxT],
36
36
  message_pool: AgentMessagePool[CtxT] | None = None,
37
37
  recipient_ids: list[AgentID] | None = None,
38
38
  dynamic_routing: bool = False,
@@ -51,27 +51,27 @@ class LoopedWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT, Ctx
51
51
 
52
52
  self._max_iterations = max_iterations
53
53
 
54
- self._workflow_loop_exit_impl: WorkflowLoopExitHandler[OutT, CtxT] | None = None
54
+ self._exit_workflow_loop_impl: ExitWorkflowLoopHandler[OutT, CtxT] | None = None
55
55
 
56
56
  @property
57
57
  def max_iterations(self) -> int:
58
58
  return self._max_iterations
59
59
 
60
- def workflow_loop_exit_handler(
61
- self, func: WorkflowLoopExitHandler[OutT, CtxT]
62
- ) -> WorkflowLoopExitHandler[OutT, CtxT]:
63
- self._workflow_loop_exit_impl = func
60
+ def exit_workflow_loop_handler(
61
+ self, func: ExitWorkflowLoopHandler[OutT, CtxT]
62
+ ) -> ExitWorkflowLoopHandler[OutT, CtxT]:
63
+ self._exit_workflow_loop_impl = func
64
64
 
65
65
  return func
66
66
 
67
67
  def _exit_workflow_loop(
68
68
  self,
69
- output_message: AgentMessage[OutT, AgentState],
69
+ output_message: AgentMessage[OutT, Any],
70
70
  ctx: RunContextWrapper[CtxT] | None,
71
71
  **kwargs: Any,
72
72
  ) -> bool:
73
- if self._workflow_loop_exit_impl:
74
- return self._workflow_loop_exit_impl(output_message, ctx=ctx, **kwargs)
73
+ if self._exit_workflow_loop_impl:
74
+ return self._exit_workflow_loop_impl(output_message, ctx=ctx, **kwargs)
75
75
 
76
76
  return False
77
77
 
@@ -80,15 +80,15 @@ class LoopedWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT, Ctx
80
80
  self,
81
81
  inp_items: Any | None = None,
82
82
  *,
83
+ rcv_message: AgentMessage[InT, Any] | None = None,
83
84
  ctx: RunContextWrapper[CtxT] | None = None,
84
- rcv_message: AgentMessage[InT, AgentState] | None = None,
85
85
  entry_point: bool = False,
86
86
  forbid_state_change: bool = False,
87
87
  **kwargs: Any,
88
88
  ) -> AgentMessage[OutT, AgentState]:
89
89
  agent_message = rcv_message
90
90
  num_iterations = 0
91
- exit_message: AgentMessage[OutT, AgentState] | None = None
91
+ exit_message: AgentMessage[OutT, Any] | None = None
92
92
 
93
93
  while True:
94
94
  for subagent in self.subagents:
@@ -4,7 +4,7 @@ from typing import Any, ClassVar, Generic, cast, final
4
4
  from ..agent_message_pool import AgentMessage, AgentMessagePool
5
5
  from ..comm_agent import CommunicatingAgent
6
6
  from ..run_context import CtxT, RunContextWrapper
7
- from ..typing.io import AgentID, AgentState, InT, OutT
7
+ from ..typing.io import AgentID, InT, OutT
8
8
  from .workflow_agent import WorkflowAgent
9
9
 
10
10
 
@@ -17,7 +17,7 @@ class SequentialWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT,
17
17
  def __init__(
18
18
  self,
19
19
  agent_id: AgentID,
20
- subagents: Sequence[CommunicatingAgent[Any, Any, AgentState, CtxT]],
20
+ subagents: Sequence[CommunicatingAgent[Any, Any, Any, CtxT]],
21
21
  message_pool: AgentMessagePool[CtxT] | None = None,
22
22
  recipient_ids: list[AgentID] | None = None,
23
23
  dynamic_routing: bool = False,
@@ -38,12 +38,12 @@ class SequentialWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT,
38
38
  self,
39
39
  inp_items: Any | None = None,
40
40
  *,
41
+ rcv_message: AgentMessage[InT, Any] | None = None,
41
42
  ctx: RunContextWrapper[CtxT] | None = None,
42
- rcv_message: AgentMessage[InT, AgentState] | None = None,
43
43
  entry_point: bool = False,
44
44
  forbid_state_change: bool = False,
45
45
  **kwargs: Any,
46
- ) -> AgentMessage[OutT, AgentState]:
46
+ ) -> AgentMessage[OutT, Any]:
47
47
  agent_message = rcv_message
48
48
  for subagent in self.subagents:
49
49
  agent_message = await subagent.run(
@@ -57,4 +57,4 @@ class SequentialWorkflowAgent(WorkflowAgent[InT, OutT, CtxT], Generic[InT, OutT,
57
57
  inp_items = None
58
58
  entry_point = False
59
59
 
60
- return cast("AgentMessage[OutT, AgentState]", agent_message)
60
+ return cast("AgentMessage[OutT, Any]", agent_message)
@@ -5,13 +5,11 @@ from typing import Any, ClassVar, Generic
5
5
  from ..agent_message_pool import AgentMessage, AgentMessagePool
6
6
  from ..comm_agent import CommunicatingAgent
7
7
  from ..run_context import CtxT, RunContextWrapper
8
- from ..typing.io import AgentID, AgentState, InT, OutT
8
+ from ..typing.io import AgentID, InT, OutT
9
9
 
10
10
 
11
11
  class WorkflowAgent(
12
- CommunicatingAgent[InT, OutT, AgentState, CtxT],
13
- ABC,
14
- Generic[InT, OutT, CtxT],
12
+ CommunicatingAgent[InT, OutT, Any, CtxT], ABC, Generic[InT, OutT, CtxT]
15
13
  ):
16
14
  _generic_arg_to_instance_attr_map: ClassVar[dict[int, str]] = {
17
15
  0: "_in_type",
@@ -21,9 +19,9 @@ class WorkflowAgent(
21
19
  def __init__(
22
20
  self,
23
21
  agent_id: AgentID,
24
- subagents: Sequence[CommunicatingAgent[Any, Any, AgentState, CtxT]],
25
- start_agent: CommunicatingAgent[InT, Any, AgentState, CtxT],
26
- end_agent: CommunicatingAgent[Any, OutT, AgentState, CtxT],
22
+ subagents: Sequence[CommunicatingAgent[Any, Any, Any, CtxT]],
23
+ start_agent: CommunicatingAgent[InT, Any, Any, CtxT],
24
+ end_agent: CommunicatingAgent[Any, OutT, Any, CtxT],
27
25
  message_pool: AgentMessagePool[CtxT] | None = None,
28
26
  recipient_ids: list[AgentID] | None = None,
29
27
  dynamic_routing: bool = False,
@@ -53,11 +51,11 @@ class WorkflowAgent(
53
51
  )
54
52
 
55
53
  @property
56
- def start_agent(self) -> CommunicatingAgent[InT, Any, AgentState, CtxT]:
54
+ def start_agent(self) -> CommunicatingAgent[InT, Any, Any, CtxT]:
57
55
  return self._start_agent
58
56
 
59
57
  @property
60
- def end_agent(self) -> CommunicatingAgent[Any, OutT, AgentState, CtxT]:
58
+ def end_agent(self) -> CommunicatingAgent[Any, OutT, Any, CtxT]:
61
59
  return self._end_agent
62
60
 
63
61
  @abstractmethod
@@ -65,10 +63,10 @@ class WorkflowAgent(
65
63
  self,
66
64
  inp_items: Any | None = None,
67
65
  *,
66
+ rcv_message: AgentMessage[InT, Any] | None = None,
68
67
  ctx: RunContextWrapper[CtxT] | None = None,
69
- rcv_message: AgentMessage[InT, AgentState] | None = None,
70
68
  entry_point: bool = False,
71
69
  forbid_state_change: bool = False,
72
70
  **generation_kwargs: Any,
73
- ) -> AgentMessage[OutT, AgentState]:
71
+ ) -> AgentMessage[OutT, Any]:
74
72
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grasp_agents
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Grasp Agents Library
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: <4,>=3.11.4
@@ -1,28 +1,29 @@
1
+ grasp_agents/__init__.py,sha256=WPNUUFbucwli6oWIwxbckz0zpY4W2LnND0o7melYZOw,979
1
2
  grasp_agents/agent_message.py,sha256=eJV5n44t8EIE6M3jl48Ld7pmaW9dDhBX_FWm_u9yGWE,877
2
3
  grasp_agents/agent_message_pool.py,sha256=OKTXNEo9LAJTQJkzxmJ3TQgWw7WJKOzrKCJjeHpln6o,3158
3
4
  grasp_agents/base_agent.py,sha256=BOLYxS_cSisOR4qupUYIVn2FW15svit3jbNNfjw_cT8,1347
4
- grasp_agents/cloud_llm.py,sha256=X9o9x9TsX1zCBmzO-swEcTWhReQ8wHpFpFj3-kOT_-M,13116
5
+ grasp_agents/cloud_llm.py,sha256=D3iWYb0F-6cG4rXgVoP4bWm2u2RtnEh_xG3MufFaFDo,13157
5
6
  grasp_agents/comm_agent.py,sha256=e2IsatGLxdDkSZpPxQm6s1ha6w0Z9XAzRU1L4qb9wNY,7280
6
7
  grasp_agents/costs_dict.yaml,sha256=EW6XxRXLZobMwQEEiUNYALbDzfbZFb2zEVCaTSAqYjw,2334
7
8
  grasp_agents/generics_utils.py,sha256=kw4Odte6Nvl4c9U7-mKPgXCavWZXo009zYDHAA0BR3g,6234
8
9
  grasp_agents/grasp_logging.py,sha256=H1GYhXdQvVkmauFDZ-KDwvVmPQHZUUm9sRqX_ObK2xI,1111
9
10
  grasp_agents/http_client.py,sha256=KZva2MjJjuI5ohUeU8RdTAImUnQYaqBrV2jDH8smbJw,738
10
11
  grasp_agents/llm.py,sha256=n67lXbB8spr_i3Xz0Plw7oeykfjQmVHHkSiveqBB5Lw,3150
11
- grasp_agents/llm_agent.py,sha256=V8aStNrrBFMEhqBpuq-YxpTPMPJAsvYBXzBV_uxg0qY,13468
12
- grasp_agents/llm_agent_state.py,sha256=Bt470miFHTFnxdIXUjd7jha6FWDf0hUuMI8n_G6cZI8,2439
12
+ grasp_agents/llm_agent.py,sha256=Lx6L9ahe9WYnzKpCc-XaIyNw73U2YHtTb6OhFGUv9mU,14989
13
+ grasp_agents/llm_agent_state.py,sha256=lLdYni2f3TA5zJLf_jqR5DSWqVI_zP2YfNrwEGqZnvg,2402
13
14
  grasp_agents/memory.py,sha256=X1YtVX8XxP5KnGPMW8BqjID8QK4hTG2obxoyhnnZ4pU,5575
14
15
  grasp_agents/printer.py,sha256=Jk6OJExio53gbKBod5Dd8Y3CWYrVb4K5q4UJ8i9cQvo,5024
15
- grasp_agents/prompt_builder.py,sha256=G1ZX0sCnA6_-rBi44ZtkiNU6h7Jpvu-5dTM4ok2ax70,8043
16
+ grasp_agents/prompt_builder.py,sha256=wb7V8JXjD3IeD3l24HItUkvGmVE4e0PUzu8IT_9rgko,8208
16
17
  grasp_agents/run_context.py,sha256=M4w_HXl5aiz-18CDlfNCRNZm3m5UIQMrjKkhurFTtkY,2229
17
- grasp_agents/tool_orchestrator.py,sha256=BCzeLTJkXOFXjKTRs75fRs1vJVvavbTEXzvHFEvwCM4,6167
18
+ grasp_agents/tool_orchestrator.py,sha256=--E-ue7Z8nK6NwqGbWeCQWfTjWIbPxEe5X54bjPe62M,6107
18
19
  grasp_agents/usage_tracker.py,sha256=5YuN6hpg6HASdg-hOylgWzhCiORmDMnZuQtbISfhm_4,3378
19
- grasp_agents/utils.py,sha256=NzMg11w7akRyMOYjS6hHTE2XZ7DKeuM5BD1SRfFFsNk,5350
20
- grasp_agents/openai/__init__.py,sha256=qN8HMAatSJKOsA6v-JwakMYguwkswCVHqrmK1gFy9wI,3096
20
+ grasp_agents/utils.py,sha256=gKUtJ6__HB7yHBUPWY5tkdSAfgj3_R3--s2J5B5fBPE,5739
21
+ grasp_agents/openai/__init__.py,sha256=P7oa4tdJ2JKJv16frRi1C09FnrMayuOYomu5l95uayE,3196
21
22
  grasp_agents/openai/completion_converters.py,sha256=lX9h1kaGAo5ttsl-4V7l4x8IpjxJaJJtyU2cKu3-EOc,1871
22
23
  grasp_agents/openai/content_converters.py,sha256=6GI0D7xJalzsiawAJOyCUzTJTo0NQdpv87YKmfN0LYQ,2631
23
24
  grasp_agents/openai/converters.py,sha256=DBXBxow9oRG6pc8inpZBLiuUqHzVfpscmHFpN9bAdvc,5276
24
25
  grasp_agents/openai/message_converters.py,sha256=KjF6FbXzwlWdM-1YT3cswUV-74sjiwOhLFPMY4sJ5Xk,4593
25
- grasp_agents/openai/openai_llm.py,sha256=dscGlVhH4v1yAw4NRPgJdww9toOoMRpIqA6HD4IGWOs,6132
26
+ grasp_agents/openai/openai_llm.py,sha256=93dSQXed9yqMIVLKJoHp5rauin7xAc-8Aokf1vFJSho,6131
26
27
  grasp_agents/openai/tool_converters.py,sha256=KhWRETkjhjocISUo_HBZ8QfBiyTOoC5WurPNAR4BYxc,1027
27
28
  grasp_agents/rate_limiting/__init__.py,sha256=KRgtF_E7R3YfA2cpYcFcZ7wycV0pWVJ0xRQC7YhiIEQ,158
28
29
  grasp_agents/rate_limiting/rate_limiter_chunked.py,sha256=BPgkUXvhmZhTpZs2T6uujNFuxH_kYHiISuf6_-eNhUc,5544
@@ -30,16 +31,16 @@ grasp_agents/rate_limiting/types.py,sha256=PbnNhEAcYedQdIpPJWud8HUVcxa_xZS2RDZu4
30
31
  grasp_agents/rate_limiting/utils.py,sha256=oEDWDNHYMUdxOOG49PlAJochkZq8nnVBCo6JxPc1iSo,2007
31
32
  grasp_agents/typing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
33
  grasp_agents/typing/completion.py,sha256=_KDLx3Gtz7o-pEZrvAFgCZwDmkr2oQkxrL-2LSXHHsw,657
33
- grasp_agents/typing/content.py,sha256=13nLNZqZgtpo9sM0vCRQmZ4bQjjZqUSElMQOwjL7bO8,3651
34
+ grasp_agents/typing/content.py,sha256=VdnHdW8PHDCtX_ffvcwQz-7ypPUQNSGqHa3txFE_72Y,3676
34
35
  grasp_agents/typing/converters.py,sha256=yORIljRsVoKz7oj38pHLD6luIelM1RcYL_PqG_D4nWM,3086
35
- grasp_agents/typing/io.py,sha256=aDT8tpZYc2R2jOa6QU0xO_HJYEBqCkeL1flHDugrsOg,541
36
- grasp_agents/typing/message.py,sha256=oCpqD_CV2Da-M-l-e5liFJSwK8267fxfcU68LIc7C1E,3801
37
- grasp_agents/typing/tool.py,sha256=l6TlWGpeUiJuXWUa0g1Xx7lw7jxZOpZ0bK63Ixt9h5A,2240
36
+ grasp_agents/typing/io.py,sha256=uxSvbD05UK5nIhPfDvXIoGuU6xRMW4USZq_4IgBeGCY,609
37
+ grasp_agents/typing/message.py,sha256=XgPjXeh47e2GG1AYslhxaNw1Ax6Ozatga_7X2SFFKMA,3826
38
+ grasp_agents/typing/tool.py,sha256=e0pTMnRcpMpGNVQ8muE9wnh7LdIgh92AqXDo9hMDxf0,1960
38
39
  grasp_agents/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- grasp_agents/workflow/looped_agent.py,sha256=8NVy6dAwEs7f6JgYHZUI1h-N8SabpND4n-3rSy5mh70,3945
40
- grasp_agents/workflow/sequential_agent.py,sha256=Ral6Bvsl5-NdO-uKMGiWuz5EE9rNcYb1lXhY8CcQw4w,2054
41
- grasp_agents/workflow/workflow_agent.py,sha256=LadvEJTsV6YEGRb_eaYgu5r7k1aa8N-2FHXMYJpbBVU,2460
42
- grasp_agents-0.2.0.dist-info/METADATA,sha256=TfFjGFaXtrnwSbjy-Hr2m9TR0ryW4CZN75QJJlFZ3ds,6889
43
- grasp_agents-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
- grasp_agents-0.2.0.dist-info/licenses/LICENSE.md,sha256=Kfeo0gdlLS6tLQiWwO9UWhjp9-f93a5kShSiBp2FG-c,1201
45
- grasp_agents-0.2.0.dist-info/RECORD,,
40
+ grasp_agents/workflow/looped_agent.py,sha256=VU1Gst_qacU6TrJ0PcwHYOUgRkdR3NdiXm7KoD3U0vw,3903
41
+ grasp_agents/workflow/sequential_agent.py,sha256=4aFLZDJk4A1_0I0mvISIqyO8Nlz5Q5rT7Ig5Zy80cHg,2014
42
+ grasp_agents/workflow/workflow_agent.py,sha256=FzTil10WXzOIHzum-yo2mEETfjoY9RMXUycZhlLNqkw,2383
43
+ grasp_agents-0.2.2.dist-info/METADATA,sha256=ejV2_4MaH9s2zh6HgVcHpae9HP9FzZo8eXZ8xRrEdPY,6889
44
+ grasp_agents-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ grasp_agents-0.2.2.dist-info/licenses/LICENSE.md,sha256=-nNNdWqGB8gJ2O-peFQ2Irshv5tW5pHKyTcYkwvH7CE,1201
46
+ grasp_agents-0.2.2.dist-info/RECORD,,
@@ -8,6 +8,6 @@ Package production dependencies are licensed under the following terms:
8
8
  | dotenv | 0.9.9 | BSD-3-Clause license | https://github.com/pedroburon/dotenv |
9
9
  | httpx | 0.28.1 | BSD License | https://github.com/encode/httpx |
10
10
  | openai | 1.77.0 | Apache Software License | https://github.com/openai/openai-python |
11
- | tenacity | 9.1.2 | Apache Software License | https://github.com/jd/tenacity |
11
+ | tenacity | 8.5.0 | Apache Software License | https://github.com/jd/tenacity |
12
12
  | termcolor | 2.5.0 | MIT License | https://github.com/termcolor/termcolor |
13
13
  | tqdm | 4.67.1 | MIT License; Mozilla Public License 2.0 (MPL 2.0) | https://tqdm.github.io |