crewai-pastdotdev 0.1.0__tar.gz
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.
- crewai_pastdotdev-0.1.0/.gitignore +7 -0
- crewai_pastdotdev-0.1.0/LICENSE +21 -0
- crewai_pastdotdev-0.1.0/PKG-INFO +225 -0
- crewai_pastdotdev-0.1.0/README.md +195 -0
- crewai_pastdotdev-0.1.0/pyproject.toml +54 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/__init__.py +23 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/_common.py +85 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/_version.py +1 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/client.py +289 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/memory.py +366 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/py.typed +0 -0
- crewai_pastdotdev-0.1.0/src/crewai_pastdotdev/tools.py +168 -0
- crewai_pastdotdev-0.1.0/tests/conftest.py +149 -0
- crewai_pastdotdev-0.1.0/tests/test_client.py +193 -0
- crewai_pastdotdev-0.1.0/tests/test_crew_e2e.py +91 -0
- crewai_pastdotdev-0.1.0/tests/test_live.py +39 -0
- crewai_pastdotdev-0.1.0/tests/test_memory.py +168 -0
- crewai_pastdotdev-0.1.0/tests/test_tools.py +104 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 past.dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: crewai-pastdotdev
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: past dev for CrewAI: Ingest and Recall as crew memory and as agent tools.
|
|
5
|
+
Project-URL: Homepage, https://past.dev
|
|
6
|
+
Project-URL: Documentation, https://past.dev/docs/memory-api/quickstart
|
|
7
|
+
Project-URL: API reference, https://past.dev/docs/memory-api/api-reference
|
|
8
|
+
Project-URL: Source, https://github.com/pastdotdev/crewai-pastdotdev
|
|
9
|
+
Author-email: "past.dev" <louis@past.dev>
|
|
10
|
+
Maintainer-email: "past.dev" <louis@past.dev>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,crewai,llm,memory,past dev,recall
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: <3.14,>=3.10
|
|
25
|
+
Requires-Dist: crewai<2,>=1.14.6
|
|
26
|
+
Requires-Dist: httpx<1,>=0.27
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# crewai-pastdotdev
|
|
32
|
+
|
|
33
|
+
past dev is a memory API for AI agents. This package connects it to
|
|
34
|
+
[CrewAI](https://www.crewai.com): your crews keep what they learn in past dev
|
|
35
|
+
and recall it in later runs, with the date and the source of every memory.
|
|
36
|
+
|
|
37
|
+
It gives you two ways to use past dev:
|
|
38
|
+
|
|
39
|
+
- **`PastDevMemory`**: crew memory. Pass it as `memory=` to a `Crew` or an
|
|
40
|
+
`Agent`. CrewAI recalls before each task and saves each task result. A save
|
|
41
|
+
is a past dev **Ingest** and a search is a past dev **Recall**.
|
|
42
|
+
- **`PastDevRecallTool`** and **`PastDevIngestTool`**: tools that an agent
|
|
43
|
+
calls when it decides to. The model gives only the query or the content.
|
|
44
|
+
|
|
45
|
+
[Website](https://past.dev) ·
|
|
46
|
+
[Quickstart](https://past.dev/docs/memory-api/quickstart) ·
|
|
47
|
+
[API reference](https://past.dev/docs/memory-api/api-reference)
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install crewai-pastdotdev
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The package needs Python 3.10 to 3.13 and CrewAI 1.14.6 or later.
|
|
56
|
+
|
|
57
|
+
## Set the API key
|
|
58
|
+
|
|
59
|
+
Make a project API key in the past dev console. It starts with `past_sk_`.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export PAST_API_KEY="past_sk_..."
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If you run past dev in your own cloud, also set the API host. The default is
|
|
66
|
+
`https://api.past.dev`.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
export PAST_API_HOST="https://past.example.com"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also give `api_key=` and `api_host=` to each class.
|
|
73
|
+
|
|
74
|
+
## The identity
|
|
75
|
+
|
|
76
|
+
Every memory in past dev belongs to an **identity**: the id of the person or
|
|
77
|
+
account the memory is about, for example the user id in your application.
|
|
78
|
+
Recall requires an identity. Ingest makes the identity the first time it sees
|
|
79
|
+
it.
|
|
80
|
+
|
|
81
|
+
**Your code sets the identity, never the model.** Take it from the user
|
|
82
|
+
record or the trigger of the run, and give it to the constructor. The tools
|
|
83
|
+
have no identity argument, so an agent cannot read or write the memory of
|
|
84
|
+
another person. Make one memory or one tool for each user.
|
|
85
|
+
|
|
86
|
+
## Use past dev as crew memory
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from crewai import Agent, Crew, Task
|
|
90
|
+
from crewai_pastdotdev import PastDevMemory
|
|
91
|
+
|
|
92
|
+
memory = PastDevMemory(identity=current_user.id)
|
|
93
|
+
|
|
94
|
+
writer = Agent(
|
|
95
|
+
role="Account manager",
|
|
96
|
+
goal="Write the weekly report the way the customer wants it",
|
|
97
|
+
backstory="You know the customer well.",
|
|
98
|
+
)
|
|
99
|
+
report = Task(
|
|
100
|
+
description="Write this week's report for the customer.",
|
|
101
|
+
expected_output="A report in the format the customer prefers.",
|
|
102
|
+
agent=writer,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
crew = Crew(agents=[writer], tasks=[report], memory=memory)
|
|
106
|
+
crew.kickoff()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
What happens in a run:
|
|
110
|
+
|
|
111
|
+
1. Before each task, CrewAI recalls with the task description. The recalled
|
|
112
|
+
memories go into the agent's prompt.
|
|
113
|
+
2. After each task, CrewAI saves the task and its result. `PastDevMemory`
|
|
114
|
+
ingests them as one data point. past dev extracts the memories on its own
|
|
115
|
+
side, so no extra LLM call runs in your crew.
|
|
116
|
+
3. CrewAI also gives the agents its own "Search memory" and "Save to memory"
|
|
117
|
+
tools. They go through past dev too.
|
|
118
|
+
|
|
119
|
+
You can also call the memory directly:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
record = memory.remember("The customer wants the report on Mondays.")
|
|
123
|
+
for match in memory.recall("When does the customer want the report?"):
|
|
124
|
+
print(match.record.content, match.record.metadata["occurred_at"])
|
|
125
|
+
memory.forget(record_ids=[record.id]) # Delete data point
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Settings
|
|
129
|
+
|
|
130
|
+
| Argument | Default | What it does |
|
|
131
|
+
| :- | :- | :- |
|
|
132
|
+
| `identity` | required | The identity for every Ingest and Recall. |
|
|
133
|
+
| `api_key` | `PAST_API_KEY` | The project API key. |
|
|
134
|
+
| `api_host` | `PAST_API_HOST`, else `https://api.past.dev` | The API host. |
|
|
135
|
+
| `audience` | none | An audience slug for ingested memories. Without it, a memory is visible to the whole project. |
|
|
136
|
+
| `label` | none | A short title stored with each data point. |
|
|
137
|
+
| `recall_level` | `low` | The evidence budget of a recall: `low`, `medium`, `high` or `extra-high`. |
|
|
138
|
+
| `recall_max_tokens` | none | An exact evidence budget in tokens, instead of `recall_level`. |
|
|
139
|
+
| `read_only` | `False` | Recall only. Nothing is ingested. |
|
|
140
|
+
|
|
141
|
+
### How CrewAI concepts map to past dev
|
|
142
|
+
|
|
143
|
+
| CrewAI | past dev |
|
|
144
|
+
| :- | :- |
|
|
145
|
+
| `remember()`, `remember_many()` | Ingest (`POST /api/v1/ingest`). The source id is stable for the same identity and content, so a repeated save is free. |
|
|
146
|
+
| `recall()` | Recall (`POST /api/v1/recall`). `limit` is the number of memories, at most 200. |
|
|
147
|
+
| `forget(record_ids=[...])` | Delete data point (`DELETE /api/v1/data-points/{sourceId}`). |
|
|
148
|
+
| `MemoryMatch.score` | `1 / rank`. past dev ranks the results; it does not return a score. |
|
|
149
|
+
| Scopes, categories, agent role | Kept in the data point's metadata (`crewai_scope`, `crewai_categories`, `crewai_agent_role`). Recall does not filter on them. |
|
|
150
|
+
| `tree()`, `info()`, `list_records()`, `update()`, `reset()` | Not available: past dev keeps memory on its servers. Use the past dev console to browse. To change a fact, remember the new one: past dev marks the earlier statement as superseded. |
|
|
151
|
+
|
|
152
|
+
## Use past dev as agent tools
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from crewai import Agent
|
|
156
|
+
from crewai_pastdotdev import PastDevIngestTool, PastDevRecallTool
|
|
157
|
+
|
|
158
|
+
agent = Agent(
|
|
159
|
+
role="Support agent",
|
|
160
|
+
goal="Help the customer with what you know about them",
|
|
161
|
+
backstory="You remember every earlier conversation.",
|
|
162
|
+
tools=[
|
|
163
|
+
PastDevRecallTool(identity=current_user.id),
|
|
164
|
+
PastDevIngestTool(identity=current_user.id),
|
|
165
|
+
],
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Tool | The model gives | Other settings (in the constructor) |
|
|
170
|
+
| :- | :- | :- |
|
|
171
|
+
| `PastDevRecallTool` ("past dev Recall") | `query` | `identity`, `limit` (10), `level`, `api_key`, `api_host` |
|
|
172
|
+
| `PastDevIngestTool` ("past dev Ingest") | `content` | `identity`, `audience`, `label`, `metadata`, `api_key`, `api_host` |
|
|
173
|
+
|
|
174
|
+
The recall tool returns the memories as text: rank, date, content, and the
|
|
175
|
+
quotations from their sources. If a memory was superseded by a later
|
|
176
|
+
statement, the text says so.
|
|
177
|
+
|
|
178
|
+
## Credits and errors
|
|
179
|
+
|
|
180
|
+
Every Ingest and every Recall spends credits on your past dev plan. The
|
|
181
|
+
connection check (`PastDevClient().check_connection()`, which reads the
|
|
182
|
+
project's audiences) and Delete data point are free.
|
|
183
|
+
|
|
184
|
+
An error from the API raises `PastDevError` with the API's `code` (for
|
|
185
|
+
example `out-of-credits`), the HTTP `status`, the message and the
|
|
186
|
+
`request_id`. Give the request id to past dev support when you ask about a
|
|
187
|
+
call. The tools return the error to the agent as text, and tell it not to
|
|
188
|
+
retry when a retry cannot help.
|
|
189
|
+
|
|
190
|
+
The client sends a request again only when the API answers 429 for the rate
|
|
191
|
+
limit, after the wait the API asks for (two times at most). It never retries
|
|
192
|
+
`402 out-of-credits`, a monthly cap, or a `400`.
|
|
193
|
+
|
|
194
|
+
## The HTTP client
|
|
195
|
+
|
|
196
|
+
`PastDevClient` is a small client for the calls this package uses. You can
|
|
197
|
+
use it on its own:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from crewai_pastdotdev import PastDevClient
|
|
201
|
+
|
|
202
|
+
with PastDevClient() as past:
|
|
203
|
+
past.check_connection()
|
|
204
|
+
past.ingest("The customer moved to Lyon.", identity="user-42", id="crm-note-17")
|
|
205
|
+
page = past.recall("Where does the customer live?", identity="user-42", limit=5)
|
|
206
|
+
past.delete_data_point("crm-note-17")
|
|
207
|
+
audiences = past.list_audiences()
|
|
208
|
+
identities = list(past.list_identities())
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
python -m venv .venv && . .venv/bin/activate
|
|
215
|
+
pip install -e ".[test]"
|
|
216
|
+
pytest
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The tests use `httpx.MockTransport` and a scripted LLM, so they need no key
|
|
220
|
+
and no network. One live test runs a real Ingest, Recall and Delete data
|
|
221
|
+
point when `PAST_API_KEY` is set. It spends a few credits.
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# crewai-pastdotdev
|
|
2
|
+
|
|
3
|
+
past dev is a memory API for AI agents. This package connects it to
|
|
4
|
+
[CrewAI](https://www.crewai.com): your crews keep what they learn in past dev
|
|
5
|
+
and recall it in later runs, with the date and the source of every memory.
|
|
6
|
+
|
|
7
|
+
It gives you two ways to use past dev:
|
|
8
|
+
|
|
9
|
+
- **`PastDevMemory`**: crew memory. Pass it as `memory=` to a `Crew` or an
|
|
10
|
+
`Agent`. CrewAI recalls before each task and saves each task result. A save
|
|
11
|
+
is a past dev **Ingest** and a search is a past dev **Recall**.
|
|
12
|
+
- **`PastDevRecallTool`** and **`PastDevIngestTool`**: tools that an agent
|
|
13
|
+
calls when it decides to. The model gives only the query or the content.
|
|
14
|
+
|
|
15
|
+
[Website](https://past.dev) ·
|
|
16
|
+
[Quickstart](https://past.dev/docs/memory-api/quickstart) ·
|
|
17
|
+
[API reference](https://past.dev/docs/memory-api/api-reference)
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install crewai-pastdotdev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package needs Python 3.10 to 3.13 and CrewAI 1.14.6 or later.
|
|
26
|
+
|
|
27
|
+
## Set the API key
|
|
28
|
+
|
|
29
|
+
Make a project API key in the past dev console. It starts with `past_sk_`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export PAST_API_KEY="past_sk_..."
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If you run past dev in your own cloud, also set the API host. The default is
|
|
36
|
+
`https://api.past.dev`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export PAST_API_HOST="https://past.example.com"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You can also give `api_key=` and `api_host=` to each class.
|
|
43
|
+
|
|
44
|
+
## The identity
|
|
45
|
+
|
|
46
|
+
Every memory in past dev belongs to an **identity**: the id of the person or
|
|
47
|
+
account the memory is about, for example the user id in your application.
|
|
48
|
+
Recall requires an identity. Ingest makes the identity the first time it sees
|
|
49
|
+
it.
|
|
50
|
+
|
|
51
|
+
**Your code sets the identity, never the model.** Take it from the user
|
|
52
|
+
record or the trigger of the run, and give it to the constructor. The tools
|
|
53
|
+
have no identity argument, so an agent cannot read or write the memory of
|
|
54
|
+
another person. Make one memory or one tool for each user.
|
|
55
|
+
|
|
56
|
+
## Use past dev as crew memory
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from crewai import Agent, Crew, Task
|
|
60
|
+
from crewai_pastdotdev import PastDevMemory
|
|
61
|
+
|
|
62
|
+
memory = PastDevMemory(identity=current_user.id)
|
|
63
|
+
|
|
64
|
+
writer = Agent(
|
|
65
|
+
role="Account manager",
|
|
66
|
+
goal="Write the weekly report the way the customer wants it",
|
|
67
|
+
backstory="You know the customer well.",
|
|
68
|
+
)
|
|
69
|
+
report = Task(
|
|
70
|
+
description="Write this week's report for the customer.",
|
|
71
|
+
expected_output="A report in the format the customer prefers.",
|
|
72
|
+
agent=writer,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
crew = Crew(agents=[writer], tasks=[report], memory=memory)
|
|
76
|
+
crew.kickoff()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
What happens in a run:
|
|
80
|
+
|
|
81
|
+
1. Before each task, CrewAI recalls with the task description. The recalled
|
|
82
|
+
memories go into the agent's prompt.
|
|
83
|
+
2. After each task, CrewAI saves the task and its result. `PastDevMemory`
|
|
84
|
+
ingests them as one data point. past dev extracts the memories on its own
|
|
85
|
+
side, so no extra LLM call runs in your crew.
|
|
86
|
+
3. CrewAI also gives the agents its own "Search memory" and "Save to memory"
|
|
87
|
+
tools. They go through past dev too.
|
|
88
|
+
|
|
89
|
+
You can also call the memory directly:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
record = memory.remember("The customer wants the report on Mondays.")
|
|
93
|
+
for match in memory.recall("When does the customer want the report?"):
|
|
94
|
+
print(match.record.content, match.record.metadata["occurred_at"])
|
|
95
|
+
memory.forget(record_ids=[record.id]) # Delete data point
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Settings
|
|
99
|
+
|
|
100
|
+
| Argument | Default | What it does |
|
|
101
|
+
| :- | :- | :- |
|
|
102
|
+
| `identity` | required | The identity for every Ingest and Recall. |
|
|
103
|
+
| `api_key` | `PAST_API_KEY` | The project API key. |
|
|
104
|
+
| `api_host` | `PAST_API_HOST`, else `https://api.past.dev` | The API host. |
|
|
105
|
+
| `audience` | none | An audience slug for ingested memories. Without it, a memory is visible to the whole project. |
|
|
106
|
+
| `label` | none | A short title stored with each data point. |
|
|
107
|
+
| `recall_level` | `low` | The evidence budget of a recall: `low`, `medium`, `high` or `extra-high`. |
|
|
108
|
+
| `recall_max_tokens` | none | An exact evidence budget in tokens, instead of `recall_level`. |
|
|
109
|
+
| `read_only` | `False` | Recall only. Nothing is ingested. |
|
|
110
|
+
|
|
111
|
+
### How CrewAI concepts map to past dev
|
|
112
|
+
|
|
113
|
+
| CrewAI | past dev |
|
|
114
|
+
| :- | :- |
|
|
115
|
+
| `remember()`, `remember_many()` | Ingest (`POST /api/v1/ingest`). The source id is stable for the same identity and content, so a repeated save is free. |
|
|
116
|
+
| `recall()` | Recall (`POST /api/v1/recall`). `limit` is the number of memories, at most 200. |
|
|
117
|
+
| `forget(record_ids=[...])` | Delete data point (`DELETE /api/v1/data-points/{sourceId}`). |
|
|
118
|
+
| `MemoryMatch.score` | `1 / rank`. past dev ranks the results; it does not return a score. |
|
|
119
|
+
| Scopes, categories, agent role | Kept in the data point's metadata (`crewai_scope`, `crewai_categories`, `crewai_agent_role`). Recall does not filter on them. |
|
|
120
|
+
| `tree()`, `info()`, `list_records()`, `update()`, `reset()` | Not available: past dev keeps memory on its servers. Use the past dev console to browse. To change a fact, remember the new one: past dev marks the earlier statement as superseded. |
|
|
121
|
+
|
|
122
|
+
## Use past dev as agent tools
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from crewai import Agent
|
|
126
|
+
from crewai_pastdotdev import PastDevIngestTool, PastDevRecallTool
|
|
127
|
+
|
|
128
|
+
agent = Agent(
|
|
129
|
+
role="Support agent",
|
|
130
|
+
goal="Help the customer with what you know about them",
|
|
131
|
+
backstory="You remember every earlier conversation.",
|
|
132
|
+
tools=[
|
|
133
|
+
PastDevRecallTool(identity=current_user.id),
|
|
134
|
+
PastDevIngestTool(identity=current_user.id),
|
|
135
|
+
],
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Tool | The model gives | Other settings (in the constructor) |
|
|
140
|
+
| :- | :- | :- |
|
|
141
|
+
| `PastDevRecallTool` ("past dev Recall") | `query` | `identity`, `limit` (10), `level`, `api_key`, `api_host` |
|
|
142
|
+
| `PastDevIngestTool` ("past dev Ingest") | `content` | `identity`, `audience`, `label`, `metadata`, `api_key`, `api_host` |
|
|
143
|
+
|
|
144
|
+
The recall tool returns the memories as text: rank, date, content, and the
|
|
145
|
+
quotations from their sources. If a memory was superseded by a later
|
|
146
|
+
statement, the text says so.
|
|
147
|
+
|
|
148
|
+
## Credits and errors
|
|
149
|
+
|
|
150
|
+
Every Ingest and every Recall spends credits on your past dev plan. The
|
|
151
|
+
connection check (`PastDevClient().check_connection()`, which reads the
|
|
152
|
+
project's audiences) and Delete data point are free.
|
|
153
|
+
|
|
154
|
+
An error from the API raises `PastDevError` with the API's `code` (for
|
|
155
|
+
example `out-of-credits`), the HTTP `status`, the message and the
|
|
156
|
+
`request_id`. Give the request id to past dev support when you ask about a
|
|
157
|
+
call. The tools return the error to the agent as text, and tell it not to
|
|
158
|
+
retry when a retry cannot help.
|
|
159
|
+
|
|
160
|
+
The client sends a request again only when the API answers 429 for the rate
|
|
161
|
+
limit, after the wait the API asks for (two times at most). It never retries
|
|
162
|
+
`402 out-of-credits`, a monthly cap, or a `400`.
|
|
163
|
+
|
|
164
|
+
## The HTTP client
|
|
165
|
+
|
|
166
|
+
`PastDevClient` is a small client for the calls this package uses. You can
|
|
167
|
+
use it on its own:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from crewai_pastdotdev import PastDevClient
|
|
171
|
+
|
|
172
|
+
with PastDevClient() as past:
|
|
173
|
+
past.check_connection()
|
|
174
|
+
past.ingest("The customer moved to Lyon.", identity="user-42", id="crm-note-17")
|
|
175
|
+
page = past.recall("Where does the customer live?", identity="user-42", limit=5)
|
|
176
|
+
past.delete_data_point("crm-note-17")
|
|
177
|
+
audiences = past.list_audiences()
|
|
178
|
+
identities = list(past.list_identities())
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
python -m venv .venv && . .venv/bin/activate
|
|
185
|
+
pip install -e ".[test]"
|
|
186
|
+
pytest
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The tests use `httpx.MockTransport` and a scripted LLM, so they need no key
|
|
190
|
+
and no network. One live test runs a real Ingest, Recall and Delete data
|
|
191
|
+
point when `PAST_API_KEY` is set. It spends a few credits.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "crewai-pastdotdev"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "past dev for CrewAI: Ingest and Recall as crew memory and as agent tools."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "past.dev", email = "louis@past.dev" }]
|
|
13
|
+
maintainers = [{ name = "past.dev", email = "louis@past.dev" }]
|
|
14
|
+
requires-python = ">=3.10,<3.14"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"crewai>=1.14.6,<2",
|
|
17
|
+
"httpx>=0.27,<1",
|
|
18
|
+
]
|
|
19
|
+
keywords = ["crewai", "memory", "agents", "llm", "past dev", "recall"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
test = ["pytest>=8"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://past.dev"
|
|
38
|
+
Documentation = "https://past.dev/docs/memory-api/quickstart"
|
|
39
|
+
"API reference" = "https://past.dev/docs/memory-api/api-reference"
|
|
40
|
+
Source = "https://github.com/pastdotdev/crewai-pastdotdev"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.version]
|
|
43
|
+
path = "src/crewai_pastdotdev/_version.py"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/crewai_pastdotdev"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = ["src", "tests", "README.md", "LICENSE", "pyproject.toml"]
|
|
50
|
+
exclude = ["DOCS_PR.md", "PUBLISHING.md"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
markers = ["live: calls the real past dev API (skipped unless PAST_API_KEY is set)"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""past dev for CrewAI: Ingest and Recall as crew memory and as agent tools."""
|
|
2
|
+
|
|
3
|
+
from crewai_pastdotdev._version import __version__
|
|
4
|
+
from crewai_pastdotdev.client import DEFAULT_API_HOST, PastDevClient, PastDevError
|
|
5
|
+
from crewai_pastdotdev.memory import PastDevMemory
|
|
6
|
+
from crewai_pastdotdev.tools import (
|
|
7
|
+
PastDevIngestInput,
|
|
8
|
+
PastDevIngestTool,
|
|
9
|
+
PastDevRecallInput,
|
|
10
|
+
PastDevRecallTool,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"DEFAULT_API_HOST",
|
|
15
|
+
"PastDevClient",
|
|
16
|
+
"PastDevError",
|
|
17
|
+
"PastDevIngestInput",
|
|
18
|
+
"PastDevIngestTool",
|
|
19
|
+
"PastDevMemory",
|
|
20
|
+
"PastDevRecallInput",
|
|
21
|
+
"PastDevRecallTool",
|
|
22
|
+
"__version__",
|
|
23
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Helpers shared by the memory and the tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import re
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import SecretStr
|
|
11
|
+
|
|
12
|
+
from crewai_pastdotdev.client import PastDevClient
|
|
13
|
+
|
|
14
|
+
MAX_IDENTITY_LENGTH = 200
|
|
15
|
+
RECALL_LIMIT_MAX = 200
|
|
16
|
+
|
|
17
|
+
_FRACTION = re.compile(r"(\.\d{6})\d+")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_identity(identity: str) -> str:
|
|
21
|
+
"""Return the identity, or raise ``ValueError`` if past dev would refuse it."""
|
|
22
|
+
if not identity or not identity.strip():
|
|
23
|
+
raise ValueError("past dev needs an identity: the id of the person the memory is about.")
|
|
24
|
+
if len(identity) > MAX_IDENTITY_LENGTH:
|
|
25
|
+
raise ValueError(f"A past dev identity is at most {MAX_IDENTITY_LENGTH} characters.")
|
|
26
|
+
return identity
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def make_client(
|
|
30
|
+
client: PastDevClient | None, api_key: SecretStr | None, api_host: str | None
|
|
31
|
+
) -> PastDevClient:
|
|
32
|
+
if client is not None:
|
|
33
|
+
return client
|
|
34
|
+
return PastDevClient(
|
|
35
|
+
api_key=api_key.get_secret_value() if api_key is not None else None,
|
|
36
|
+
api_host=api_host,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def source_id_for(identity: str, content: str) -> str:
|
|
41
|
+
"""A stable source id for one piece of content from one identity.
|
|
42
|
+
|
|
43
|
+
The same content from the same identity gets the same id, so a repeated
|
|
44
|
+
ingest is recognized as unchanged and costs nothing. Two identities that
|
|
45
|
+
send the same words keep two separate data points.
|
|
46
|
+
"""
|
|
47
|
+
digest = hashlib.sha256(f"{identity}\x00{content}".encode()).hexdigest()
|
|
48
|
+
return f"crewai-{digest}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def clamp_limit(limit: int) -> int:
|
|
52
|
+
return max(1, min(int(limit), RECALL_LIMIT_MAX))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def parse_datetime(value: str | None) -> datetime | None:
|
|
56
|
+
"""Parse an API date-time (ISO 8601, ``Z`` suffix, up to 7 decimals)."""
|
|
57
|
+
if not value:
|
|
58
|
+
return None
|
|
59
|
+
text = _FRACTION.sub(r"\1", value.replace("Z", "+00:00"))
|
|
60
|
+
try:
|
|
61
|
+
parsed = datetime.fromisoformat(text)
|
|
62
|
+
except ValueError:
|
|
63
|
+
return None
|
|
64
|
+
return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_recall(response: dict[str, Any]) -> str:
|
|
68
|
+
"""Render a recall response as text for an agent to read."""
|
|
69
|
+
results = response.get("results") or []
|
|
70
|
+
if not results:
|
|
71
|
+
return "past dev found no relevant memories."
|
|
72
|
+
lines = [f"past dev recalled {len(results)} memories, most relevant first:"]
|
|
73
|
+
for result in results:
|
|
74
|
+
date = (result.get("occurredAt") or "")[:10]
|
|
75
|
+
artifact = result.get("artifact") or {}
|
|
76
|
+
superseded = artifact.get("supersededAt")
|
|
77
|
+
head = f"{result.get('rank', '?')}. [{date}] {result.get('content', '').strip()}"
|
|
78
|
+
if superseded:
|
|
79
|
+
head += f" (superseded on {superseded[:10]})"
|
|
80
|
+
lines.append(head)
|
|
81
|
+
for source in result.get("sources") or []:
|
|
82
|
+
name = source.get("label") or source.get("sourceId")
|
|
83
|
+
for excerpt in source.get("excerpts") or []:
|
|
84
|
+
lines.append(f' - "{excerpt}" ({name}, {(source.get("occurredAt") or "")[:10]})')
|
|
85
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|