LlamaIndex
LlamaIndex is
a Python framework for building agents. Its agents work through tools: plain
Python functions that you wrap as a FunctionTool and attach to
a FunctionAgent. This page shows three small tools that call
the Korely Python SDK. Once they are
attached, the model decides when to recall, save or look something up. The
agent keeps no state between runs; the memory lives in Korely.
What the tools do
Korely reads each memory you save and
writes facts from it: short statements
such as "Luca upgraded to Pro". When a newer fact replaces an older one,
the older one is closed. The recall tool uses get_context,
which returns a context block
with the facts that are true now, plus the best-matching memories. The
model gets current knowledge instead of a pile of snippets.
Install
You need the Korely SDK, LlamaIndex core and one LLM package (here OpenAI).
The examples were run with korely-memory 0.1.28,
llama-index-core 0.14.25 and
llama-index-llms-openai 0.8.3 on Python 3.12, with a fake model server, so no model provider was called.
pip install korely-memory llama-index-core llama-index-llms-openaiYou also need a Korely API key. Copy it from API Keys in the dashboard at agent.korely.ai and export it, together with your OpenAI key:
export KORELY_API_KEY="kor_live_..."export OPENAI_API_KEY="sk-..."Define the memory tools
A LlamaIndex tool is a typed Python function with a docstring. The function
name becomes the tool name, and the docstring is what the model reads to
decide when to call it, so write the docstring for the model. Wrap each
function with FunctionTool.from_defaults(fn=...). There are
three tools: recall, save and a
targeted search.
from korely_memory import Korelyfrom llama_index.core.tools import FunctionTool
korely = Korely() # reads KORELY_API_KEY from the environment
def recall_memory(query: str) -> str: """Recall settled facts and relevant memories about the user.
Reach for this FIRST, before answering anything about the user. Returns an assembled, prompt-ready block of the user's active bi-temporal facts, not raw rows. """ ctx = korely.get_context(query=query, token_budget=800) return ctx.context
def save_memory(content: str) -> str: """Save something worth remembering about the user.
Pass a single, self-contained statement. Korely extracts the typed facts from it; you do not write the facts yourself. """ memory = korely.add(content) return f"Saved memory {memory.id}."
def search_memory(query: str) -> str: """Find the exact memory that mentioned something specific.
Use this when you need the original wording, not settled facts. Pass a keyword-style query of 1 to 5 words. """ hits = korely.search(query, limit=5) if not hits: return "No matching memories." return "\n".join(f"- ({h.score:.2f}) {h.snippet}" for h in hits)
recall_tool = FunctionTool.from_defaults(fn=recall_memory)save_tool = FunctionTool.from_defaults(fn=save_memory)search_tool = FunctionTool.from_defaults(fn=search_memory) Three tools, three jobs. recall_memory is
the one the model should use by default: it returns the current facts
already assembled. search_memory finds the exact note that
mentioned something, by meaning. save_memory writes. A read
does not call a language model on Korely's side (your model does the
reasoning), so every plan allows at least 100 times more queries than
writes per month.
These tools do not pass a user_id. Reads
then cover every end user of the
project, which is fine for a personal assistant and wrong for a product
with many users. For a product, bind the user inside the functions, for
example korely.get_context(query=query, user_id=user_id) and
korely.add(content, user_id=user_id), with a
user_id that comes from your login and not from the model.
Attach the tools to an agent
Register the tools on a FunctionAgent (for models with a native
tool-calling API) and run it. .run() is async, so call it with
await inside an async function. The system prompt tells the
model to recall before it answers.
import asynciofrom llama_index.core.agent.workflow import FunctionAgentfrom llama_index.llms.openai import OpenAI
from memory_tools import recall_tool, save_tool, search_tool
agent = FunctionAgent( tools=[recall_tool, save_tool, search_tool], llm=OpenAI(model="gpt-4o-mini"), system_prompt=( "You are a helpful assistant with long-term memory. " "Recall memory before answering questions about the user, " "and save any durable new fact the user tells you." ),)
async def main(): # First turn: the user states a fact worth keeping. r1 = await agent.run( user_msg="I just upgraded to the Pro plan, and I prefer async standups." ) print(r1)
# Later turn (even a fresh process): the agent recalls it. r2 = await agent.run(user_msg="Which plan am I on, and how do I like standups?") print(r2)
asyncio.run(main())
On the first turn the model calls save_memory and Korely
extracts the facts. On the second turn it calls recall_memory,
and the model answers "Pro plan, async standups" without you passing any
state between the calls. The text of the answers depends on your model.
Plain functions also work. The tools list
accepts FunctionTool objects or bare Python functions, which
LlamaIndex wraps for you. So tools=[recall_memory, save_memory,
search_memory] works too. Use from_defaults when you
want to set the name or the description yourself.
ReAct agents and several agents
FunctionAgent needs a model with a tool-calling API. For any
other model, use ReActAgent. It takes the same tools and the
same .run(), and drives the tools through the prompt:
from llama_index.core.agent.workflow import ReActAgentfrom llama_index.llms.openai import OpenAI
agent = ReActAgent( tools=[recall_tool, save_tool, search_tool], llm=OpenAI(model="gpt-4o-mini"), system_prompt="Recall memory before answering about the user.",)# response = await agent.run(user_msg="What do you remember about me?")
To combine several agents, put them in an AgentWorkflow and
attach the memory tools to the agent that should own recall and saving:
from llama_index.core.agent.workflow import AgentWorkflow
workflow = AgentWorkflow(agents=[agent], root_agent=agent.name)# response = await workflow.run(user_msg="Catch me up on what you know.") Facts arrive after the write. add() returns
when the memory is stored. The facts are written by
extraction, usually about ten
seconds later. A recall_memory call right after a
save_memory in the same turn may not see the new fact, so do
not save and recall the same thing inside one .run(). By the
next user message the fact is normally there. To check a write, call
korely.events() (it wraps GET /v1/events). The
memory itself can be found by search_memory at once.
A FunctionAgent forgets between runs. Two
separate .run() calls do not share the conversation (we
checked this with a fake model). That is why the durable knowledge
goes into Korely: it comes back through recall_memory on the
next turn, in the next session, or in a new process.
Where to go next
- Memory as a tool: the
full pattern for giving an agent recall, save and search, with prompt
tips and scoping by
user_id. - Get context: how the recall
tool builds its block, with the
token_budgetand source controls.
Something not working? Email
[email protected] with your
llama-index-core and korely-memory versions and
the error output. We read every message.