grasp_agents 0.4.7__py3-none-any.whl → 0.5.0__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.
Files changed (44) hide show
  1. grasp_agents/cloud_llm.py +191 -224
  2. grasp_agents/comm_processor.py +101 -100
  3. grasp_agents/errors.py +69 -9
  4. grasp_agents/litellm/__init__.py +106 -0
  5. grasp_agents/litellm/completion_chunk_converters.py +68 -0
  6. grasp_agents/litellm/completion_converters.py +72 -0
  7. grasp_agents/litellm/converters.py +138 -0
  8. grasp_agents/litellm/lite_llm.py +210 -0
  9. grasp_agents/litellm/message_converters.py +66 -0
  10. grasp_agents/llm.py +84 -49
  11. grasp_agents/llm_agent.py +136 -120
  12. grasp_agents/llm_agent_memory.py +3 -3
  13. grasp_agents/llm_policy_executor.py +167 -174
  14. grasp_agents/memory.py +4 -0
  15. grasp_agents/openai/__init__.py +24 -9
  16. grasp_agents/openai/completion_chunk_converters.py +6 -6
  17. grasp_agents/openai/completion_converters.py +12 -14
  18. grasp_agents/openai/content_converters.py +1 -3
  19. grasp_agents/openai/converters.py +6 -8
  20. grasp_agents/openai/message_converters.py +21 -3
  21. grasp_agents/openai/openai_llm.py +155 -103
  22. grasp_agents/openai/tool_converters.py +4 -6
  23. grasp_agents/packet.py +5 -2
  24. grasp_agents/packet_pool.py +14 -13
  25. grasp_agents/printer.py +234 -72
  26. grasp_agents/processor.py +228 -88
  27. grasp_agents/prompt_builder.py +2 -2
  28. grasp_agents/run_context.py +11 -20
  29. grasp_agents/runner.py +42 -0
  30. grasp_agents/typing/completion.py +16 -9
  31. grasp_agents/typing/completion_chunk.py +51 -22
  32. grasp_agents/typing/events.py +95 -19
  33. grasp_agents/typing/message.py +25 -1
  34. grasp_agents/typing/tool.py +2 -0
  35. grasp_agents/usage_tracker.py +31 -37
  36. grasp_agents/utils.py +95 -84
  37. grasp_agents/workflow/looped_workflow.py +60 -11
  38. grasp_agents/workflow/sequential_workflow.py +43 -11
  39. grasp_agents/workflow/workflow_processor.py +25 -24
  40. {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.0.dist-info}/METADATA +7 -6
  41. grasp_agents-0.5.0.dist-info/RECORD +57 -0
  42. grasp_agents-0.4.7.dist-info/RECORD +0 -50
  43. {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.0.dist-info}/WHEEL +0 -0
  44. {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.0.dist-info}/licenses/LICENSE.md +0 -0
grasp_agents/utils.py CHANGED
@@ -2,23 +2,22 @@ import ast
2
2
  import asyncio
3
3
  import json
4
4
  import re
5
- from collections.abc import Coroutine, Mapping
5
+ from collections.abc import AsyncIterator, Coroutine, Mapping
6
6
  from datetime import UTC, datetime
7
7
  from logging import getLogger
8
8
  from pathlib import Path
9
- from typing import Annotated, Any, TypeVar, get_args, get_origin, overload
9
+ from typing import Annotated, Any, TypeVar, get_args, get_origin
10
10
 
11
- from pydantic import TypeAdapter, ValidationError
11
+ from pydantic import TypeAdapter
12
+ from pydantic import ValidationError as PydanticValidationError
12
13
  from tqdm.autonotebook import tqdm
13
14
 
14
- from .errors import OutputValidationError, StringParsingError
15
+ from .errors import JSONSchemaValidationError, PyJSONStringParsingError
15
16
 
16
17
  logger = getLogger(__name__)
17
18
 
18
19
  _JSON_START_RE = re.compile(r"[{\[]")
19
20
 
20
- T = TypeVar("T")
21
-
22
21
 
23
22
  def extract_json_substring(text: str) -> str | None:
24
23
  decoder = json.JSONDecoder()
@@ -34,112 +33,95 @@ def extract_json_substring(text: str) -> str | None:
34
33
 
35
34
 
36
35
  def parse_json_or_py_string(
37
- s: str, return_none_on_failure: bool = False, strip_language_markdown: bool = True
36
+ s: str,
37
+ from_substring: bool = False,
38
+ return_none_on_failure: bool = False,
39
+ strip_language_markdown: bool = True,
38
40
  ) -> dict[str, Any] | list[Any] | None:
39
41
  s_orig = s
42
+
40
43
  if strip_language_markdown:
41
44
  s = re.sub(r"```[a-zA-Z0-9]*\n|```", "", s).strip()
