LangMemLangMem 0.0.30 · LangGraph 1.2 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
19 small wins to finish your pathNext lesson →

Profiles

A profile is a memory with a fixed shape: one Pydantic document per user, such as a name, a contact method and a city, that the manager keeps up to date field by field instead of adding new memories.

Last updated: 30 Sep, 2026 · LangMem 0.0.30

Some facts have exactly one current value: Asha has one name and one preferred contact method. For those, a profile is easier to use than a collection, because your code can read profile.contact directly, with no search.

Syntax:

python
manager = create_memory_manager(model, schemas=[Profile], enable_inserts=False)

The profile schema

A schema is a Pydantic model. Its docstring becomes the tool's description and its fields the tool's arguments, so the model fills in fields instead of writing sentences.

python
from pydantic import BaseModel


class Profile(BaseModel):
    """What we know about a support customer."""

    name: str | None = None
    contact: str | None = None
    city: str | None = None

A manager that keeps one profile

schemas=[Profile] replaces the default Memory tool with a Profile tool. enable_inserts=False means: once a profile exists, only update it, never add a second one.

python
profiler = create_memory_manager(
    model,
    schemas=[Profile],
    instructions="Fill in the customer's profile.",
    enable_inserts=False,
)

Filling in and updating Asha's profile

ExampleAPI key
from langchain.chat_models import init_chat_model
from langmem import create_memory_manager

from pydantic import BaseModel


class Profile(BaseModel):
    """What we know about a support customer."""

    name: str | None = None
    contact: str | None = None
    city: str | None = None


model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0)
profiler = create_memory_manager(
    model,
    schemas=[Profile],
    instructions="Fill in the customer's profile.",
    enable_inserts=False,
)

first = profiler.invoke({"messages": [{"role": "user", "content": "My name is Asha, please email me."}]})
print(first[0].content)

update = profiler.invoke({
    "messages": [{"role": "user", "content": "I moved to Pune last month."}],
    "existing": [(first[0].id, first[0].content)],
})
print(update[0].content, "| same id:", update[0].id == first[0].id)

What the two calls did

  • First call: name filled, contact left empty. Asha said "please email me", yet contact is None. The model missed a field it could have filled; a clearer field description or instruction, such as "contact is the channel: email, phone or text", gives it a better chance.
  • Second call: city filled in, same id. same id: True means the model patched the one profile instead of adding a second, because enable_inserts=False allows only updates once a profile exists.
  • The name stayed. A patch changes only the fields it names; name was untouched.

Profile vs collection

ProfileCollection
How many per userOneAs many as the conversations produce
What the model writesField valuesFree text
Reading itprofile.contactSearch, then read each memory
LosesAnything that has no fieldNothing, but it keeps growing

When a profile fits

  • Settings a user has one of: preferred name, language, timezone, contact method.
  • Data your code needs as a value, such as the channel to send a notification on.
  • A screen where users see and correct what the assistant knows about them.
Watch out. A profile only keeps what has a field. "I work nights" has nowhere to go in this Profile, so it is dropped. Add a field, or keep a collection next to the profile for everything else.
Try it yourself
  • Add order_ids: list[str] = [] to Profile and mention two orders.
  • Set enable_inserts=True and send two different names in separate calls.
  • Print Profile.model_json_schema(): that is the tool the model sees.

You understood something today that you didn't yesterday.