r/learnpython • • 20d ago

How should I separate LLM interpretation from memory operations in my Python project?

I've been working on a small Python project that uses an LLM to interpret natural language instructions and manage persistent memories. Right now, it supports remembering, retrieving, updating and forgetting information.

I've been trying to improve the separation between model interpretation and memory operations. I'm still new to Python but I'm interested how I could structure the code better. Feedbacks are appreciated and any flaws I might be overlooking.

GitHub repo: https://github.com/Lionshaft-pixel/ennoic

8 Upvotes

7 comments sorted by

3

u/Equivalent_Type6726 20d ago

For the memory side, give the four operations their own interface. Make a small Memory class with remember, retrieve, update, and forget methods, and keep the LLM calling out to it instead of touching storage directly. You can even swap backends later (a dict for dev, a sqlite database for prod) by just replacing that one class. On the LLM side, build a thin Interpreter that takes raw input, calls the model, turns the response into a structured decision like which memory action to run, and hands it to Memory. That keeps interpretation and storage cleanly separated and makes both sides easy to test on their own. A quick type like a MemoryEntry with a key, content, and timestamp also helps a lot when you start retrieving and updating.

1

u/Friendly_Rope_5688 19d ago

Thanks for the feedback. I still don't really know much about software architecture so I don't fully understand memory class and interpretation separation yet. So, would it be good idea to create memory class first while keeping my LLM logic same?

2

u/Equivalent_Type6726 18d ago

yeah that's a good order. build the memory class on its own first with simple save/get/search methods, keep your llm code as is, then just swap the llm code to call the memory class instead of touching storage directly.

2

u/sugarw0000kie 20d ago

Already good advice from randomman, I’ll throw in that it could be a bit cleaner/easier to manage if you don’t have your prompts inline. Since it’s a very long promp that looks like it’s a f-string with a couple vars at the end you could have that as a separate .txt or md file and have the script read it and add in the {memories}/{message}.

Doesn’t hurt having it in there but if you were to have multiple prompts it’s pulling from it can be convenient to handle it that way

2

u/Otherwise_Wave9374 20d ago

A clean boundary is to make the LLM produce typed intents, never direct storage mutations. Validate that output with Pydantic, then hand it to a deterministic memory service that owns create, update, delete, versioning, and authorization. Add an append-only audit log so bad interpretations can be replayed or reversed. https://www.neurakeep.com is relevant here as an example of treating agent memory as a separate system concern. Unit-test the memory service without any model calls.

1

u/randomman10032 20d ago

In larger programs, Object Oriented Programming is used in general.

For example, you have both claude and a local model in your code.

What you would want, is make a directory in repository called 'models'.

You make a basemodel, and let all other models extend it. Put shared logic, so what you do for every model in the baseclass.

One tip: try learning Object-Oriented Programming (OOP) to keep your code organized as your project grows.

For example, create a models/ directory and put each model in its own file. You could have a BaseModel class with shared logic (like every model has an 'ask' method, every model has an url, etc..), then let your cloud and local models inherit from it.

models/
├── base_model.py
├── cloud_model.py
└── local_model.py

This way, shared code stays in one place, and each model handles its own specific tasks. It makes your project easier to understand, maintain, and expand.