grasp_agents 0.4.7__py3-none-any.whl → 0.5.1__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.
- grasp_agents/cloud_llm.py +191 -224
- grasp_agents/comm_processor.py +101 -100
- grasp_agents/errors.py +69 -9
- grasp_agents/litellm/__init__.py +106 -0
- grasp_agents/litellm/completion_chunk_converters.py +68 -0
- grasp_agents/litellm/completion_converters.py +72 -0
- grasp_agents/litellm/converters.py +138 -0
- grasp_agents/litellm/lite_llm.py +210 -0
- grasp_agents/litellm/message_converters.py +66 -0
- grasp_agents/llm.py +84 -49
- grasp_agents/llm_agent.py +136 -120
- grasp_agents/llm_agent_memory.py +3 -3
- grasp_agents/llm_policy_executor.py +167 -174
- grasp_agents/memory.py +23 -0
- grasp_agents/openai/__init__.py +24 -9
- grasp_agents/openai/completion_chunk_converters.py +6 -6
- grasp_agents/openai/completion_converters.py +12 -14
- grasp_agents/openai/content_converters.py +1 -3
- grasp_agents/openai/converters.py +6 -8
- grasp_agents/openai/message_converters.py +21 -3
- grasp_agents/openai/openai_llm.py +155 -103
- grasp_agents/openai/tool_converters.py +4 -6
- grasp_agents/packet.py +5 -2
- grasp_agents/packet_pool.py +14 -13
- grasp_agents/printer.py +233 -73
- grasp_agents/processor.py +229 -91
- grasp_agents/prompt_builder.py +2 -2
- grasp_agents/run_context.py +11 -20
- grasp_agents/runner.py +42 -0
- grasp_agents/typing/completion.py +16 -9
- grasp_agents/typing/completion_chunk.py +51 -22
- grasp_agents/typing/events.py +95 -19
- grasp_agents/typing/message.py +25 -1
- grasp_agents/typing/tool.py +2 -0
- grasp_agents/usage_tracker.py +31 -37
- grasp_agents/utils.py +95 -84
- grasp_agents/workflow/looped_workflow.py +60 -11
- grasp_agents/workflow/sequential_workflow.py +43 -11
- grasp_agents/workflow/workflow_processor.py +25 -24
- {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.1.dist-info}/METADATA +7 -6
- grasp_agents-0.5.1.dist-info/RECORD +57 -0
- grasp_agents-0.4.7.dist-info/RECORD +0 -50
- {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.1.dist-info}/WHEEL +0 -0
- {grasp_agents-0.4.7.dist-info → grasp_agents-0.5.1.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
|
9
|
+
from typing import Annotated, Any, TypeVar, get_args, get_origin
|
10
10
|
|
11
|
-
from pydantic import TypeAdapter
|
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
|
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,
|
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
|
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
|
57
|
-
|
58
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
76
|
+
schema: Any,
|
81
77
|
from_substring: bool = False,
|
82
78
|
strip_language_markdown: bool = True,
|
83
|
-
) ->
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
97
|
+
schema_by_xml_tag: Mapping[str, Any],
|
89
98
|
from_substring: bool = False,
|
90
99
|
strip_language_markdown: bool = True,
|
91
|
-
) ->
|
92
|
-
|
93
|
-
|
94
|
-
|
100
|
+
) -> Mapping[str, Any]:
|
101
|
+
validated_obj_per_tag: dict[str, Any] = {}
|
102
|
+
_schema: Any = None
|
103
|
+
_tag: str | None = None
|
95
104
|
|
96
|
-
|
97
|
-
for _tag,
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
80
|
-
self,
|
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.
|
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
|
-
|
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.
|
3
|
+
Version: 0.5.1
|
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
|
-
|
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.
|
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=
|
182
|
-
model_name="
|
183
|
-
llm_settings=
|
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=keHuNEZNSxHT9FKpMohHOCNi7UAz_oRIc91IQEuzaWE,1162
|
14
|
+
grasp_agents/packet.py,sha256=neJ139jgLKgZjLyXAwSe0lH08BftveYOsLFyri9MZ0w,785
|
15
|
+
grasp_agents/packet_pool.py,sha256=doG7UpTO7cqJi4YT0UP_vfOnOebqiwzBUHfoxy1TkPY,3282
|
16
|
+
grasp_agents/printer.py,sha256=Yp4aPFBZOHy6PHRlJIDj44UimYLdnTx-hcyebJQKMX8,10819
|
17
|
+
grasp_agents/processor.py,sha256=jel12KXWR8oA241NKt-sy0eWGbsfuBPwwdmj6Ecqg5s,15826
|
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.1.dist-info/METADATA,sha256=Llv8pchnnXoW-ndbZj1lU8wNGOFnpcRV_vS-4vZM0hk,6865
|
55
|
+
grasp_agents-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
56
|
+
grasp_agents-0.5.1.dist-info/licenses/LICENSE.md,sha256=-nNNdWqGB8gJ2O-peFQ2Irshv5tW5pHKyTcYkwvH7CE,1201
|
57
|
+
grasp_agents-0.5.1.dist-info/RECORD,,
|