grasp_agents 0.1.15__py3-none-any.whl → 0.1.17__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/agent_message.py +0 -1
- grasp_agents/base_agent.py +1 -1
- grasp_agents/cloud_llm.py +83 -40
- grasp_agents/comm_agent.py +40 -49
- grasp_agents/llm.py +6 -6
- grasp_agents/llm_agent.py +81 -63
- grasp_agents/memory.py +0 -6
- grasp_agents/openai/completion_converters.py +4 -3
- grasp_agents/openai/converters.py +2 -8
- grasp_agents/openai/message_converters.py +1 -6
- grasp_agents/openai/openai_llm.py +4 -6
- grasp_agents/openai/tool_converters.py +1 -1
- grasp_agents/{data_retrieval → rate_limiting}/rate_limiter_chunked.py +2 -9
- grasp_agents/{data_retrieval → rate_limiting}/utils.py +15 -5
- grasp_agents/tool_orchestrator.py +2 -2
- grasp_agents/typing/converters.py +2 -10
- grasp_agents/typing/io.py +1 -4
- grasp_agents/typing/message.py +5 -3
- grasp_agents/typing/tool.py +18 -11
- grasp_agents/utils.py +114 -65
- grasp_agents-0.1.17.dist-info/METADATA +212 -0
- grasp_agents-0.1.17.dist-info/RECORD +44 -0
- grasp_agents-0.1.15.dist-info/METADATA +0 -152
- grasp_agents-0.1.15.dist-info/RECORD +0 -44
- /grasp_agents/{data_retrieval → rate_limiting}/__init__.py +0 -0
- /grasp_agents/{data_retrieval → rate_limiting}/types.py +0 -0
- {grasp_agents-0.1.15.dist-info → grasp_agents-0.1.17.dist-info}/WHEEL +0 -0
- {grasp_agents-0.1.15.dist-info → grasp_agents-0.1.17.dist-info}/licenses/LICENSE.md +0 -0
grasp_agents/utils.py
CHANGED
@@ -1,57 +1,38 @@
|
|
1
1
|
import ast
|
2
2
|
import asyncio
|
3
|
-
from datetime import datetime
|
4
3
|
import functools
|
5
4
|
import json
|
6
5
|
import re
|
7
6
|
from collections.abc import Callable, Coroutine
|
8
7
|
from copy import deepcopy
|
8
|
+
from datetime import datetime
|
9
|
+
from logging import getLogger
|
9
10
|
from pathlib import Path
|
10
11
|
from typing import Any, TypeVar, cast
|
11
12
|
|
12
|
-
from pydantic import BaseModel, create_model
|
13
|
+
from pydantic import BaseModel, GetCoreSchemaHandler, TypeAdapter, create_model
|
13
14
|
from pydantic.fields import FieldInfo
|
15
|
+
from pydantic_core import core_schema
|
14
16
|
from tqdm.autonotebook import tqdm
|
15
17
|
|
18
|
+
logger = getLogger(__name__)
|
16
19
|
|
17
|
-
def read_contents_from_file(
|
18
|
-
file_path: str | Path,
|
19
|
-
binary_mode: bool = False,
|
20
|
-
) -> str:
|
21
|
-
"""Reads and returns contents of file"""
|
22
|
-
try:
|
23
|
-
if binary_mode:
|
24
|
-
with open(file_path, "rb") as file:
|
25
|
-
return file.read()
|
26
|
-
else:
|
27
|
-
with open(file_path) as file:
|
28
|
-
return file.read()
|
29
|
-
except FileNotFoundError:
|
30
|
-
print(f"File {file_path} not found.")
|
31
|
-
return ""
|
32
|
-
|
33
|
-
|
34
|
-
async def asyncio_gather_with_pbar(
|
35
|
-
*corouts: Coroutine[Any, Any, Any],
|
36
|
-
no_tqdm: bool = False,
|
37
|
-
desc: str | None = None,
|
38
|
-
) -> list[Any]:
|
39
|
-
pbar = tqdm(total=len(corouts), desc=desc, disable=no_tqdm)
|
40
20
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
21
|
+
def merge_pydantic_models(*models: type[BaseModel]) -> type[BaseModel]:
|
22
|
+
fields_dict: dict[str, FieldInfo] = {}
|
23
|
+
for model in models:
|
24
|
+
for field_name, field_info in model.model_fields.items():
|
25
|
+
if field_name in fields_dict:
|
26
|
+
raise ValueError(
|
27
|
+
f"Field conflict detected: '{field_name}' exists in multiple models"
|
28
|
+
)
|
29
|
+
fields_dict[field_name] = field_info
|
49
30
|
|
50
|
-
return
|
31
|
+
return create_model("MergedModel", __module__=__name__, **fields_dict) # type: ignore
|
51
32
|
|
52
33
|
|
53
|
-
def
|
54
|
-
return
|
34
|
+
def filter_fields(data: dict[str, Any], model: type[BaseModel]) -> dict[str, Any]:
|
35
|
+
return {key: data[key] for key in model.model_fields if key in data}
|
55
36
|
|
56
37
|
|
57
38
|
def read_txt(file_path: str) -> str:
|
@@ -73,26 +54,32 @@ def format_json_string(text: str) -> str:
|
|
73
54
|
pass
|
74
55
|
i += 1
|
75
56
|
|
76
|
-
return
|
57
|
+
return text
|
77
58
|
|
78
59
|
|
79
|
-
def read_json_string(
|
60
|
+
def read_json_string(
|
61
|
+
json_str: str, return_none_on_failure: bool = False
|
62
|
+
) -> dict[str, Any] | list[Any] | None:
|
80
63
|
try:
|
81
64
|
json_response = ast.literal_eval(json_str)
|
82
65
|
except (ValueError, SyntaxError):
|
83
66
|
try:
|
84
67
|
json_response = json.loads(json_str)
|
85
68
|
except json.JSONDecodeError as exc:
|
69
|
+
if return_none_on_failure:
|
70
|
+
return None
|
86
71
|
raise ValueError(
|
87
72
|
"Invalid JSON - Both ast.literal_eval and json.loads "
|
88
73
|
f"failed to parse the following response:\n{json_str}"
|
89
74
|
) from exc
|
90
75
|
|
91
|
-
return json_response
|
76
|
+
return json_response
|
92
77
|
|
93
78
|
|
94
|
-
def extract_json(
|
95
|
-
|
79
|
+
def extract_json(
|
80
|
+
json_str: str, return_none_on_failure: bool = False
|
81
|
+
) -> dict[str, Any] | list[Any] | None:
|
82
|
+
return read_json_string(format_json_string(json_str), return_none_on_failure)
|
96
83
|
|
97
84
|
|
98
85
|
def extract_xml_list(text: str) -> list[str]:
|
@@ -105,32 +92,43 @@ def extract_xml_list(text: str) -> list[str]:
|
|
105
92
|
return chunks
|
106
93
|
|
107
94
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
)
|
127
|
-
|
128
|
-
|
129
|
-
|
95
|
+
def make_conditional_parsed_output_type(
|
96
|
+
response_format: type, marker: str = "<DONE>"
|
97
|
+
) -> type:
|
98
|
+
class ParsedOutput:
|
99
|
+
"""
|
100
|
+
* Accepts any **str**.
|
101
|
+
* If the string contains `marker`, it must contain a valid JSON for
|
102
|
+
`response_format` → we return that a response_format instance.
|
103
|
+
* Otherwise we leave the string untouched.
|
104
|
+
"""
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def __get_pydantic_core_schema__(
|
108
|
+
cls,
|
109
|
+
_source_type: Any,
|
110
|
+
_handler: GetCoreSchemaHandler,
|
111
|
+
) -> core_schema.CoreSchema:
|
112
|
+
def validator(v: Any) -> Any:
|
113
|
+
if isinstance(v, str) and marker in v:
|
114
|
+
v_json_str = format_json_string(v)
|
115
|
+
response_format_adapter = TypeAdapter[Any](response_format)
|
116
|
+
|
117
|
+
return response_format_adapter.validate_json(v_json_str)
|
118
|
+
|
119
|
+
return v
|
120
|
+
|
121
|
+
return core_schema.no_info_after_validator_function(
|
122
|
+
validator, core_schema.any_schema()
|
123
|
+
)
|
130
124
|
|
125
|
+
@classmethod
|
126
|
+
def __get_pydantic_json_schema__(
|
127
|
+
cls, core_schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler
|
128
|
+
):
|
129
|
+
return handler(core_schema)
|
131
130
|
|
132
|
-
|
133
|
-
return {key: data[key] for key in model.model_fields if key in data}
|
131
|
+
return ParsedOutput
|
134
132
|
|
135
133
|
|
136
134
|
T = TypeVar("T", bound=Callable[..., Any])
|
@@ -149,3 +147,54 @@ def forbid_state_change(method: T) -> T:
|
|
149
147
|
return result
|
150
148
|
|
151
149
|
return cast("T", wrapper)
|
150
|
+
|
151
|
+
|
152
|
+
def read_contents_from_file(
|
153
|
+
file_path: str | Path,
|
154
|
+
binary_mode: bool = False,
|
155
|
+
) -> str | bytes:
|
156
|
+
"""Reads and returns contents of file"""
|
157
|
+
try:
|
158
|
+
if binary_mode:
|
159
|
+
with open(file_path, "rb") as file:
|
160
|
+
return file.read()
|
161
|
+
else:
|
162
|
+
with open(file_path) as file:
|
163
|
+
return file.read()
|
164
|
+
except FileNotFoundError:
|
165
|
+
logger.error(f"File {file_path} not found.")
|
166
|
+
return ""
|
167
|
+
|
168
|
+
|
169
|
+
def get_prompt(prompt_text: str | None, prompt_path: str | Path | None) -> str | None:
|
170
|
+
if prompt_text is None:
|
171
|
+
prompt = (
|
172
|
+
read_contents_from_file(prompt_path) if prompt_path is not None else None
|
173
|
+
)
|
174
|
+
else:
|
175
|
+
prompt = prompt_text
|
176
|
+
|
177
|
+
return prompt # type: ignore[assignment]
|
178
|
+
|
179
|
+
|
180
|
+
async def asyncio_gather_with_pbar(
|
181
|
+
*corouts: Coroutine[Any, Any, Any],
|
182
|
+
no_tqdm: bool = False,
|
183
|
+
desc: str | None = None,
|
184
|
+
) -> list[Any]:
|
185
|
+
pbar = tqdm(total=len(corouts), desc=desc, disable=no_tqdm)
|
186
|
+
|
187
|
+
async def run_and_update(coro: Coroutine[Any, Any, Any]) -> Any:
|
188
|
+
result = await coro
|
189
|
+
pbar.update(1)
|
190
|
+
return result
|
191
|
+
|
192
|
+
wrapped_tasks = [run_and_update(c) for c in corouts]
|
193
|
+
results = await asyncio.gather(*wrapped_tasks)
|
194
|
+
pbar.close()
|
195
|
+
|
196
|
+
return results
|
197
|
+
|
198
|
+
|
199
|
+
def get_timestamp() -> str:
|
200
|
+
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
@@ -0,0 +1,212 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: grasp_agents
|
3
|
+
Version: 0.1.17
|
4
|
+
Summary: Grasp Agents Library
|
5
|
+
License-File: LICENSE.md
|
6
|
+
Requires-Python: <4,>=3.11.4
|
7
|
+
Requires-Dist: dotenv>=0.9.9
|
8
|
+
Requires-Dist: httpx<1,>=0.27.0
|
9
|
+
Requires-Dist: openai<2,>=1.68.2
|
10
|
+
Requires-Dist: pydantic>=2
|
11
|
+
Requires-Dist: pyyaml>=6.0.2
|
12
|
+
Requires-Dist: tenacity>=9.1.2
|
13
|
+
Requires-Dist: termcolor<3,>=2.4.0
|
14
|
+
Requires-Dist: tqdm<5,>=4.66.2
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
17
|
+
# Grasp Agents
|
18
|
+
|
19
|
+
<br/>
|
20
|
+
<img src="./.assets/grasp.svg" alt="Grasp Agents" width="320" />
|
21
|
+
<br/>
|
22
|
+
<br/>
|
23
|
+
|
24
|
+
[](https://badge.fury.io/py/grasp-agents)
|
25
|
+
[](https://mit-license.org/)
|
26
|
+
[](https://pypi.org/project/grasp-agents/)
|
27
|
+
[](https://github.com/grasp-technologies/grasp-agents/stargazers)
|
28
|
+
[](https://github.com/grasp-technologies/grasp-agents/network/members)
|
29
|
+
|
30
|
+
## Overview
|
31
|
+
|
32
|
+
**Grasp Agents** is a modular Python framework for building agentic AI pipelines and applications. It is meant to be minimalistic but functional, allowing for rapid experimentation while keeping full and granular low-level control over prompting, LLM handling, and inter-agent communication by avoiding excessive higher-level abstractions.
|
33
|
+
|
34
|
+
## Features
|
35
|
+
|
36
|
+
- Clean formulation of agents as generic entities over:
|
37
|
+
* I/O schemas
|
38
|
+
* Agent state
|
39
|
+
* Shared context
|
40
|
+
- Transparent implementation of common agentic patterns:
|
41
|
+
* Single-agent loops with an optional "ReAct mode" to enforce reasoning between the tool calls
|
42
|
+
* Workflows (static communication topology), including loops
|
43
|
+
* Agents-as-tools for task delegation
|
44
|
+
* Freeform A2A communication via in-process Actor model
|
45
|
+
- Batch processing support outside of agentic loops
|
46
|
+
- Simple logging and usage/cost tracking
|
47
|
+
|
48
|
+
## Project Structure
|
49
|
+
|
50
|
+
- `base_agent.py`, `llm_agent.py`, `comm_agent.py`: Core agent class implementations.
|
51
|
+
- `agent_message.py`, `agent_message_pool.py`: Messaging and message pool management.
|
52
|
+
- `llm_agent_state.py`: State management for LLM agents.
|
53
|
+
- `tool_orchestrator.py`: Orchestration of tools used by agents.
|
54
|
+
- `prompt_builder.py`: Tools for constructing prompts.
|
55
|
+
- `workflow/`: Modules for defining and managing agent workflows.
|
56
|
+
- `cloud_llm.py`, `llm.py`: LLM integration and base LLM functionalities.
|
57
|
+
- `openai/`: Modules specific to OpenAI API integration.
|
58
|
+
- `memory.py`: Memory management for agents (currently only message history).
|
59
|
+
- `run_context.py`: Context management for agent runs.
|
60
|
+
- `usage_tracker.py`: Tracking of API usage and costs.
|
61
|
+
- `costs_dict.yaml`: Dictionary for cost tracking (update if needed).
|
62
|
+
- `rate_limiting/`: Basic rate limiting tools.
|
63
|
+
|
64
|
+
## Quickstart & Installation Variants (UV Package manager)
|
65
|
+
|
66
|
+
> **Note:** You can check this sample project code in the [src/grasp_agents/examples/demo/uv](src/grasp_agents/examples/demo/uv) folder. Feel free to copy and paste the code from there to a separate project. There are also [examples](src/grasp_agents/examples/demo/) for other package managers.
|
67
|
+
|
68
|
+
#### 1. Prerequisites
|
69
|
+
|
70
|
+
Install the [UV Package Manager](https://github.com/astral-sh/uv):
|
71
|
+
|
72
|
+
```bash
|
73
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
74
|
+
```
|
75
|
+
|
76
|
+
#### 2. Create Project & Install Dependencies
|
77
|
+
|
78
|
+
```bash
|
79
|
+
mkdir my-test-uv-app
|
80
|
+
cd my-test-uv-app
|
81
|
+
uv init .
|
82
|
+
```
|
83
|
+
|
84
|
+
Create and activate a virtual environment:
|
85
|
+
|
86
|
+
```bash
|
87
|
+
uv venv
|
88
|
+
source .venv/bin/activate
|
89
|
+
```
|
90
|
+
|
91
|
+
Add and sync dependencies:
|
92
|
+
|
93
|
+
```bash
|
94
|
+
uv add grasp_agents
|
95
|
+
uv sync
|
96
|
+
```
|
97
|
+
|
98
|
+
#### 3. Example Usage
|
99
|
+
|
100
|
+
Ensure you have a `.env` file with your OpenAI and Google AI Studio API keys set
|
101
|
+
|
102
|
+
```
|
103
|
+
OPENAI_API_KEY=your_openai_api_key
|
104
|
+
GOOGLE_AI_STUDIO_API_KEY=your_google_ai_studio_api_key
|
105
|
+
```
|
106
|
+
|
107
|
+
Create a script, e.g., `problem_recommender.py`:
|
108
|
+
|
109
|
+
```python
|
110
|
+
import re
|
111
|
+
from typing import Any
|
112
|
+
from pathlib import Path
|
113
|
+
from pydantic import BaseModel, Field
|
114
|
+
from dotenv import load_dotenv
|
115
|
+
from grasp_agents.typing.tool import BaseTool
|
116
|
+
from grasp_agents.typing.io import AgentPayload
|
117
|
+
from grasp_agents.run_context import RunContextWrapper
|
118
|
+
from grasp_agents.openai.openai_llm import OpenAILLM, OpenAILLMSettings
|
119
|
+
from grasp_agents.llm_agent import LLMAgent
|
120
|
+
from grasp_agents.grasp_logging import setup_logging
|
121
|
+
from grasp_agents.typing.message import Conversation
|
122
|
+
|
123
|
+
load_dotenv()
|
124
|
+
|
125
|
+
|
126
|
+
# Configure the logger to output to the console and/or a file
|
127
|
+
setup_logging(
|
128
|
+
logs_file_path="grasp_agents_demo.log",
|
129
|
+
logs_config_path=Path().cwd() / "configs/logging/default.yaml",
|
130
|
+
)
|
131
|
+
|
132
|
+
sys_prompt_react = """
|
133
|
+
Your task is to suggest an exciting stats problem to a student.
|
134
|
+
Ask the student about their education, interests, and preferences, then suggest a problem tailored to them.
|
135
|
+
|
136
|
+
# Instructions
|
137
|
+
* Ask questions one by one.
|
138
|
+
* Provide your thinking before asking a question and after receiving a reply.
|
139
|
+
* The problem must be enclosed in <PROBLEM> tags.
|
140
|
+
"""
|
141
|
+
|
142
|
+
|
143
|
+
class TeacherQuestion(BaseModel):
|
144
|
+
question: str = Field(..., description="The question to ask the student.")
|
145
|
+
|
146
|
+
StudentReply = str
|
147
|
+
|
148
|
+
|
149
|
+
class AskStudentTool(BaseTool[TeacherQuestion, StudentReply, Any]):
|
150
|
+
name: str = "ask_student_tool"
|
151
|
+
description: str = "Ask the student a question and get their reply."
|
152
|
+
in_schema: type[TeacherQuestion] = TeacherQuestion
|
153
|
+
out_schema: type[StudentReply] = StudentReply
|
154
|
+
|
155
|
+
async def run(
|
156
|
+
self, inp: TeacherQuestion, ctx: RunContextWrapper[Any] | None = None
|
157
|
+
) -> StudentReply:
|
158
|
+
return input(inp.question)
|
159
|
+
|
160
|
+
|
161
|
+
class FinalResponse(AgentPayload):
|
162
|
+
problem: str
|
163
|
+
|
164
|
+
|
165
|
+
teacher = LLMAgent[Any, FinalResponse, None](
|
166
|
+
agent_id="teacher",
|
167
|
+
llm=OpenAILLM(
|
168
|
+
model_name="gpt-4.1",
|
169
|
+
api_provider="openai",
|
170
|
+
llm_settings=OpenAILLMSettings(temperature=0.1),
|
171
|
+
),
|
172
|
+
tools=[AskStudentTool()],
|
173
|
+
max_turns=20,
|
174
|
+
react_mode=True,
|
175
|
+
sys_prompt=sys_prompt_react,
|
176
|
+
out_schema=FinalResponse,
|
177
|
+
set_state_strategy="reset",
|
178
|
+
)
|
179
|
+
|
180
|
+
|
181
|
+
@teacher.tool_call_loop_exit_handler
|
182
|
+
def exit_tool_call_loop(conversation: Conversation, ctx, **kwargs) -> None:
|
183
|
+
message_text = conversation[-1].content
|
184
|
+
|
185
|
+
return re.search(r"<PROBLEM>", message_text)
|
186
|
+
|
187
|
+
|
188
|
+
@teacher.parse_output_handler
|
189
|
+
def parse_output(conversation: Conversation, ctx, **kwargs) -> FinalResponse:
|
190
|
+
message_text = conversation[-1].content
|
191
|
+
matches = re.findall(r"<PROBLEM>(.*?)</PROBLEM>", message_text, re.DOTALL)
|
192
|
+
|
193
|
+
return FinalResponse(problem=matches[0])
|
194
|
+
|
195
|
+
|
196
|
+
async def main():
|
197
|
+
ctx = RunContextWrapper(print_messages=True)
|
198
|
+
out = await teacher.run(ctx=ctx)
|
199
|
+
print(out.payloads[0].problem)
|
200
|
+
print(ctx.usage_tracker.total_usage)
|
201
|
+
|
202
|
+
|
203
|
+
asyncio.run(main())
|
204
|
+
```
|
205
|
+
|
206
|
+
Run your script:
|
207
|
+
|
208
|
+
```bash
|
209
|
+
uv run problem_recommender.py
|
210
|
+
```
|
211
|
+
|
212
|
+
You can find more examples in [src/grasp_agents/examples/notebooks/agents_demo.ipynb](src/grasp_agents/examples/notebooks/agents_demo.ipynb).
|
@@ -0,0 +1,44 @@
|
|
1
|
+
grasp_agents/agent_message.py,sha256=Z-czIHNSe7eAo5r6q2Zu10HFpUjQjVbayyglGyyj4Lw,911
|
2
|
+
grasp_agents/agent_message_pool.py,sha256=4O4xz-aZ_I5m3iLAUiMAQcVn5AGRP4daUM8XME03Xsw,3250
|
3
|
+
grasp_agents/base_agent.py,sha256=4mNMyWdt9MTzg64JhrVjcU_FSYDj0ED_AKpRZXCRVb4,1870
|
4
|
+
grasp_agents/cloud_llm.py,sha256=JJmIUu8SOJ4TElQ4B19mPgXJ1SiapZdPFFhA2YVRKRw,13015
|
5
|
+
grasp_agents/comm_agent.py,sha256=ukkT0zXgE8-yt63fyi3MQ_3Q0QjwIDHKz4MbHt9FBxw,7323
|
6
|
+
grasp_agents/costs_dict.yaml,sha256=EW6XxRXLZobMwQEEiUNYALbDzfbZFb2zEVCaTSAqYjw,2334
|
7
|
+
grasp_agents/grasp_logging.py,sha256=H1GYhXdQvVkmauFDZ-KDwvVmPQHZUUm9sRqX_ObK2xI,1111
|
8
|
+
grasp_agents/http_client.py,sha256=KZva2MjJjuI5ohUeU8RdTAImUnQYaqBrV2jDH8smbJw,738
|
9
|
+
grasp_agents/llm.py,sha256=RZY25UNJEdPUmqOHifUrrQTgfoDCAVtZN9WQvvxnLC4,3004
|
10
|
+
grasp_agents/llm_agent.py,sha256=_D16rA8LDhALZKYYuWxB-E75g3uBgzM04EcJyuRMKZA,12619
|
11
|
+
grasp_agents/llm_agent_state.py,sha256=91K1-8Uodbe-t_I6nu0xBzHfQjssZYCHjMuDbu5aCr0,2327
|
12
|
+
grasp_agents/memory.py,sha256=X1YtVX8XxP5KnGPMW8BqjID8QK4hTG2obxoyhnnZ4pU,5575
|
13
|
+
grasp_agents/printer.py,sha256=Jk6OJExio53gbKBod5Dd8Y3CWYrVb4K5q4UJ8i9cQvo,5024
|
14
|
+
grasp_agents/prompt_builder.py,sha256=rYVIY4adJwBitjrTYvpEh5x8C7cLbIiXxT1F-vQuvEM,7393
|
15
|
+
grasp_agents/run_context.py,sha256=hyETO3-p0azPFws75kX6rrUDLf58Ar6jmyt6TQ5Po78,2589
|
16
|
+
grasp_agents/tool_orchestrator.py,sha256=6VX8FGYeUHiSt9GpUjOga7KGP55Kzs1jEiNcOZysIAo,5501
|
17
|
+
grasp_agents/usage_tracker.py,sha256=5YuN6hpg6HASdg-hOylgWzhCiORmDMnZuQtbISfhm_4,3378
|
18
|
+
grasp_agents/utils.py,sha256=-OdCs27FKek8owGbwmAVWwLXDgEzoTu-B5_EwRLEsz4,5869
|
19
|
+
grasp_agents/openai/__init__.py,sha256=qN8HMAatSJKOsA6v-JwakMYguwkswCVHqrmK1gFy9wI,3096
|
20
|
+
grasp_agents/openai/completion_converters.py,sha256=lX9h1kaGAo5ttsl-4V7l4x8IpjxJaJJtyU2cKu3-EOc,1871
|
21
|
+
grasp_agents/openai/content_converters.py,sha256=6GI0D7xJalzsiawAJOyCUzTJTo0NQdpv87YKmfN0LYQ,2631
|
22
|
+
grasp_agents/openai/converters.py,sha256=DBXBxow9oRG6pc8inpZBLiuUqHzVfpscmHFpN9bAdvc,5276
|
23
|
+
grasp_agents/openai/message_converters.py,sha256=KjF6FbXzwlWdM-1YT3cswUV-74sjiwOhLFPMY4sJ5Xk,4593
|
24
|
+
grasp_agents/openai/openai_llm.py,sha256=dscGlVhH4v1yAw4NRPgJdww9toOoMRpIqA6HD4IGWOs,6132
|
25
|
+
grasp_agents/openai/tool_converters.py,sha256=KhWRETkjhjocISUo_HBZ8QfBiyTOoC5WurPNAR4BYxc,1027
|
26
|
+
grasp_agents/rate_limiting/__init__.py,sha256=KRgtF_E7R3YfA2cpYcFcZ7wycV0pWVJ0xRQC7YhiIEQ,158
|
27
|
+
grasp_agents/rate_limiting/rate_limiter_chunked.py,sha256=jp6bqWGq2qYAxbmyU_bAGeBXM_nEmSrR6VabDutcTA0,5800
|
28
|
+
grasp_agents/rate_limiting/types.py,sha256=JbLYJC-gmzcHH_4-YNTz9IcIwVpcpDyDGvljxNznf5k,1389
|
29
|
+
grasp_agents/rate_limiting/utils.py,sha256=yrqboDWWqeekDcs0xaeOPOvelhgAlywDpQpscpPqlhg,1995
|
30
|
+
grasp_agents/typing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
+
grasp_agents/typing/completion.py,sha256=_KDLx3Gtz7o-pEZrvAFgCZwDmkr2oQkxrL-2LSXHHsw,657
|
32
|
+
grasp_agents/typing/content.py,sha256=13nLNZqZgtpo9sM0vCRQmZ4bQjjZqUSElMQOwjL7bO8,3651
|
33
|
+
grasp_agents/typing/converters.py,sha256=EbGur_Ngx-wINfyOEHa7JqmKnMAxZ5vJjhAPoqBf_AM,3048
|
34
|
+
grasp_agents/typing/io.py,sha256=M294tNROa9gJ_I38c9pLekEbUK2p3qirYdE_QGpuw1c,624
|
35
|
+
grasp_agents/typing/message.py,sha256=oCpqD_CV2Da-M-l-e5liFJSwK8267fxfcU68LIc7C1E,3801
|
36
|
+
grasp_agents/typing/tool.py,sha256=t40vr3ljQY_Qx0f0KZIc151bYlHUF7Bf7JfOkiJOi2c,1657
|
37
|
+
grasp_agents/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
+
grasp_agents/workflow/looped_agent.py,sha256=YBOgOIvy3_NwKvEoGgzQJ2fY9SNG66MQk6obSBGWvCc,3896
|
39
|
+
grasp_agents/workflow/sequential_agent.py,sha256=yDt2nA-b1leVByD8jsKrWD6bHe0o9z33jrOJGOLwbyk,2004
|
40
|
+
grasp_agents/workflow/workflow_agent.py,sha256=9U94IQ39Vb1W_5u8aoqHb65ikdarEhEJkexDz8xwHD4,2294
|
41
|
+
grasp_agents-0.1.17.dist-info/METADATA,sha256=YG_CGwTAOAQFAOA_Aqi6pqwF_ODYtzY_nQEgf2IRMFM,7001
|
42
|
+
grasp_agents-0.1.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
43
|
+
grasp_agents-0.1.17.dist-info/licenses/LICENSE.md,sha256=Kfeo0gdlLS6tLQiWwO9UWhjp9-f93a5kShSiBp2FG-c,1201
|
44
|
+
grasp_agents-0.1.17.dist-info/RECORD,,
|
@@ -1,152 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: grasp_agents
|
3
|
-
Version: 0.1.15
|
4
|
-
Summary: Grasp Agents Library
|
5
|
-
License-File: LICENSE.md
|
6
|
-
Requires-Python: <4,>=3.11.4
|
7
|
-
Requires-Dist: dotenv>=0.9.9
|
8
|
-
Requires-Dist: httpx<1,>=0.27.0
|
9
|
-
Requires-Dist: openai<2,>=1.68.2
|
10
|
-
Requires-Dist: pyyaml>=6.0.2
|
11
|
-
Requires-Dist: tenacity>=9.1.2
|
12
|
-
Requires-Dist: termcolor<3,>=2.4.0
|
13
|
-
Requires-Dist: tqdm<5,>=4.66.2
|
14
|
-
Description-Content-Type: text/markdown
|
15
|
-
|
16
|
-
# Grasp Agents
|
17
|
-
|
18
|
-
<br/>
|
19
|
-
<img src="./.assets/grasp.svg" alt="Grasp Agents" width="320" />
|
20
|
-
<br/>
|
21
|
-
<br/>
|
22
|
-
|
23
|
-
[](https://badge.fury.io/py/grasp-agents)
|
24
|
-
[](https://mit-license.org/)
|
25
|
-
[](https://pypi.org/project/grasp-agents/)
|
26
|
-
[](https://github.com/grasp-technologies/grasp-agents/stargazers)
|
27
|
-
[](https://github.com/grasp-technologies/grasp-agents/network/members)
|
28
|
-
|
29
|
-
## Overview
|
30
|
-
|
31
|
-
**Grasp Agents** is a modular Python framework for building agentic AI pipelines and applications. It provides reusable agent classes, message handling, LLM integration, memory, and orchestration utilities. The framework is designed for flexibility, composability, and clarity, enabling rapid prototyping and robust development of multi-agent systems.
|
32
|
-
|
33
|
-
## Features
|
34
|
-
|
35
|
-
- Modular agent base classes
|
36
|
-
- Message and memory management
|
37
|
-
- LLM and tool orchestration
|
38
|
-
- Logging and usage tracking
|
39
|
-
- Extensible architecture
|
40
|
-
|
41
|
-
## Project Structure
|
42
|
-
|
43
|
-
- `src/grasp_agents/` — Core framework modules
|
44
|
-
- `base_agent.py`, `llm_agent.py`, `comm_agent.py`: Agent classes
|
45
|
-
- `agent_message.py`, `agent_message_pool.py`: Messaging
|
46
|
-
- `memory.py`: Memory management
|
47
|
-
- `cloud_llm.py`, `llm.py`: LLM integration
|
48
|
-
- `tool_orchestrator.py`: Tool orchestration
|
49
|
-
- `usage_tracker.py`, `grasp_logging.py`: Usage and logging
|
50
|
-
- `data_retrieval/`, `openai/`, `typing/`, `workflow/`: Extensions and utilities
|
51
|
-
- `configs/` — Configuration files
|
52
|
-
- `data/` — Logs and datasets
|
53
|
-
|
54
|
-
## Quickstart & Installation Variants (UV Package manager)
|
55
|
-
|
56
|
-
### Option 1: UV Package Manager Project
|
57
|
-
|
58
|
-
> **Note:** You can check this sample project code in the [src/grasp_agents/examples/demo/uv](src/grasp_agents/examples/demo/uv) folder. Feel free to copy and paste the code from there to a separate project. There are also [examples](src/grasp_agents/examples/demo/) for other package managers.
|
59
|
-
|
60
|
-
#### 1. Prerequisites
|
61
|
-
|
62
|
-
Install the [UV Package Manager](https://github.com/astral-sh/uv):
|
63
|
-
|
64
|
-
```bash
|
65
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
66
|
-
```
|
67
|
-
|
68
|
-
#### 2. Create Project & Install Dependencies
|
69
|
-
|
70
|
-
```bash
|
71
|
-
mkdir my-test-uv-app
|
72
|
-
cd my-test-uv-app
|
73
|
-
uv init .
|
74
|
-
```
|
75
|
-
|
76
|
-
Create and activate a virtual environment:
|
77
|
-
|
78
|
-
```bash
|
79
|
-
uv venv
|
80
|
-
source .venv/bin/activate
|
81
|
-
```
|
82
|
-
|
83
|
-
Add and sync dependencies:
|
84
|
-
|
85
|
-
```bash
|
86
|
-
uv add grasp_agents
|
87
|
-
uv sync
|
88
|
-
```
|
89
|
-
|
90
|
-
#### 3. Example Usage
|
91
|
-
|
92
|
-
Create a file, e.g., `hello.py`:
|
93
|
-
|
94
|
-
Ensure you have a `.env` file with your OpenAI and Google AI Studio API keys set
|
95
|
-
|
96
|
-
```
|
97
|
-
OPENAI_API_KEY=your_openai_api_key
|
98
|
-
GOOGLE_AI_STUDIO_API_KEY=your_google_ai_studio_api_key
|
99
|
-
```
|
100
|
-
|
101
|
-
```python
|
102
|
-
import asyncio
|
103
|
-
from typing import Any
|
104
|
-
|
105
|
-
from grasp_agents.llm_agent import LLMAgent
|
106
|
-
from grasp_agents.openai.openai_llm import (
|
107
|
-
OpenAILLM,
|
108
|
-
OpenAILLMSettings,
|
109
|
-
)
|
110
|
-
from grasp_agents.typing.io import (
|
111
|
-
AgentPayload,
|
112
|
-
)
|
113
|
-
from grasp_agents.run_context import RunContextWrapper
|
114
|
-
|
115
|
-
from dotenv import load_dotenv
|
116
|
-
|
117
|
-
load_dotenv()
|
118
|
-
|
119
|
-
class Response(AgentPayload):
|
120
|
-
response: str
|
121
|
-
|
122
|
-
|
123
|
-
chatbot = LLMAgent[Any, Response, None](
|
124
|
-
agent_id="chatbot",
|
125
|
-
llm=OpenAILLM(
|
126
|
-
model_name="gpt-4o",
|
127
|
-
llm_settings=OpenAILLMSettings(),
|
128
|
-
),
|
129
|
-
sys_prompt=None,
|
130
|
-
out_schema=Response,
|
131
|
-
)
|
132
|
-
|
133
|
-
|
134
|
-
@chatbot.parse_output_handler
|
135
|
-
def output_handler(conversation, ctx, **kwargs) -> Response:
|
136
|
-
return Response(response=conversation[-1].content)
|
137
|
-
|
138
|
-
|
139
|
-
async def main():
|
140
|
-
ctx = RunContextWrapper(print_messages=True)
|
141
|
-
out = await chatbot.run("Hello, agent!", ctx=ctx)
|
142
|
-
print(out.payloads[0].response)
|
143
|
-
|
144
|
-
|
145
|
-
asyncio.run(main())
|
146
|
-
```
|
147
|
-
|
148
|
-
Run your script:
|
149
|
-
|
150
|
-
```bash
|
151
|
-
uv run hello.py
|
152
|
-
```
|