goodmem-agent-framework 0.3.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Microsoft Corporation.
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,252 @@
1
+ Metadata-Version: 2.4
2
+ Name: goodmem-agent-framework
3
+ Version: 0.3.0
4
+ Summary: GoodMem integration for Microsoft Agent Framework.
5
+ Author-email: GoodMem <support@goodmem.ai>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ License-File: LICENSE
18
+ Requires-Dist: agent-framework-core>=1.0.0rc4
19
+ Requires-Dist: httpx>=0.24.0,<1
20
+ Requires-Dist: pydantic>=2.0,<3
21
+ Requires-Dist: pytest>=7.0 ; extra == "dev"
22
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == "dev"
23
+ Requires-Dist: pytest-timeout ; extra == "dev"
24
+ Requires-Dist: build ; extra == "dev"
25
+ Requires-Dist: twine ; extra == "dev"
26
+ Project-URL: homepage, https://github.com/PAIR-Systems-Inc/goodmem_agent-framework
27
+ Project-URL: issues, https://github.com/PAIR-Systems-Inc/goodmem_agent-framework/issues
28
+ Project-URL: source, https://github.com/PAIR-Systems-Inc/goodmem_agent-framework
29
+ Provides-Extra: dev
30
+
31
+ # goodmem-agent-framework
32
+
33
+ [GoodMem](https://goodmem.ai) integration for the
34
+ [Microsoft Agent Framework](https://github.com/microsoft/agent-framework).
35
+
36
+ This package gives Agent Framework agents persistent, semantic long-term memory
37
+ backed by a GoodMem server. It exposes:
38
+
39
+ - **`GoodMemClient`** — an async REST client for the GoodMem v1 API.
40
+ - **`GoodMemContextProvider`** — a `BaseContextProvider` that automatically
41
+ retrieves relevant memories before each agent run and stores conversations
42
+ afterwards.
43
+ - **`create_goodmem_tools`** — a factory that returns ready-to-use function
44
+ tools so the model itself can manage spaces and memories.
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install goodmem-agent-framework
50
+ ```
51
+
52
+ For local development:
53
+
54
+ ```bash
55
+ pip install -e .
56
+ ```
57
+
58
+ ## Quickstart
59
+
60
+ ```python
61
+ import asyncio
62
+ from goodmem_agent_framework import GoodMemClient, create_goodmem_tools
63
+
64
+ async def main():
65
+ client = GoodMemClient(
66
+ base_url="https://localhost:8080",
67
+ api_key="gm_xxxxxxxxxxxxxxxxxxxxxxxx",
68
+ verify_ssl=False, # self-signed local server
69
+ )
70
+
71
+ embedders = await client.list_embedders()
72
+ embedder_id = embedders[0]["embedderId"]
73
+
74
+ space = await client.create_space(name="quickstart", embedder_id=embedder_id)
75
+ space_id = space["spaceId"]
76
+
77
+ await client.create_memory(
78
+ space_id=space_id,
79
+ text_content="The capital of France is Paris.",
80
+ )
81
+
82
+ results = await client.retrieve_memories(
83
+ query="What is the capital of France?",
84
+ space_ids=[space_id],
85
+ max_results=3,
86
+ wait_for_indexing=True,
87
+ )
88
+ print(results)
89
+
90
+ await client.close()
91
+
92
+ asyncio.run(main())
93
+ ```
94
+
95
+ ## Available tools
96
+
97
+ `create_goodmem_tools(client)` returns the following 11 function tools:
98
+
99
+ | Tool | Description |
100
+ |------|-------------|
101
+ | `goodmem_list_embedders` | List embedder models available on the server |
102
+ | `goodmem_list_spaces` | List all spaces accessible to the API key |
103
+ | `goodmem_get_space` | Fetch a space by ID |
104
+ | `goodmem_create_space` | Create a space, or reuse a same-name space that uses the same embedder |
105
+ | `goodmem_update_space` | Update a space's name/labels/visibility |
106
+ | `goodmem_delete_space` | Delete a space |
107
+ | `goodmem_create_memory` | Store text or a file as a memory |
108
+ | `goodmem_list_memories` | List memories in a space |
109
+ | `goodmem_retrieve_memories` | Semantic retrieval, with optional reranker/LLM |
110
+ | `goodmem_get_memory` | Fetch a memory by ID (with original content) |
111
+ | `goodmem_delete_memory` | Delete a memory |
112
+
113
+ ### Retrieval options
114
+
115
+ `goodmem_retrieve_memories` (and `GoodMemClient.retrieve_memories`) accept the
116
+ GoodMem post-processor parameters:
117
+
118
+ | Parameter | Type | Description |
119
+ |-----------|------|-------------|
120
+ | `reranker_id` | UUID | Reranker model to improve result ordering |
121
+ | `llm_id` | UUID | LLM used to generate a contextual abstract reply |
122
+ | `relevance_threshold` | 0–1 | Minimum score for including a result |
123
+ | `llm_temperature` | 0–2 | Creativity for the LLM post-processor |
124
+ | `max_results` | int | Cap on returned chunks |
125
+ | `chronological_resort` | bool | Reorder results by memory creation time |
126
+
127
+ ### Retrieval results and server statuses
128
+
129
+ `retrieve_memories` returns a dict (the tool returns the same dict as JSON):
130
+
131
+ | Key | Description |
132
+ |-----|-------------|
133
+ | `success` | `true` unless the request itself failed (see [Errors](#errors)) |
134
+ | `results` | Matching chunks: `chunkId`, `chunkText`, `memoryId`, `relevanceScore`, `memoryIndex` |
135
+ | `memories` | Memory definitions (when `include_memory_definition` is true) |
136
+ | `totalResults` | Number of entries in `results` |
137
+ | `resultSetId` | The server's result set ID |
138
+ | `abstractReply` | The LLM's reply, when an `llm_id` was given and it succeeded |
139
+ | `partial` | `true` when the server reported a problem during this retrieval |
140
+ | `statuses` | The problems the server reported, in stream order; empty when `partial` is `false` |
141
+ | `message` | A readable summary when `partial` is `true`, or when `wait_for_indexing` gave up after 60 seconds |
142
+
143
+ Each entry in `statuses` is `{"code", "message", "details"}`, exactly as the
144
+ server sent it. These rules decide what counts as a problem:
145
+
146
+ - `FEATURE_DISABLED` and `LLM_CAPABILITY_INFERRED` are informational (an
147
+ optional feature you did not configure). They are left out of `statuses`
148
+ and never set `partial`.
149
+ - A code this package does not recognize is reported with `code: "UNKNOWN"`
150
+ and the server's code in `originalCode`, and sets `partial`. It is never
151
+ dropped and never raises.
152
+ - A problem with hits (for example a nonexistent `reranker_id`): the hits are
153
+ returned, with `partial: true` and the statuses.
154
+ - A problem with no hits: an empty `results`, with `partial: true` and the
155
+ statuses. Nothing is raised.
156
+
157
+ With `wait_for_indexing=True`, polling stops as soon as the server reports a
158
+ problem, so a nonexistent reranker or LLM is reported at once instead of after
159
+ 60 seconds. When the reranker fails (`RERANKING_FAILED`, or `NOT_FOUND` naming
160
+ the reranker) the server still returns the vector search's hits: their
161
+ `relevanceScore` values are vector-search scores, not reranker scores, and
162
+ `message` says so.
163
+
164
+ `GoodMemContextProvider` logs a WARNING with the statuses when a retrieval is
165
+ partial, and still uses any chunks that came back.
166
+
167
+ ### Space reuse
168
+
169
+ `create_space` (and `goodmem_create_space`) looks for a space with exactly the
170
+ requested name, across every page of the space listing:
171
+
172
+ - **No such space:** a new one is created (`reused: false`).
173
+ - **One space, same embedder:** it is reused (`reused: true`). `embedderId`
174
+ and `embedderIds` report the space's real embedder (`embedderId` is `null`
175
+ for a space with several), and `chunkingConfig` its real chunking
176
+ configuration (which may differ from the one requested).
177
+ - **One space, different embedder:** nothing is created. The result has
178
+ `success: false`, an `error` naming the space, its ID and both embedders,
179
+ plus `existingSpaceId`, `existingEmbedderIds` and `requestedEmbedderId`.
180
+ - **Several spaces with that name:** nothing is created. The result has
181
+ `success: false`, an `error` listing each space and its embedders, and
182
+ `existingSpaceIds`.
183
+
184
+ If no `embedder_id` is given, an existing space with that name is reused
185
+ whatever its embedder, and a new space uses the server's first embedder.
186
+
187
+ ### Errors
188
+
189
+ Tools never raise: a failure is returned as `{"success": false, "error": ...}`.
190
+ When the server rejects a request, `GoodMemClient` raises
191
+ `httpx.HTTPStatusError` whose message includes the server's own error text,
192
+ for example `HTTP 409 Conflict for POST /v1/spaces: A space with this name
193
+ already exists`, and the tools pass that text on in `error`.
194
+
195
+ ## Context provider
196
+
197
+ ```python
198
+ from agent_framework import Agent
199
+ from agent_framework.openai import OpenAIChatClient
200
+ from goodmem_agent_framework import GoodMemClient, GoodMemContextProvider
201
+
202
+ client = GoodMemClient(base_url="https://localhost:8080", api_key="gm_...", verify_ssl=False)
203
+
204
+ provider = GoodMemContextProvider(
205
+ client=client,
206
+ space_id=space_id,
207
+ max_results=5,
208
+ store_conversations=True,
209
+ )
210
+
211
+ agent = Agent(
212
+ client=OpenAIChatClient(model="gpt-4o"),
213
+ name="memory-agent",
214
+ instructions="You are a helpful assistant with persistent memory.",
215
+ context_providers=[provider],
216
+ )
217
+ ```
218
+
219
+ ## Running the tests
220
+
221
+ The offline tests replay retrieval streams captured from a live GoodMem server
222
+ (`tests/fixtures/`) against a fake server, and need no credentials:
223
+
224
+ ```bash
225
+ pip install -e ".[dev]"
226
+ pytest -v tests/test_retrieval_statuses.py tests/test_space_reuse.py
227
+ ```
228
+
229
+ The live integration tests in `tests/test_goodmem_integration.py` run against
230
+ a real server and are skipped when `GOODMEM_API_KEY` is not set. They create
231
+ and delete their own spaces. `GOODMEM_EMBEDDER_ID`, `GOODMEM_RERANKER_ID`,
232
+ `GOODMEM_LLM_ID` and `GOODMEM_PDF_PATH` pick the models and file they use.
233
+
234
+ ```bash
235
+ export GOODMEM_API_KEY=gm_xxxxxxxxxxxxxxxxxxxxxxxx
236
+ export GOODMEM_BASE_URL=https://localhost:8080
237
+ pip install -e ".[dev]"
238
+ pytest -m integration -v tests/test_goodmem_integration.py
239
+ ```
240
+
241
+ ### Continuous integration
242
+
243
+ `.github/workflows/ci.yml` runs on every pull request and on pushes to `main`,
244
+ on Python 3.10, 3.11, 3.12 and 3.13. It installs the package with
245
+ `pip install -e ".[dev]"`, compiles every module and imports the public API
246
+ (no linter is configured), fails if a GoodMem API key is committed, and runs
247
+ `python -m pytest -v` with no API key set, so the live tests are skipped.
248
+
249
+ ## Changes
250
+
251
+ See [CHANGELOG.md](CHANGELOG.md).
252
+
@@ -0,0 +1,221 @@
1
+ # goodmem-agent-framework
2
+
3
+ [GoodMem](https://goodmem.ai) integration for the
4
+ [Microsoft Agent Framework](https://github.com/microsoft/agent-framework).
5
+
6
+ This package gives Agent Framework agents persistent, semantic long-term memory
7
+ backed by a GoodMem server. It exposes:
8
+
9
+ - **`GoodMemClient`** — an async REST client for the GoodMem v1 API.
10
+ - **`GoodMemContextProvider`** — a `BaseContextProvider` that automatically
11
+ retrieves relevant memories before each agent run and stores conversations
12
+ afterwards.
13
+ - **`create_goodmem_tools`** — a factory that returns ready-to-use function
14
+ tools so the model itself can manage spaces and memories.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install goodmem-agent-framework
20
+ ```
21
+
22
+ For local development:
23
+
24
+ ```bash
25
+ pip install -e .
26
+ ```
27
+
28
+ ## Quickstart
29
+
30
+ ```python
31
+ import asyncio
32
+ from goodmem_agent_framework import GoodMemClient, create_goodmem_tools
33
+
34
+ async def main():
35
+ client = GoodMemClient(
36
+ base_url="https://localhost:8080",
37
+ api_key="gm_xxxxxxxxxxxxxxxxxxxxxxxx",
38
+ verify_ssl=False, # self-signed local server
39
+ )
40
+
41
+ embedders = await client.list_embedders()
42
+ embedder_id = embedders[0]["embedderId"]
43
+
44
+ space = await client.create_space(name="quickstart", embedder_id=embedder_id)
45
+ space_id = space["spaceId"]
46
+
47
+ await client.create_memory(
48
+ space_id=space_id,
49
+ text_content="The capital of France is Paris.",
50
+ )
51
+
52
+ results = await client.retrieve_memories(
53
+ query="What is the capital of France?",
54
+ space_ids=[space_id],
55
+ max_results=3,
56
+ wait_for_indexing=True,
57
+ )
58
+ print(results)
59
+
60
+ await client.close()
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ ## Available tools
66
+
67
+ `create_goodmem_tools(client)` returns the following 11 function tools:
68
+
69
+ | Tool | Description |
70
+ |------|-------------|
71
+ | `goodmem_list_embedders` | List embedder models available on the server |
72
+ | `goodmem_list_spaces` | List all spaces accessible to the API key |
73
+ | `goodmem_get_space` | Fetch a space by ID |
74
+ | `goodmem_create_space` | Create a space, or reuse a same-name space that uses the same embedder |
75
+ | `goodmem_update_space` | Update a space's name/labels/visibility |
76
+ | `goodmem_delete_space` | Delete a space |
77
+ | `goodmem_create_memory` | Store text or a file as a memory |
78
+ | `goodmem_list_memories` | List memories in a space |
79
+ | `goodmem_retrieve_memories` | Semantic retrieval, with optional reranker/LLM |
80
+ | `goodmem_get_memory` | Fetch a memory by ID (with original content) |
81
+ | `goodmem_delete_memory` | Delete a memory |
82
+
83
+ ### Retrieval options
84
+
85
+ `goodmem_retrieve_memories` (and `GoodMemClient.retrieve_memories`) accept the
86
+ GoodMem post-processor parameters:
87
+
88
+ | Parameter | Type | Description |
89
+ |-----------|------|-------------|
90
+ | `reranker_id` | UUID | Reranker model to improve result ordering |
91
+ | `llm_id` | UUID | LLM used to generate a contextual abstract reply |
92
+ | `relevance_threshold` | 0–1 | Minimum score for including a result |
93
+ | `llm_temperature` | 0–2 | Creativity for the LLM post-processor |
94
+ | `max_results` | int | Cap on returned chunks |
95
+ | `chronological_resort` | bool | Reorder results by memory creation time |
96
+
97
+ ### Retrieval results and server statuses
98
+
99
+ `retrieve_memories` returns a dict (the tool returns the same dict as JSON):
100
+
101
+ | Key | Description |
102
+ |-----|-------------|
103
+ | `success` | `true` unless the request itself failed (see [Errors](#errors)) |
104
+ | `results` | Matching chunks: `chunkId`, `chunkText`, `memoryId`, `relevanceScore`, `memoryIndex` |
105
+ | `memories` | Memory definitions (when `include_memory_definition` is true) |
106
+ | `totalResults` | Number of entries in `results` |
107
+ | `resultSetId` | The server's result set ID |
108
+ | `abstractReply` | The LLM's reply, when an `llm_id` was given and it succeeded |
109
+ | `partial` | `true` when the server reported a problem during this retrieval |
110
+ | `statuses` | The problems the server reported, in stream order; empty when `partial` is `false` |
111
+ | `message` | A readable summary when `partial` is `true`, or when `wait_for_indexing` gave up after 60 seconds |
112
+
113
+ Each entry in `statuses` is `{"code", "message", "details"}`, exactly as the
114
+ server sent it. These rules decide what counts as a problem:
115
+
116
+ - `FEATURE_DISABLED` and `LLM_CAPABILITY_INFERRED` are informational (an
117
+ optional feature you did not configure). They are left out of `statuses`
118
+ and never set `partial`.
119
+ - A code this package does not recognize is reported with `code: "UNKNOWN"`
120
+ and the server's code in `originalCode`, and sets `partial`. It is never
121
+ dropped and never raises.
122
+ - A problem with hits (for example a nonexistent `reranker_id`): the hits are
123
+ returned, with `partial: true` and the statuses.
124
+ - A problem with no hits: an empty `results`, with `partial: true` and the
125
+ statuses. Nothing is raised.
126
+
127
+ With `wait_for_indexing=True`, polling stops as soon as the server reports a
128
+ problem, so a nonexistent reranker or LLM is reported at once instead of after
129
+ 60 seconds. When the reranker fails (`RERANKING_FAILED`, or `NOT_FOUND` naming
130
+ the reranker) the server still returns the vector search's hits: their
131
+ `relevanceScore` values are vector-search scores, not reranker scores, and
132
+ `message` says so.
133
+
134
+ `GoodMemContextProvider` logs a WARNING with the statuses when a retrieval is
135
+ partial, and still uses any chunks that came back.
136
+
137
+ ### Space reuse
138
+
139
+ `create_space` (and `goodmem_create_space`) looks for a space with exactly the
140
+ requested name, across every page of the space listing:
141
+
142
+ - **No such space:** a new one is created (`reused: false`).
143
+ - **One space, same embedder:** it is reused (`reused: true`). `embedderId`
144
+ and `embedderIds` report the space's real embedder (`embedderId` is `null`
145
+ for a space with several), and `chunkingConfig` its real chunking
146
+ configuration (which may differ from the one requested).
147
+ - **One space, different embedder:** nothing is created. The result has
148
+ `success: false`, an `error` naming the space, its ID and both embedders,
149
+ plus `existingSpaceId`, `existingEmbedderIds` and `requestedEmbedderId`.
150
+ - **Several spaces with that name:** nothing is created. The result has
151
+ `success: false`, an `error` listing each space and its embedders, and
152
+ `existingSpaceIds`.
153
+
154
+ If no `embedder_id` is given, an existing space with that name is reused
155
+ whatever its embedder, and a new space uses the server's first embedder.
156
+
157
+ ### Errors
158
+
159
+ Tools never raise: a failure is returned as `{"success": false, "error": ...}`.
160
+ When the server rejects a request, `GoodMemClient` raises
161
+ `httpx.HTTPStatusError` whose message includes the server's own error text,
162
+ for example `HTTP 409 Conflict for POST /v1/spaces: A space with this name
163
+ already exists`, and the tools pass that text on in `error`.
164
+
165
+ ## Context provider
166
+
167
+ ```python
168
+ from agent_framework import Agent
169
+ from agent_framework.openai import OpenAIChatClient
170
+ from goodmem_agent_framework import GoodMemClient, GoodMemContextProvider
171
+
172
+ client = GoodMemClient(base_url="https://localhost:8080", api_key="gm_...", verify_ssl=False)
173
+
174
+ provider = GoodMemContextProvider(
175
+ client=client,
176
+ space_id=space_id,
177
+ max_results=5,
178
+ store_conversations=True,
179
+ )
180
+
181
+ agent = Agent(
182
+ client=OpenAIChatClient(model="gpt-4o"),
183
+ name="memory-agent",
184
+ instructions="You are a helpful assistant with persistent memory.",
185
+ context_providers=[provider],
186
+ )
187
+ ```
188
+
189
+ ## Running the tests
190
+
191
+ The offline tests replay retrieval streams captured from a live GoodMem server
192
+ (`tests/fixtures/`) against a fake server, and need no credentials:
193
+
194
+ ```bash
195
+ pip install -e ".[dev]"
196
+ pytest -v tests/test_retrieval_statuses.py tests/test_space_reuse.py
197
+ ```
198
+
199
+ The live integration tests in `tests/test_goodmem_integration.py` run against
200
+ a real server and are skipped when `GOODMEM_API_KEY` is not set. They create
201
+ and delete their own spaces. `GOODMEM_EMBEDDER_ID`, `GOODMEM_RERANKER_ID`,
202
+ `GOODMEM_LLM_ID` and `GOODMEM_PDF_PATH` pick the models and file they use.
203
+
204
+ ```bash
205
+ export GOODMEM_API_KEY=gm_xxxxxxxxxxxxxxxxxxxxxxxx
206
+ export GOODMEM_BASE_URL=https://localhost:8080
207
+ pip install -e ".[dev]"
208
+ pytest -m integration -v tests/test_goodmem_integration.py
209
+ ```
210
+
211
+ ### Continuous integration
212
+
213
+ `.github/workflows/ci.yml` runs on every pull request and on pushes to `main`,
214
+ on Python 3.10, 3.11, 3.12 and 3.13. It installs the package with
215
+ `pip install -e ".[dev]"`, compiles every module and imports the public API
216
+ (no linter is configured), fails if a GoodMem API key is committed, and runs
217
+ `python -m pytest -v` with no API key set, so the live tests are skipped.
218
+
219
+ ## Changes
220
+
221
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -0,0 +1,47 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """GoodMem integration for Microsoft Agent Framework.
4
+
5
+ This package provides two ways to use GoodMem with Agent Framework agents:
6
+
7
+ 1. **Context provider** — :class:`GoodMemContextProvider` automatically
8
+ retrieves relevant memories before each agent run and stores
9
+ conversations after, using the ``BaseContextProvider`` hooks pattern.
10
+
11
+ 2. **Tools** — :func:`create_goodmem_tools` exposes GoodMem operations
12
+ as agent tools, letting the model decide when to read/write memories.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import importlib.metadata
18
+ from typing import TYPE_CHECKING
19
+
20
+ from ._client import GoodMemClient
21
+ from ._context_provider import GoodMemContextProvider
22
+
23
+ if TYPE_CHECKING:
24
+ from ._tools import create_goodmem_tools as create_goodmem_tools
25
+ else:
26
+
27
+ def __getattr__(name: str): # noqa: ANN001, ANN202
28
+ if name == "create_goodmem_tools":
29
+ from ._tools import create_goodmem_tools
30
+
31
+ return create_goodmem_tools
32
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
33
+
34
+
35
+ # Looked up by the distribution name rather than __name__, so it does not
36
+ # depend on the import name matching the distribution name.
37
+ try:
38
+ __version__ = importlib.metadata.version("goodmem-agent-framework")
39
+ except importlib.metadata.PackageNotFoundError:
40
+ __version__ = "0.0.0" # Fallback for development mode
41
+
42
+ __all__ = [
43
+ "GoodMemClient",
44
+ "GoodMemContextProvider",
45
+ "create_goodmem_tools",
46
+ "__version__",
47
+ ]