45
+
46
+ if from_substring:
47
+ s = extract_json_substring(s) or ""
48
+
42
49
  try:
43
50
  return ast.literal_eval(s)
44
51
  except (ValueError, SyntaxError):
45
52
  try:
46
53
  return json.loads(s)
47
54
  except json.JSONDecodeError as exc:
55
+ err_message = (
56
+ "Both ast.literal_eval and json.loads "
57
+ f"failed to parse the following JSON/Python string:\n{s_orig}"
58
+ )
48
59
  if return_none_on_failure:
60
+ logger.warning(err_message)
49
61
  return None
50
- raise StringParsingError(
51
- "Invalid JSON/Python string - Both ast.literal_eval and json.loads "
52
- f"failed to parse the following response:\n{s_orig}"
53
- ) from exc
62
+ raise PyJSONStringParsingError(s_orig, message=err_message) from exc
54
63
 
55
64
 
56
- def parse_json_or_py_substring(
57
- json_str: str,
58
- return_none_on_failure: bool = False,
59
- strip_language_markdown: bool = True,
60
- ) -> dict[str, Any] | list[Any] | None:
61
- return parse_json_or_py_string(
62
- extract_json_substring(json_str) or "",
63
- return_none_on_failure=return_none_on_failure,
64
- strip_language_markdown=strip_language_markdown,
65
- )
66
-
65
+ def is_str_type(t: Any) -> bool:
66
+ type_origin = get_origin(t)
67
+ type_args = get_args(t)
67
68
 
68
- @overload
69
- def validate_obj_from_json_or_py_string(
70
- s: str,
71
- adapter: TypeAdapter[T],
72
- from_substring: bool = False,
73
- strip_language_markdown: bool = True,
74
- ) -> T: ...
69
+ return (t is str) or (
70
+ (type_origin is Annotated) and len(type_args) > 0 and type_args[0] is str
71
+ )
75
72
 
76
73
 
77
- @overload
78
74
  def validate_obj_from_json_or_py_string(
79
75
  s: str,
80
- adapter: Mapping[str, TypeAdapter[T]],
76
+ schema: Any,
81
77
  from_substring: bool = False,
82
78
  strip_language_markdown: bool = True,
83
- ) -> T | str: ...
84
-
85
-
86
- def validate_obj_from_json_or_py_string(
79
+ ) -> Any:
80
+ try:
81
+ if is_str_type(schema):
82
+ parsed = s
83
+ else:
84
+ parsed = parse_json_or_py_string(
85
+ s,
86
+ return_none_on_failure=True,
87
+ from_substring=from_substring,
88
+ strip_language_markdown=strip_language_markdown,
89
+ )
90
+ return TypeAdapter(schema).validate_python(parsed)
91
+ except PydanticValidationError as exc:
92
+ raise JSONSchemaValidationError(s, schema) from exc
93
+
94
+
95
+ def validate_tagged_objs_from_json_or_py_string(
87
96
  s: str,
88
- adapter: TypeAdapter[T] | Mapping[str, TypeAdapter[T]],
97
+ schema_by_xml_tag: Mapping[str, Any],
89
98
  from_substring: bool = False,
90
99
  strip_language_markdown: bool = True,
91
- ) -> T | str:
92
- _selected_adapter: TypeAdapter[T] | None = None
93
- _selected_tag: str | None = None
94
- s_orig = s
100
+ ) -> Mapping[str, Any]:
101
+ validated_obj_per_tag: dict[str, Any] = {}
102
+ _schema: Any = None
103
+ _tag: str | None = None
95
104
 
96
- if isinstance(adapter, Mapping):
97
- for _tag, _adapter in adapter.items():
105
+ try:
106
+ for _tag, _schema in schema_by_xml_tag.items():
98
107
  match = re.search(rf"<{_tag}>\s*(.*?)\s*</{_tag}>", s, re.DOTALL)
99
108
  if not match:
100
109
  continue
