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:
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.
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 = NoneA 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.
profiler = create_memory_manager(
model,
schemas=[Profile],
instructions="Fill in the customer's profile.",
enable_inserts=False,
)Filling in and updating Asha's profile
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)name='Asha' contact=None city=None name='Asha' contact=None city='Pune' | same id: True
What the two calls did
- First call: name filled, contact left empty. Asha said "please email me", yet
contactisNone. 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: Truemeans the model patched the one profile instead of adding a second, becauseenable_inserts=Falseallows only updates once a profile exists. - The name stayed. A patch changes only the fields it names;
namewas untouched.
Profile vs collection
| Profile | Collection | |
|---|---|---|
| How many per user | One | As many as the conversations produce |
| What the model writes | Field values | Free text |
| Reading it | profile.contact | Search, then read each memory |
| Loses | Anything that has no field | Nothing, 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.
Profile, so it is dropped. Add a field, or keep a collection next to the profile for everything else.Related
- Previous: Updates and deletes
- Next: Episodic memory
- Reference: How to manage user profiles
- Add
order_ids: list[str] = []toProfileand mention two orders. - Set
enable_inserts=Trueand 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.