Metadata-Version: 2.4
Name: goodmem-autogen
Version: 0.3.0
Summary: GoodMem memory and tools for the AutoGen agent framework.
Keywords: autogen,goodmem,memory,rag,agents,llm
Author: PAIR Systems
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Dist: autogen-core>=0.7.5
Requires-Dist: goodmem>=0.1.34
Requires-Dist: pydantic>=2.0,<3
Requires-Dist: typing-extensions>=4.7
Requires-Dist: pytest>=7 ; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23 ; extra == "dev"
Requires-Dist: pytest-timeout ; extra == "dev"
Requires-Dist: httpx ; extra == "dev"
Requires-Dist: ruff>=0.6 ; extra == "dev"
Requires-Dist: mypy>=1.11 ; extra == "dev"
Requires-Dist: build ; extra == "dev"
Requires-Dist: twine ; extra == "dev"
Project-URL: Homepage, https://github.com/PAIR-Systems-Inc/goodmem-autogen
Project-URL: Issues, https://github.com/PAIR-Systems-Inc/goodmem-autogen/issues
Project-URL: Repository, https://github.com/PAIR-Systems-Inc/goodmem-autogen
Provides-Extra: dev

# goodmem-autogen

[GoodMem](https://goodmem.ai) memory and tools for the
[AutoGen](https://github.com/microsoft/autogen) agent framework.

Two ways in:

1. **`GoodMemContextProvider`** — an `autogen_core.memory.Memory` backed by a
   GoodMem space, so relevant passages are injected into the model context on
   every turn.
2. **`create_goodmem_search_tool` / `create_goodmem_admin_tools`** — function
   tools an agent can call directly.

Built on the official `goodmem` SDK's async client, so nothing blocks the
event loop.

## Install

```bash
pip install goodmem-autogen
```

Requires Python 3.10+, `autogen-core` 0.7.5+ and the `goodmem` SDK 0.1.34+
(installed with it). Version 0.2 is a break from
0.1 — see [CHANGELOG](CHANGELOG.md) for the mapping.

## As an AutoGen `Memory`

```python
from autogen_core.memory import MemoryContent, MemoryMimeType
from goodmem_autogen import GoodMemContextProvider, GoodMemMemoryConfig

provider = GoodMemContextProvider(
    config=GoodMemMemoryConfig(
        base_url="https://goodmem.example.com",
        api_key="gm_...",              # stored as SecretStr, never serialized
        space_name="handbook",         # or space_id="..." to skip the lookup
        embedder_id="<embedder-uuid>",
    )
)

await provider.add(MemoryContent(
    content="Refunds above $500 need a manager's approval.",
    mime_type=MemoryMimeType.TEXT,
    metadata={"title": "handbook", "category": "policy"},
))

results = await provider.query("who approves a large refund?")
await provider.close()
```

`add()` waits for the memory to finish indexing by default, so a query right
after it finds the result. Searching is never used as a way to wait.

Attach it to an agent and `update_context` injects retrieved passages as a
system message each turn — the same pattern AutoGen's own `ListMemory` uses.

### What a result carries

`MemoryContent` has no score field, so provenance lives in `metadata`:

```python
{
  "title": "handbook", "category": "policy",   # the memory's own metadata
  "chunk_id": "...", "memory_id": "...", "space_id": "...", "source": "...",
  "score": -0.53, "score_kind": "vector",
  "partial": False, "statuses": [],
}
```

- `partial` is `True` when part of the search did not complete — a reranker
  was unavailable, one space was unreachable. The passages are usable but may
  be incomplete, and `statuses` says why. A search that produced nothing
  usable returns an empty result and emits a warning carrying the statuses,
  so it is distinguishable from "no matches" without being raised. The
  search tool returns `partial: true` with `statuses` in its JSON for the
  same case.
- `score` is passed through exactly as GoodMem reports it. Vector scores are
  opaque similarities that may be negative; reranker scores are on a scale
  that depends on the reranker model (Voyage `rerank-2.5` ~`0.27..0.93`, Jina
  `jina-reranker-v3` ~`-0.14..0.43` on the same documents). `score_kind` says
  which you have — which is why `relevance_threshold` requires a
  `reranker_id`, and why it must be calibrated for the reranker in use rather
  than assumed to be 0–1. The threshold is applied by the server; if it
  removes every result the provider warns, since an empty result would
  otherwise read as "no matches".
- `score_kind` is read from the response, not the configuration. If the
  reranker fails (`RERANKING_FAILED`, or `NOT_FOUND` for the reranker) the
  server still returns the vector-search hits; they are kept, labelled
  `vector`, and marked `partial` with those statuses.

## As tools

The tools take a `goodmem.AsyncGoodmem` client, which you create and close.

```python
from autogen_core import CancellationToken
from goodmem_autogen import create_goodmem_admin_tools, create_goodmem_search_tool
from goodmem import AsyncGoodmem

client = AsyncGoodmem(
    base_url="https://goodmem.example.com",
    api_key="gm_...",
    # verify="/path/to/ca.pem",   # a server with a self-signed certificate
)

search = create_goodmem_search_tool(
    client, space_ids=["<space-uuid>"], limit=5,
    reranker_id="<reranker-uuid>",            # optional
    metadata_filter={"category": "policy"},   # optional, escaped for you
)
admin_tools = create_goodmem_admin_tools(client)  # only for agents that need them

# What an agent's tool call does:
print(await search.run_json({"query": "who approves a large refund?"}, CancellationToken()))

await client.close()
```

The model supplies only the query; spaces, reranking and filters are yours, so
an agent cannot redirect a search or widen it mid-run. The search tool returns
JSON: `results` (each with `chunk_text`, `score`, `score_kind`, `metadata` and
IDs), `total_results`, `partial`, and `statuses` when the server reported any.

`create_goodmem_admin_tools(client)` adds space and memory management
(`goodmem_list_embedders`, `goodmem_list_rerankers`, `goodmem_list_spaces`,
`goodmem_get_space`, `goodmem_create_space`, `goodmem_update_space`,
`goodmem_delete_space`, `goodmem_create_memory`, `goodmem_list_memories`,
`goodmem_get_memory`, `goodmem_delete_memory`, and `goodmem_upload_file`
when `upload_dir` is given). A listing returns at most `max_items` (default
100) and sets `truncated` when that cap may have cut it short. These
carry the authority of the configured API key — give them only to agents that
need them. File upload is only created when you pass `upload_dir`, and paths
resolving outside that directory are refused before the file is opened.

Every ID — a tool argument, `space_ids`, `reranker_id`, or an ID field of the
config — must be a UUID (it is lowercased); anything else raises `ValueError`
before a request is made. The SDK puts IDs into request paths unescaped, so
`"../spaces/<id>"` given as a memory ID would otherwise address a whole space.

## Cancellation

`add`, `add_file` and `query` honour an `autogen_core.CancellationToken`: an
already-cancelled token prevents the request, and cancelling mid-flight aborts
it.

## Clearing a space

`clear()` deletes every memory in the space and requires
`allow_clear=True` on the config, so a reflexive `clear()` cannot empty a
space by accident.

## Filters

A filter is a GoodMem expression applied to every configured space, e.g.
`CAST(val('$.category') AS TEXT) = 'policy'`. Pass `metadata_filter={...}` and
it is built and escaped for you. Writing one by hand: inside a quoted value
escape `'` as `\'` and `\` as `\\` — SQL-style `''` doubling is rejected by
the server.

## Development

```bash
uv venv && uv pip install -e ".[dev]"
uv run ruff check goodmem_autogen tests
uv run mypy goodmem_autogen
uv run pytest -m "not integration"   # offline: the real SDK over a mock transport or a local server
GOODMEM_BASE_URL=... GOODMEM_API_KEY=... GOODMEM_EMBEDDER_ID=... \
  GOODMEM_VERIFY_SSL=false uv run pytest -m integration
```

CI runs the first four on Python 3.10–3.13, then `uv build` and an import
of the wheel in a clean environment. It does not run the live suite, which
needs a server: `GOODMEM_EMBEDDER_ID` is the embedder its spaces are
created with, and `GOODMEM_VERIFY_SSL=false` is for a local server with a
self-signed certificate. The offline suite also executes every Python
snippet in this README against a local server.

Offline tests use event shapes captured from a live server. There is no
default API key — live tests skip unless the environment provides one.

MIT.