101
- s = match.group(1).strip()
102
- _selected_adapter = _adapter
103
- _selected_tag = _tag
104
- break
105
- if _selected_adapter is None:
106
- return s
107
- else:
108
- _selected_adapter = adapter
109
-
110
- _type = _selected_adapter._type # type: ignore[attr-defined]
111
- type_origin = get_origin(_type)
112
- type_args = get_args(_type)
113
- is_str_type = (_type is str) or (
114
- type_origin is Annotated and type_args and type_args[0] is str
115
- )
116
-
117
- try:
118
- if not is_str_type:
119
- if from_substring:
120
- parsed = parse_json_or_py_substring(
121
- s,
122
- return_none_on_failure=True,
123
- strip_language_markdown=strip_language_markdown,
124
- )
125
- else:
126
- parsed = parse_json_or_py_string(
127
- s,
128
- return_none_on_failure=True,
129
- strip_language_markdown=strip_language_markdown,
130
- )
131
- if parsed is None:
132
- parsed = s
133
- else:
134
- parsed = s
135
- return _selected_adapter.validate_python(parsed)
136
- except ValidationError as exc:
137
- err_message = f"Invalid JSON or Python string:\n{s_orig}"
138
- if _selected_tag:
139
- err_message += f"\nExpected type {_type} within tag <{_selected_tag}>"
140
- else:
141
- err_message += f"\nExpected type {_type}"
142
- raise OutputValidationError(err_message) from exc
110
+ tagged_substring = match.group(1).strip()
111
+ validated_obj_per_tag[_tag] = validate_obj_from_json_or_py_string(
112
+ tagged_substring, # type: ignore[assignment]
113
+ schema=_schema,
114
+ from_substring=from_substring,
115
+ strip_language_markdown=strip_language_markdown,
116
+ )
117
+ except JSONSchemaValidationError as exc:
118
+ err_message = (
119
+ f"Failed to validate substring within tag <{_tag}> against JSON schema:"
120
+ f"\n{s}\nExpected type: {_schema}"
121
+ )
122
+ raise JSONSchemaValidationError(s, _schema, message=err_message) from exc
123
+
124
+ return validated_obj_per_tag
143
125
 
144
126
 
145
127
  def extract_xml_list(text: str) -> list[str]:
