InfoLang BaseMemoryService for Google ADK agents.
Project description
infolang-adk
InfoLang memory for Google ADK agents:
InfoLangMemoryService, a google.adk.memory.BaseMemoryService implementation
backed by the InfoLang memory API.
Why this exists
ADK's BaseMemoryService ABC declares two required async methods
(search_memory, add_session_to_memory) and two optional ones
(add_events_to_memory, add_memory) that default to raising
NotImplementedError. Most third-party memory integrations for agent
frameworks wrap their SDK as a function-tool the LLM calls, rather than
implementing the framework's actual memory-service interface — see
mem0ai/mem0#3999, where the
maintainers confirm their ADK integration does exactly this. That approach
skips Runner-level memory wiring (auto session ingestion, load_memory
tool auto-registration, etc.) entirely.
infolang-adk implements the real interface: all four methods, including
the two optional ones, backed by real InfoLang calls, not stubs.
Install
pip install infolang-adk
Requires Python 3.11+, google-adk>=1.0.0, and an InfoLang API key.
Quickstart
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from infolang_adk import InfoLangMemoryService
memory_service = InfoLangMemoryService(api_key="il_live_...")
agent = Agent(
name="assistant",
model="gemini-2.0-flash",
instruction="You are a helpful assistant with long-term memory.",
)
runner = Runner(
agent=agent,
app_name="my_app",
session_service=InMemorySessionService(),
memory_service=memory_service,
)
Runner calls add_session_to_memory when a session ends and exposes
search_memory to agents that use ADK's built-in load_memory tool (or call
it directly, e.g. from a before_agent_callback). See examples/ for a
runnable end-to-end agent.
Credentials resolve the same way as the infolang SDK: pass api_key=
explicitly, or set the INFOLANG_API_KEY environment variable and construct
InfoLangMemoryService() with no arguments. Any keyword InfoLangMemoryService
doesn't recognize (base_url=, dev_key=, workspace=, ...) is forwarded
verbatim to infolang.AsyncInfoLang(...); see that class for the full list.
To reuse an existing client instead:
from infolang import AsyncInfoLang
from infolang_adk import InfoLangMemoryService
client = AsyncInfoLang(api_key="il_live_...")
memory_service = InfoLangMemoryService(client=client)
# ... use memory_service ...
await memory_service.aclose() # only closes clients infolang-adk constructed itself
Passing an existing client= means infolang-adk never closes it — close it
yourself (or use async with AsyncInfoLang(...) as client:).
Namespace scoping
InfoLang namespaces are a single flat string per memory bank. ADK scopes
memory two-dimensionally, by (app_name, user_id). infolang-adk collapses
that pair into one namespace per default scheme:
{namespace_prefix}-{sanitized app_name}-{sanitized user_id}
namespace_prefix defaults to "adk". Both segments are sanitized to
[a-zA-Z0-9_.-] (anything else, including / and :, becomes -) because
app_name/user_id end up in InfoLang recall/list-memory query strings.
Two different (app_name, user_id) pairs whose sanitized forms are
identical share a namespace — e.g. ("team/a", "u1") and ("team a", "u1")
both sanitize to adk-team-a-u1. If that collision risk matters for your
app_name/user_id values, supply your own mapping:
memory_service = InfoLangMemoryService(
api_key="il_live_...",
namespace_for=lambda app_name, user_id: f"prod-{app_name}-{user_id}",
)
Every InfoLang API call infolang-adk makes is scoped to the namespace for
the app_name/user_id of the request that triggered it — one user's agent
session never reads or writes another user's memories through this service
(scoping only fails if InfoLang's own API-key/workspace boundary is
misconfigured upstream; namespace scoping happens after that boundary, not
instead of it).
What each method actually does
search_memory(app_name, user_id, query)— one InfoLangrecallcall scoped to the namespace, IL-cosine ranked, topsearch_top_k(default 10) chunks. Not keyword/full-text search. A namespace with nothing written to it yet returns an empty result rather than raising.min_score=filters out low-confidence chunks client-side (InfoLang's SDK treats scores below0.85as a weak match; this class does not enforce that threshold for you — setmin_score=0.85if you want it enforced).add_session_to_memory(session)— ingests every event in the session that has non-empty text content, oneremember_batchcall. Re-ingesting the same session (e.g. because it gets called again after more turns) stores the events again rather than deduplicating or updating in place — InfoLang has no update-in-place primitive forremember. If you call this repeatedly on a growing session, you'll get duplicate memories for already-ingested turns; useadd_events_to_memorywith only the new events instead.add_events_to_memory(app_name, user_id, events, session_id=None, custom_metadata=None)— ingests an explicit delta of events (does not require the full session object).custom_metadatascalar values become InfoLang tags ("key:value"); non-scalar values are dropped, since InfoLang'srememberhas no structured-metadata field, only string tags.add_memory(app_name, user_id, memories, custom_metadata=None)— ingests explicitMemoryEntryitems directly (no event/session framing). Entries with no text content raiseValueError; InfoLang only stores text.
Every InfoLang API error other than "namespace not found" (which
search_memory treats as empty results) propagates to the caller as the
infolang SDK's typed exceptions (AuthenticationError, ValidationError,
RateLimitError, ServerError, ...) — this package does not swallow or
retry them.
Development
pip install -e ".[dev]"
ruff check .
mypy src/infolang_adk
pytest
Unit tests mock the HTTP layer (no live InfoLang calls). An optional live
smoke test (tests/test_live_smoke.py) runs only when INFOLANG_API_KEY is
set, and only ever writes to namespaces prefixed ittest-adk-, which it
deletes afterward.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file infolang_adk-0.1.0.tar.gz.
File metadata
- Download URL: infolang_adk-0.1.0.tar.gz
- Upload date:
- Size: 17.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a264806710cce08db5e18e98cc54d55618f99f4704c576ea2733bcb2fe4b4c5
|
|
| MD5 |
c80124d2a6a470b3892d66e726c830b2
|
|
| BLAKE2b-256 |
caff323c6a746c28dd5575eefe01f4ae9cdd875a440e7ae12728c1ca3f7bb5a3
|
File details
Details for the file infolang_adk-0.1.0-py3-none-any.whl.
File metadata
- Download URL: infolang_adk-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9bf45ed114e26f3dc7513c665d5e38366bba6b8d82264923d264b9606e24546
|
|
| MD5 |
6e405d7da8a8a5c207e866d3e75cb9bd
|
|
| BLAKE2b-256 |
0816aa86425af59283427e4a2a2ea2fe66672a0973cf1ccbe5d562828f1792f1
|