@@ -198,3 +180,32 @@ async def asyncio_gather_with_pbar(
198
180
 
199
181
  def get_timestamp() -> str:
200
182
  return datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
183
+
184
+
185
+ _T = TypeVar("_T")
186
+
187
+
188
+ async def stream_concurrent(
189
+ generators: list[AsyncIterator[_T]],
190
+ ) -> AsyncIterator[tuple[int, _T]]:
191
+ queue: asyncio.Queue[tuple[int, _T] | None] = asyncio.Queue()
192
+ pumps_left = len(generators)
193
+
194
+ async def pump(gen: AsyncIterator[_T], idx: int) -> None:
195
+ nonlocal pumps_left
196
+ try:
197
+ async for item in gen:
198
+ await queue.put((idx, item))
199
+ finally:
200
+ pumps_left -= 1
201
+ if pumps_left == 0:
202
+ await queue.put(None)
203
+
204
+ for idx, gen in enumerate(generators):
205
+ asyncio.create_task(pump(gen, idx))
206
+
207
+ while True:
208
+ msg = await queue.get()
209
+ if msg is None:
210
+ break
211
+ yield msg
@@ -1,12 +1,13 @@
1
- from collections.abc import Sequence
1
+ from collections.abc import AsyncIterator, Sequence
2
2
  from itertools import pairwise
3
3
  from logging import getLogger
4
- from typing import Any, ClassVar, Generic, Protocol, TypeVar, cast, final
4
+ from typing import Any, Generic, Protocol, TypeVar, cast, final
5
5
 
6
6
  from ..errors import WorkflowConstructionError
7
7
  from ..packet_pool import Packet, PacketPool
8
8
  from ..processor import Processor
9
9
  from ..run_context import CtxT, RunContext
10
+ from ..typing.events import Event, ProcPacketOutputEvent, WorkflowResultEvent
10
11
  from ..typing.io import InT, OutT_co, ProcName
11
12
  from .workflow_processor import WorkflowProcessor
12
13
 
@@ -27,11 +28,6 @@ class ExitWorkflowLoopHandler(Protocol[_OutT_contra, CtxT]):
27
28
  class LoopedWorkflow(
28
29
  WorkflowProcessor[InT, OutT_co, CtxT], Generic[InT, OutT_co, CtxT]
29
30
  ):
30
- _generic_arg_to_instance_attr_map: ClassVar[dict[int, str]] = {
31
- 0: "_in_type",
32
- 1: "_out_type",
33
- }
34
-
35
31
  def __init__(
36
32
  self,
37
33
  name: ProcName,
@@ -39,7 +35,7 @@ class LoopedWorkflow(
39
35
  exit_proc: Processor[Any, OutT_co, Any, CtxT],
40
36
  packet_pool: PacketPool[CtxT] | None = None,
41
37
  recipients: list[ProcName] | None = None,
42
- num_par_run_retries: int = 0,
38
+ max_retries: int = 0,
43
39
  max_iterations: int = 10,
44
40
  ) -> None:
45
41
  super().__init__(
@@ -49,7 +45,7 @@ class LoopedWorkflow(
49
45
  end_proc=exit_proc,
50
46
  packet_pool=packet_pool,
51
47
  recipients=recipients,
52
- num_par_run_retries=num_par_run_retries,
48
+ max_retries=max_retries,
53
49
  )
54
50
 
55
51
  for prev_proc, proc in pairwise(subprocs):
@@ -102,10 +98,12 @@ class LoopedWorkflow(
102
98
  *,
103
99
  in_packet: Packet[InT] | None = None,
104
100
  in_args: InT | Sequence[InT] | None = None,
105
- run_id: str | None = None,
101
+ call_id: str | None = None,
106
102
  forgetful: bool = False,
107
103
  ctx: RunContext[CtxT] | None = None,
108
104
  ) -> Packet[OutT_co]:
105
+ call_id = self._generate_call_id(call_id)
106
+
109
107
  packet = in_packet
110
108
  num_iterations = 0
111
109
  exit_packet: Packet[OutT_co] | None = None
@@ -117,7 +115,7 @@ class LoopedWorkflow(
117
115
  in_packet=packet,
118
116
  in_args=in_args,
119
117
  forgetful=forgetful,
120
- run_id=self._generate_subproc_run_id(run_id, subproc=subproc),
118
+ call_id=f"{call_id}/{subproc.name}",
121
119
  ctx=ctx,
122
120
  )
123
121
 
@@ -135,3 +133,54 @@ class LoopedWorkflow(
135
133
 
136
134
  chat_inputs = None
137
135
  in_args = None
136
+
137
+ @final
138
+ async def run_stream( # type: ignore[override]
139
+ self,
140
+ chat_inputs: Any | None = None,
141
+ *,
142
+ in_packet: Packet[InT] | None = None,
143
+ in_args: InT | Sequence[InT] | None = None,
144
+ call_id: str | None = None,
145
+ forgetful: bool = False,
146
+ ctx: RunContext[CtxT] | None = None,
147
+ ) -> AsyncIterator[Event[Any]]:
148
+ call_id = self._generate_call_id(call_id)
149
+
150
+ packet = in_packet
151
+ num_iterations = 0
152
+ exit_packet: Packet[OutT_co] | None = None
153
+
154
+ for subproc in self.subprocs:
155
+ async for event in subproc.run_stream(
156
+ chat_inputs=chat_inputs,
157
+ in_packet=packet,
158
+ in_args=in_args,
159
+ forgetful=forgetful,
160
+ call_id=f"{call_id}/{subproc.name}",
161
+ ctx=ctx,
162
+ ):
163
+ if isinstance(event, ProcPacketOutputEvent):
164
+ packet = event.data
165
+ yield event
166
+
167
+ if subproc is self._end_proc:
168
+ num_iterations += 1
169
+ exit_packet = cast("Packet[OutT_co]", packet)
170
+ if self._exit_workflow_loop(exit_packet, ctx=ctx):
171
+ yield WorkflowResultEvent(
172
+ data=exit_packet, proc_name=self.name, call_id=call_id
173
+ )
174
+ return
175
+ if num_iterations >= self._max_iterations:
176
+ logger.info(
177
+ f"Max iterations reached ({self._max_iterations}). "
178
+ "Exiting loop."
179
+ )
180
+ yield WorkflowResultEvent(
181
+ data=exit_packet, proc_name=self.name, call_id=call_id
182
+ )
183
+ return
184
+
185
+ chat_inputs = None
186
+ in_args = None
@@ -1,11 +1,12 @@
1
- from collections.abc import Sequence
1
+ from collections.abc import AsyncIterator, Sequence
2
2
  from itertools import pairwise
3
- from typing import Any, ClassVar, Generic, cast, final
3
+ from typing import Any, Generic, cast, final
4
4
 
5
5
  from ..errors import WorkflowConstructionError
6
6
  from ..packet_pool import Packet, PacketPool
7
7
  from ..processor import Processor
8
8
  from ..run_context import CtxT, RunContext
9
+ from ..typing.events import Event, ProcPacketOutputEvent, WorkflowResultEvent
9
10
  from ..typing.io import InT, OutT_co, ProcName
10
11
  from .workflow_processor import WorkflowProcessor
11
12
 
@@ -13,18 +14,13 @@ from .workflow_processor import WorkflowProcessor
13
14
  class SequentialWorkflow(
14
15
  WorkflowProcessor[InT, OutT_co, CtxT], Generic[InT, OutT_co, CtxT]
15
16
  ):
16
- _generic_arg_to_instance_attr_map: ClassVar[dict[int, str]] = {
17
- 0: "_in_type",
18
- 1: "_out_type",
19
- }
20
-
21
17
  def __init__(
22
18
  self,
23
19
  name: ProcName,
24
20
  subprocs: Sequence[Processor[Any, Any, Any, CtxT]],
25
21
  packet_pool: PacketPool[CtxT] | None = None,
26
22
  recipients: list[ProcName] | None = None,
27
- num_par_run_retries: int = 0,
23
+ max_retries: int = 0,
28
24
  ) -> None:
29
25
  super().__init__(
30
26
  subprocs=subprocs,
@@ -33,7 +29,7 @@ class SequentialWorkflow(
33
29
  name=name,
34
30
  packet_pool=packet_pool,
35
31
  recipients=recipients,
36
- num_par_run_retries=num_par_run_retries,
32
+ max_retries=max_retries,
37
33
  )
38
34
 
39
35
  for prev_proc, proc in pairwise(subprocs):
@@ -52,9 +48,11 @@ class SequentialWorkflow(
52
48
  in_packet: Packet[InT] | None = None,
53
49
  in_args: InT | Sequence[InT] | None = None,
54
50
  forgetful: bool = False,
55
- run_id: str | None = None,
51
+ call_id: str | None = None,
56
52
  ctx: RunContext[CtxT] | None = None,
57
53
  ) -> Packet[OutT_co]:
54
+ call_id = self._generate_call_id(call_id)
55
+
58
56
  packet = in_packet
59
57
  for subproc in self.subprocs:
60
58
  packet = await subproc.run(
@@ -62,10 +60,44 @@ class SequentialWorkflow(
62
60
  in_packet=packet,
63
61
  in_args=in_args,
64
62
  forgetful=forgetful,
65
- run_id=self._generate_subproc_run_id(run_id, subproc),
63
+ call_id=f"{call_id}/{subproc.name}",
66
64
  ctx=ctx,
67
65
  )
68
66
  chat_inputs = None
69
67
  in_args = None
70
68
 
71
69
  return cast("Packet[OutT_co]", packet)
70
+
71
+ @final
72
+ async def run_stream( # type: ignore[override]
73
+ self,
74
+ chat_inputs: Any | None = None,
75
+ *,
76
+ in_packet: Packet[InT] | None = None,
77
+ in_args: InT | Sequence[InT] | None = None,
78
+ forgetful: bool = False,
79
+ call_id: str | None = None,
80
+ ctx: RunContext[CtxT] | None = None,
81
+ ) -> AsyncIterator[Event[Any]]:
82
+ call_id = self._generate_call_id(call_id)
83
+
84
+ packet = in_packet
85
+ for subproc in self.subprocs:
86
+ async for event in subproc.run_stream(
87
+ chat_inputs=chat_inputs,
88
+ in_packet=packet,
89
+ in_args=in_args,
90
+ forgetful=forgetful,
91
+ call_id=f"{call_id}/{subproc.name}",
92
+ ctx=ctx,
93
+ ):
94
+ if isinstance(event, ProcPacketOutputEvent):
95
+ packet = event.data
96
+ yield event
97
+
98
+ chat_inputs = None
99
+ in_args = None
100
+
101
+ yield WorkflowResultEvent(
102
+ data=cast("Packet[OutT_co]", packet), proc_name=self.name, call_id=call_id
103
+ )
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
- from collections.abc import Sequence
3
- from typing import Any, ClassVar, Generic
2
+ from collections.abc import AsyncIterator, Sequence
3
+ from typing import Any, Generic
4
4
 
5
5
  from ..comm_processor import CommProcessor
6
6
  from ..errors import WorkflowConstructionError
@@ -8,6 +8,7 @@ from ..packet import Packet
8
8
  from ..packet_pool import PacketPool
9
9
  from ..processor import Processor
10
10
  from ..run_context import CtxT, RunContext
11
+ from ..typing.events import Event
11
12
  from ..typing.io import InT, OutT_co, ProcName
12
13
 
13
14
 
@@ -16,11 +17,6 @@ class WorkflowProcessor(
16
17
  ABC,
17
18
  Generic[InT, OutT_co, CtxT],
18
19
  ):
19
- _generic_arg_to_instance_attr_map: ClassVar[dict[int, str]] = {
20
- 0: "_in_type",
21
- 1: "_out_type",
22
- }
23
-
24
20
  def __init__(
25
21
  self,
26
22
  name: ProcName,
@@ -29,15 +25,18 @@ class WorkflowProcessor(
29
25
  end_proc: Processor[Any, OutT_co, Any, CtxT],
30
26
  packet_pool: PacketPool[CtxT] | None = None,
31
27
  recipients: list[ProcName] | None = None,
32
- num_par_run_retries: int = 0,
28
+ max_retries: int = 0,
33
29
  ) -> None:
34
30
  super().__init__(
35
31
  name=name,
36
32
  packet_pool=packet_pool,
37
33
  recipients=recipients,
38
- num_par_run_retries=num_par_run_retries,
34
+ max_retries=max_retries,
39
35
  )
40
36
 
37
+ self._in_type = start_proc.in_type
38
+ self._out_type = end_proc.out_type
39
+
41
40
  if len(subprocs) < 2:
42
41
  raise WorkflowConstructionError("At least two subprocessors are required")
43
42
  if start_proc not in subprocs:
@@ -49,17 +48,6 @@ class WorkflowProcessor(
49
48
  "End subprocessor must be in the subprocessors list"
50
49
  )
51
50
 
52
- if start_proc.in_type != self.in_type:
53
- raise WorkflowConstructionError(
54
- f"Start subprocessor's input type {start_proc.in_type} does not "
55
- f"match workflow's input type {self._in_type}"
56
- )
57
- if end_proc.out_type != self.out_type:
58
- raise WorkflowConstructionError(
59
- f"End subprocessor's output type {end_proc.out_type} does not "
60
- f"match workflow's output type {self._out_type}"
61
- )
62
-
63
51
  self._subprocs = subprocs
64
52
  self._start_proc = start_proc
65
53
  self._end_proc = end_proc
@@ -76,10 +64,10 @@ class WorkflowProcessor(
76
64
  def end_proc(self) -> Processor[Any, OutT_co, Any, CtxT]:
77
65
  return self._end_proc
78
66
 
79
- def _generate_subproc_run_id(
80
- self, run_id: str | None, subproc: Processor[Any, Any, Any, CtxT]
67
+ def _generate_subproc_call_id(
68
+ self, call_id: str | None, subproc: Processor[Any, Any, Any, CtxT]
81
69
  ) -> str | None:
82
- return f"{self._generate_run_id(run_id)}/{subproc.name}"
70
+ return f"{self._generate_call_id(call_id)}/{subproc.name}"
83
71
 
84
72
  @abstractmethod
85
73
  async def run(
@@ -90,6 +78,19 @@ class WorkflowProcessor(
90
78
  in_args: InT | Sequence[InT] | None = None,
91
79
  ctx: RunContext[CtxT] | None = None,
92
80
  forgetful: bool = False,
93
- run_id: str | None = None,
81
+ call_id: str | None = None,
94
82
  ) -> Packet[OutT_co]:
95
83
  pass
84
+
85
+ @abstractmethod
86
+ async def run_stream( # type: ignore[override]
87
+ self,
88
+ chat_inputs: Any | None = None,
89
+ *,
90
+ in_packet: Packet[InT] | None = None,
91
+ in_args: InT | Sequence[InT] | None = None,
92
+ ctx: RunContext[CtxT] | None = None,
93
+ forgetful: bool = False,
94
+ call_id: str | None = None,
95
+ ) -> AsyncIterator[Event[Any]]:
96
+ pass
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grasp_agents
3
- Version: 0.4.7
3
+ Version: 0.5.0
4
4
  Summary: Grasp Agents Library
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: <4,>=3.11.4
7
7
  Requires-Dist: dotenv>=0.9.9
8
8
  Requires-Dist: httpx<1,>=0.27.0
9
+ Requires-Dist: litellm>=1.74.4
9
10
  Requires-Dist: openai<2,>=1.68.2
10
11
  Requires-Dist: pydantic>=2
11
12
  Requires-Dist: pyyaml>=6.0.2
@@ -103,7 +104,7 @@ Ensure you have a `.env` file with your OpenAI and Google AI Studio API keys set
103
104
 
104
105
  ```
105
106
  OPENAI_API_KEY=your_openai_api_key
106
- GOOGLE_AI_STUDIO_API_KEY=your_google_ai_studio_api_key
107
+ GEMINI_API_KEY=your_gemini_api_key
107
108
  ```
108
109
 
109
110
  Create a script, e.g., `problem_recommender.py`:
@@ -117,7 +118,7 @@ from dotenv import load_dotenv
117
118
  from pydantic import BaseModel, Field
118
119
 
119
120
  from grasp_agents.grasp_logging import setup_logging
120
- from grasp_agents.openai import OpenAILLM, OpenAILLMSettings
121
+ from grasp_agents.litellm import LiteLLM, LiteLLMSettings
121
122
  from grasp_agents import LLMAgent, BaseTool, RunContext
122
123
 
123
124
  load_dotenv()
@@ -178,9 +179,9 @@ class Problem(BaseModel):
178
179
 
179
180
  teacher = LLMAgent[None, Problem, None](
180
181
  name="teacher",
181
- llm=OpenAILLM(
182
- model_name="openai:gpt-4.1",
183
- llm_settings=OpenAILLMSettings(temperature=0.5),
182
+ llm=LiteLLM(
183
+ model_name="gpt-4.1",
184
+ llm_settings=LiteLLMSettings(temperature=0.5),
184
185
  ),
185
186
  tools=[AskStudentTool()],
186
187
  react_mode=True,
@@ -0,0 +1,57 @@
1
+ grasp_agents/__init__.py,sha256=CIsyUasb9HBC3M4olg6ATAwKXtVNmmtpyGJrt7hpZW4,947
2
+ grasp_agents/cloud_llm.py,sha256=C6xrKYhiQb4tNXK_rFo2pNlVTXPS_gYd5uevAnpLFeE,13119
3
+ grasp_agents/comm_processor.py,sha256=Tg6At2Ybow2WnJNvykOXKol4z5k3l8ED-GSesnA6aG0,6816
4
+ grasp_agents/costs_dict.yaml,sha256=2MFNWtkv5W5WSCcv1Cj13B1iQLVv5Ot9pS_KW2Gu2DA,2510
5
+ grasp_agents/errors.py,sha256=s4JRvccRroxmiM5d27f2KvUxw5yK8mkEZO4OCo98BKc,2531
6
+ grasp_agents/generics_utils.py,sha256=5Pw3I9dlnKC2VGqYKC4ZZUO3Z_vTNT-NPFovNfPkl6I,6542
7
+ grasp_agents/grasp_logging.py,sha256=H1GYhXdQvVkmauFDZ-KDwvVmPQHZUUm9sRqX_ObK2xI,1111
8
+ grasp_agents/http_client.py,sha256=Es8NXGDkp4Nem7g24-jW0KFGA9Hp_o2Cv3cOvjup-iU,859
9
+ grasp_agents/llm.py,sha256=YCzHlb2gdWVy77DmQpcu0dyEzGp4VQa3UMRJKodCYSs,6557
10
+ grasp_agents/llm_agent.py,sha256=coXcYSd1G8UiYPqNpF1z5ESQcH2zhSHYO8QhQfRtqiE,14401
11
+ grasp_agents/llm_agent_memory.py,sha256=GZ2Z66_JC_sR10vATvR53ui62xxY4lDDtL0XvL_AQNk,1846
12
+ grasp_agents/llm_policy_executor.py,sha256=aWxXS2-7Twzaz8U07wslzR8zR9byl2bDha1ACKxUEvI,16734
13
+ grasp_agents/memory.py,sha256=uRplf46uxHDmiNR3q7nk9fGSPv_BUtp8zJ-vzxUrXXg,783
14
+ grasp_agents/packet.py,sha256=neJ139jgLKgZjLyXAwSe0lH08BftveYOsLFyri9MZ0w,785
15
+ grasp_agents/packet_pool.py,sha256=doG7UpTO7cqJi4YT0UP_vfOnOebqiwzBUHfoxy1TkPY,3282
16
+ grasp_agents/printer.py,sha256=JpEnal0wuI5dAVvg7IgzBSJsZgfydUVmfjXLGpEOE0I,10853
17
+ grasp_agents/processor.py,sha256=WKtU5BleZ7IGSqDv0ZpO8RgBY04SInMU7JIjh3m34wg,15828
18
+ grasp_agents/prompt_builder.py,sha256=3Ua-f-lRdoE2LomFWDiZILpKpfuQxAM-aHcY69hvv1U,7978
19
+ grasp_agents/run_context.py,sha256=dUxwx1yM3gSk2KqMIAeDVVftysdk7v_1Ht0EcLk9jJI,1242
20
+ grasp_agents/runner.py,sha256=yH1yUIhwpcAivLFFaq5pXJ9OQdFa4WjOs_uqJ4XTNQw,1456
21
+ grasp_agents/usage_tracker.py,sha256=ZQfVUUpG0C89hyPWT_JgXnjQOxoYmumcQ9t-aCfcMo8,3561
22
+ grasp_agents/utils.py,sha256=qKmGBwrQHw1-BgqRLuGTPKGs3J_zbrpk3nxnP1iZBiQ,6152
23
+ grasp_agents/litellm/__init__.py,sha256=wD8RZBYokFDfbS9Cs7nO_zKb3w7RIVwEGj7g2D5CJH0,4510
24
+ grasp_agents/litellm/completion_chunk_converters.py,sha256=J5PPxzoTBqkvKQnCoBxQxJo7Q8Xfl9cbv2GRZox8Cjo,2689
25
+ grasp_agents/litellm/completion_converters.py,sha256=JQ7XvQwwc-biFqVMcRO61SL5VGs_SkUvAhUz1QD7EmU,2516
26
+ grasp_agents/litellm/converters.py,sha256=3u648xjrphr9zPp12PO8fU13G4nI6_e9714Xcvh6SHc,4721
27
+ grasp_agents/litellm/lite_llm.py,sha256=tJngUfZb2t59Rv9glQPCGNILlLKvy_6wNrQQFS8OZrY,8096
28
+ grasp_agents/litellm/message_converters.py,sha256=PsGLIJEcAeEoluHIh-utEufJ_9WeMYzXkwnR-8jyULQ,2037
29
+ grasp_agents/openai/__init__.py,sha256=xaRnblUskiLvypIhMe4NRp9dxCG-gNR7dPiugUbPbhE,4717
30
+ grasp_agents/openai/completion_chunk_converters.py,sha256=3MnMskdlp7ycsggc1ok1XpCHaP4Us2rLYaxImPLw1eI,2573
31
+ grasp_agents/openai/completion_converters.py,sha256=UlDeQSl0AEFUS-QI5e8rrjfmXZojSYksJGnrXA7DmIk,2528
32
+ grasp_agents/openai/content_converters.py,sha256=sMsZhoatuL_8t0IdVaGWIVZLB4nyi1ajD61GewQmeY4,2503
33
+ grasp_agents/openai/converters.py,sha256=CXHF2GehEHLEzjL45HywZ_1qaB3N29-lbac5oBDnLGA,4634
34
+ grasp_agents/openai/message_converters.py,sha256=fhSN81uK51EGbLyM2-f0MvPX_UBrMy7SF3JQPo-dkXg,4686
35
+ grasp_agents/openai/openai_llm.py,sha256=uJbbCytqpv8OCncKdzpiUdkVh3mJWgo95Y9Xetk_Ptg,10556
36
+ grasp_agents/openai/tool_converters.py,sha256=IotZvpe3xMQcBfcjUTfAsn4LtZljj3zkU9bfpcoiqPw,1177
37
+ grasp_agents/rate_limiting/__init__.py,sha256=KRgtF_E7R3YfA2cpYcFcZ7wycV0pWVJ0xRQC7YhiIEQ,158
38
+ grasp_agents/rate_limiting/rate_limiter_chunked.py,sha256=BPgkUXvhmZhTpZs2T6uujNFuxH_kYHiISuf6_-eNhUc,5544
39
+ grasp_agents/rate_limiting/types.py,sha256=PbnNhEAcYedQdIpPJWud8HUVcxa_xZS2RDZu4c5jr40,1003
40
+ grasp_agents/rate_limiting/utils.py,sha256=oEDWDNHYMUdxOOG49PlAJochkZq8nnVBCo6JxPc1iSo,2007
41
+ grasp_agents/typing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ grasp_agents/typing/completion.py,sha256=PHJ01m7WI2KYQL8w7W2ti6hMsKEZnzYGaxbNcBCc_IE,2782
43
+ grasp_agents/typing/completion_chunk.py,sha256=t6PvkDWQxRN5xA4efBdeou46RifMFodBmZc45Sx7qxQ,7610
44
+ grasp_agents/typing/content.py,sha256=XFmLpNWkGhkw5JujO6UsYwhzTHkU67PfhzaXH2waLcQ,3659
45
+ grasp_agents/typing/converters.py,sha256=kHlocHQS8QnduZOzNPbj3aRD8JpvJd53oudYqWdOxKE,2978
46
+ grasp_agents/typing/events.py,sha256=XMwhEaVLq8vUBnusti5V02EOtx2TrlL1yUGbHVtiBdI,5130
47
+ grasp_agents/typing/io.py,sha256=RtBnxOiEJkfCPz7bEu1T1JXU7o71MhHK3RkeedzWwX0,237
48
+ grasp_agents/typing/message.py,sha256=o7bN84AgrC5Fm3Wx20gqL9ArAMcEtYvnHnXbb04ngCs,3224
49
+ grasp_agents/typing/tool.py,sha256=4N-k_GvHVPAFyVyEq7z_LYKkA24iQlGoVYiWCzGTgT4,1786
50
+ grasp_agents/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
+ grasp_agents/workflow/looped_workflow.py,sha256=eA2V4ftAjXth1OqQS_jTNS3D5_hzLGGyWfn4Ju47FQ0,6338
52
+ grasp_agents/workflow/sequential_workflow.py,sha256=wKlF6PvzLXkKDGlfvd8h-WI3AGNo6qyS2gkROzdPvWM,3421
53
+ grasp_agents/workflow/workflow_processor.py,sha256=I0sTMfWGeTcx0y0oYssz8B85_PFhbatHXrSJ31woI6Q,3015
54
+ grasp_agents-0.5.0.dist-info/METADATA,sha256=w21-UoIaSRaFmJe1KWfk4dQqQtvKmPfAQscIcMeMHjg,6865
55
+ grasp_agents-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
+ grasp_agents-0.5.0.dist-info/licenses/LICENSE.md,sha256=-nNNdWqGB8gJ2O-peFQ2Irshv5tW5pHKyTcYkwvH7CE,1201
57
+ grasp_agents-0.5.0.dist-info/RECORD,,