Okay, let’s see what we can do to implement long-term conversation persistence using a database. For this experiment I will use SQLite rather than PostgreSQL. In a production environment I would certainly use the latter.
Too start I am going see if I can simply replace the in-memory checkpointer with the SQLite checkpointer. Adding any code (e.g. imports, instantiations, etc.) needed to get that going. Then see what happens.
SqliteSaver
I don’t think I needed to use the async version, AsyncSqliteSaver
. Guess time will tell. Anyway from what I can see on the appropriate page in the API reference, this should be pretty straightforward. But, then again, I tend to think that is the case pretty much all the time. All too often, very mistakenly. Let’s find out.
... ...
import sqlite3
from langgraph.checkpoint.sqlite import SqliteSaver
Well, Python is complaining that no such package is available. Tried installing langgraph-checkpoint-sqlite
via conda. No go. Have to use pip install
. So, deactivate the current environment, and clone it: conda create --name llmpip-3.13 --clone llm-3.13
. Then pip install langgraph-checkpoint-sqlite
. Didn’t need to insatll pip
via conda as already in the environment. Lots of output. Activated the new environment and good to go.
I went through a number of bugs/issues. Some were just typos or lack of attention. The major one was a SQLite error:
# lines and lines of stuff not all that important
... ...
sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id 23032 and this is thread id 40892.
The solution I used was to add the parameter check_same_thread=False
in my call to qlite3.connect
. There are likely other solutions, but for now this worked. Apparently, using AsyncSqliteSaver
may have prevented this issue altogether (see article on sqlite3 concurrency issues in the Resources↷ section).
I also want to try to prevent any issues with a SQLite connection remaining open when I close the Streamlit app or things crash. So, I put the code to run the app in a with
block instantiating a connection to the SQLite database. I also put the instantiation of the SqliteSaver
object and moved the compilation of the graph into that block. I am sure you realized that would, obiviously, be a necessity.
Here’s the code for the run app block.
elif app_mode == "Run the app":
with sqlite3.connect("history.sqlite", check_same_thread=False) as conn:
mem = SqliteSaver(conn)
app = workflow.compile(checkpointer=mem)
st.markdown(app_ttl, unsafe_allow_html=True)
readme_text.empty()
with st.form("my_form"):
u_inp = st.text_area(
"Enter text:",
"What are the three key pieces of advice for learning how to code?",
)
submitted = st.form_submit_button("Submit")
if submitted:
in_msgs = [HumanMessage(u_inp)]
llm_said = app.invoke({"messages": in_msgs}, cnfg)
for i in range(len(llm_said["messages"])):
if i % 2 == 0:
st.write(f'Me: {llm_said["messages"][i].content}')
else:
st.write(f'AI: {llm_said["messages"][i].content}')
And that worked more or less as expected. The only issue I had was, if I closed the browser tab before terminating the Streamlit server the server would hang and I couldn’t close it. Also if I was in run mode when closing the server it also hung. That was because the database connection was still open—the app is in a loop at this point, so the with
block is never exited. I messed around with various ideas to make sure the database connection got closed from within the browser tab. Nothing really superlative. But, if I select “Show instructions” in the sidebar and then terminate the Streamlit server before closing the browser tab, things work nicely.
I am not going to show you any of my successful test conversation. Instead I am going to add the option to display the conversation history contained in the current SQLite database. Eventually I want to add user ids to allow for the storing of conversation histories for different users and only display the history for the currently active user. I also would like to add something like a topic field to allow storing of identifiably different conversations by user.
SQLite Learning and Testing
I am going to start by coding some basic SQLite queries and such in a new module. I need to get a handle of the coding concepts and SQLite commands. I also want to inspect the table structures and some of the data added during my testing. Don’t really want to be doing that in the chatbot module.
Get Table Names
To start I will open a connection to the database file created earlier. And get a list of the tables in the database.
(If you are wondering, *.tst*
is in my .gitignore.)
# sqlite.tst.py: simple module to investigate sqlite database generated by SqliteSaver library
# ver 0.1.0: open database, get list of tables, get structure of each table
import sqlite3
import pandas as pd
db_fl = "history_2.sqlite"
# use try block in case file does not exist or query fails
# use finally clause to close connection and cursor if open
try:
conn = sqlite3.connect(db_fl)
if conn:
print(conn)
sql_query = "SELECT name FROM sqlite_master WHERE type='table';"
# Create cursor object
cur = conn.cursor()
if cur:
print(cur)
# execute query
tbls = cur.execute(sql_query).fetchall()
print("List of tables:")
# tbls is list of tuples, we only want table names
# and in this case the second element of each tuple is empty
# e.g. [('checkpoints',), ('writes',)]
tbl_nms = sorted(list(zip(*tbls))[0])
print(f"\t{tbl_nms}")
except sqlite3.Error as error:
print("Failed to execute the above query", error)
finally:
# if cursor and/or connection is/are open, we need to close it/them
if cur:
cur.close()
if conn:
conn.close()
And, the terminal output.
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite.tst.py
<sqlite3.Connection object at 0x00000207C28A9120>
<sqlite3.Cursor object at 0x00000207C2897540>
List of tables:
['checkpoints', 'writes']
Get Table Structures
Well, structure may be a bit of a misnomer. But I will get the column names for each table. Which is rather easy.
... ...
# get column names for each table
for tbl in tbl_nms:
tmp = cur.execute(f"SELECT * FROM {tbl}")
col_nms = [description[0] for description in tmp.description]
print(f"\n{tbl}:\n\t{col_nms}")
And…
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
List of tables:
['checkpoints', 'writes']
checkpoints:
['thread_id', 'checkpoint_ns', 'checkpoint_id', 'parent_checkpoint_id', 'type', 'checkpoint', 'metadata']
writes:
['thread_id', 'checkpoint_ns', 'checkpoint_id', 'task_id', 'idx', 'channel', 'type', 'value']
And, we can, in fact, get the table structure. As a set of columns or even as the actual SQL CREATE
command. Let’s give that a go.
For the table version, I am using pandas so that we get the column header row in the printed output. If we just print the query result, we get something like the following. Not horribly friendly.
(0, 'thread_id', 'TEXT', 1, None, 1)
(1, 'checkpoint_ns', 'TEXT', 1, "''", 2)
(2, 'checkpoint_id', 'TEXT', 1, None, 3)
(3, 'task_id', 'TEXT', 1, None, 4)
(4, 'idx', 'INTEGER', 1, None, 5)
(5, 'channel', 'TEXT', 1, None, 0)
(6, 'type', 'TEXT', 0, None, 0)
(7, 'value', 'BLOB', 0, None, 0)
Here’s the tiny bit of code required.
... ...
df = pd.read_sql_query(f"PRAGMA table_info({tbl})", conn)
print("\n", df)
t_sql = f"select sql from sqlite_master where name='{tbl}';"
t_s = cur.execute(t_sql).fetchall()
print("\n", t_s[0][0])
And the terminal output for the above.
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
checkpoints:
['thread_id', 'checkpoint_ns', 'checkpoint_id', 'parent_checkpoint_id', 'type', 'checkpoint', 'metadata']
cid name type notnull dflt_value pk
0 0 thread_id TEXT 1 None 1
1 1 checkpoint_ns TEXT 1 '' 2
2 2 checkpoint_id TEXT 1 None 3
3 3 parent_checkpoint_id TEXT 0 None 0
4 4 type TEXT 0 None 0
5 5 checkpoint BLOB 0 None 0
6 6 metadata BLOB 0 None 0
CREATE TABLE checkpoints (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
parent_checkpoint_id TEXT,
type TEXT,
checkpoint BLOB,
metadata BLOB,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
)
writes:
['thread_id', 'checkpoint_ns', 'checkpoint_id', 'task_id', 'idx', 'channel', 'type', 'value']
cid name type notnull dflt_value pk
0 0 thread_id TEXT 1 None 1
1 1 checkpoint_ns TEXT 1 '' 2
2 2 checkpoint_id TEXT 1 None 3
3 3 task_id TEXT 1 None 4
4 4 idx INTEGER 1 None 5
5 5 channel TEXT 1 None 0
6 6 type TEXT 0 None 0
7 7 value BLOB 0 None 0
CREATE TABLE writes (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idx INTEGER NOT NULL,
channel TEXT NOT NULL,
type TEXT,
value BLOB,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
)
Unfortunately, that doesn’t really help us sort out what the columns are for.
Get Database Contents
Okay, let’s move on. Time to get the data from the database. The one I am going to use had a two part conversation regarding noodles and chicken soup. It’s the same one we’ve been using to get the table structure information above.
To start I am only going to get all the rows from the checkpoints
table. And print them to the terminal. When I saw how big the content for some of those fields was, I added some extra spacing for a couple of them.
if do_conv and tbl=="checkpoints":
tmp = cur.execute(f"SELECT * FROM {tbl}").fetchall()
for i, rw in enumerate(tmp):
print(f"\nrow: {i + 1}")
for j, c_nm in enumerate(col_nms):
if c_nm=="checkpoint":
print('')
print(f"\t{c_nm}: {rw[j]}")
if c_nm=="checkpoint":
print('')
I am going to truncate the output, rather lengthy. I will show the first 3 and the last rows returned by the query.
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
row: 1
thread_id: bark1
checkpoint_ns:
checkpoint_id: 1efe5936-29fa-68a0-bfff-acb9f9317521
parent_checkpoint_id: None
type: msgpack
checkpoint: b'\x87\xa1v\x01\xa2ts\xd9 2025-02-07T20:37:44.935822+00:00\xa2id\xd9$1efe5936-29fa-68a0-bfff-acb9f9317521\xaechannel_values\x81\xa9__start__\x81\xa8messages\x91\xc7\xb2\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9#What is the best noodle for a soup?\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xc0\xa7example\xc2\xb3model_validate_json\xb0channel_versions\x81\xa9__start__\xd9300000000000000000000000000000001.0.6978629174552546\xadversions_seen\x81\xa9__input__\x80\xadpending_sends\x90'
metadata: b'{"source": "input", "writes": {"__start__": {"messages": [{"lc": 1, "type": "constructor", "id": ["langchain", "schema", "messages", "HumanMessage"], "kwargs": {"content": "What is the best noodle for a soup?", "type": "human"}}]}}, "thread_id": "bark1", "step": -1, "parents": {}}'
row: 2
thread_id: bark1
checkpoint_ns:
checkpoint_id: 1efe5936-2a08-62af-8000-7649400d5c20
parent_checkpoint_id: 1efe5936-29fa-68a0-bfff-acb9f9317521
type: msgpack
checkpoint: b'\x87\xa1v\x01\xa2ts\xd9 2025-02-07T20:37:44.941366+00:00\xa2id\xd9$1efe5936-2a08-62af-8000-7649400d5c20\xaechannel_values\x82\xa8messages\x91\xc7\xd7\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9#What is the best noodle for a soup?\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$aa858d66-2cc4-4421-ad21-a2399373222a\xa7example\xc2\xb3model_validate_json\xabstart:model\xa9__start__\xb0channel_versions\x83\xa9__start__\xd9300000000000000000000000000000002.0.6290218760321353\xa8messages\xd9200000000000000000000000000000002.0.036402279941953\xabstart:model\xd9300000000000000000000000000000002.0.6303008059966366\xadversions_seen\x82\xa9__input__\x80\xa9__start__\x81\xa9__start__\xd9300000000000000000000000000000001.0.6978629174552546\xadpending_sends\x90'
metadata: b'{"source": "loop", "writes": null, "thread_id": "bark1", "step": 0, "parents": {}}'
row: 3
thread_id: bark1
checkpoint_ns:
checkpoint_id: 1efe5936-be26-6e49-8001-998e7b86c8aa
parent_checkpoint_id: 1efe5936-2a08-62af-8000-7649400d5c20
type: msgpack
checkpoint: b"\x87\xa1v\x01\xa2ts\xd9 2025-02-07T20:38:00.472885+00:00\xa2id\xd9$1efe5936-be26-6e49-8001-998e7b86c8aa\xaechannel_values\x82\xa8messages\x92\xc7\xd7\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9#What is the best noodle for a soup?\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$aa858d66-2cc4-4421-ad21-a2399373222a\xa7example\xc2\xb3model_validate_json\xc8\x0b\xb5\x05\x94\xbalangchain_core.messages.ai\xa9AIMessage\x8a\xa7content\xda\n5Choosing the best noodle for a soup depends on the type of soup you're making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:\n\n1. **Chicken Noodle Soup**:\n - **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n\n2. **Beef Noodle Soup**:\n - **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.\n - **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.\n\n3. **Japanese Soups (e.g., Ramen)**:\n - **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.\n - **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.\n - **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.\n\n4. **Vietnamese Soups (e.g., Pho)**:\n - **Rice Noodles (B\xc3\xa1nh Ph\xe1\xbb\x9f)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.\n\n5. **Italian Soups (e.g., Minestrone)**:\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n - **Orzo**: Rice-shaped pasta that adds a nice texture to soups.\n - **Stelline**: Star-shaped pasta that is great for lighter, clear soups.\n\n6. **Thai Soups (e.g., Tom Yum)**:\n - **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.\n\n7. **Korean Soups (e.g., Jjigae)**:\n - **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.\n\n8. **Mexican Soups (e.g., Sopa de Fideo)**:\n - **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.\n\n9. **Vegetable Soups**:\n - **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.\n - **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.\n\n10. **Miso Soup**:\n - **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.\n\nWhen selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.\xb1additional_kwargs\x80\xb1response_metadata\x83\xabtoken_usage\x83\xadprompt_tokens\x0e\xactotal_tokens\xcd\x03K\xb1completion_tokens\xcd\x03=\xa5model\xb4mistral-large-latest\xadfinish_reason\xa4stop\xa4type\xa2ai\xa4name\xc0\xa2id\xd9*run-9393c188-ab2b-4e38-9370-9c4ce4d88c24-0\xa7example\xc2\xaatool_calls\x90\xb2invalid_tool_calls\x90\xaeusage_metadata\x83\xacinput_tokens\x0e\xadoutput_tokens\xcd\x03=\xactotal_tokens\xcd\x03K\xb3model_validate_json\xa5model\xa5model\xb0channel_versions\x84\xa9__start__\xd9300000000000000000000000000000002.0.6290218760321353\xa8messages\xd9400000000000000000000000000000003.0.28277503923801617\xabstart:model\xd9300000000000000000000000000000003.0.8022300008494996\xa5model\xd9300000000000000000000000000000003.0.5025138805009314\xadversions_seen\x83\xa9__input__\x80\xa9__start__\x81\xa9__start__\xd9300000000000000000000000000000001.0.6978629174552546\xa5model\x81\xabstart:model\xd9300000000000000000000000000000002.0.6303008059966366\xadpending_sends\x90"
metadata: b'{"source": "loop", "writes": {"model": {"messages": {"lc": 1, "type": "constructor", "id": ["langchain", "schema", "messages", "AIMessage"], "kwargs": {"content": "Choosing the best noodle for a soup depends on the type of soup you\'re making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:\\n\\n1. **Chicken Noodle Soup**:\\n - **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.\\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\\n\\n2. **Beef Noodle Soup**:\\n - **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.\\n - **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.\\n\\n3. **Japanese Soups (e.g., Ramen)**:\\n - **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.\\n - **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.\\n - **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.\\n\\n4. **Vietnamese Soups (e.g., Pho)**:\\n - **Rice Noodles (B\xc3\xa1nh Ph\xe1\xbb\x9f)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.\\n\\n5. **Italian Soups (e.g., Minestrone)**:\\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\\n - **Orzo**: Rice-shaped pasta that adds a nice texture to soups.\\n - **Stelline**: Star-shaped pasta that is great for lighter, clear soups.\\n\\n6. **Thai Soups (e.g., Tom Yum)**:\\n - **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.\\n\\n7. **Korean Soups (e.g., Jjigae)**:\\n - **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.\\n\\n8. **Mexican Soups (e.g., Sopa de Fideo)**:\\n - **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.\\n\\n9. **Vegetable Soups**:\\n - **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.\\n - **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.\\n\\n10. **Miso Soup**:\\n - **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.\\n\\nWhen selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.", "response_metadata": {"token_usage": {"prompt_tokens": 14, "total_tokens": 843, "completion_tokens": 829}, "model": "mistral-large-latest", "finish_reason": "stop"}, "type": "ai", "id": "run-9393c188-ab2b-4e38-9370-9c4ce4d88c24-0", "usage_metadata": {"input_tokens": 14, "output_tokens": 829, "total_tokens": 843}, "tool_calls": [], "invalid_tool_calls": []}}}}, "thread_id": "bark1", "step": 1, "parents": {}}'
... ...
row: 6
thread_id: bark1
checkpoint_ns:
checkpoint_id: 1efe593b-6224-61e4-8004-3f03c3b39994
parent_checkpoint_id: 1efe593b-1ca3-6212-8003-2bbe8533bb4a
type: msgpack
checkpoint: b"\x87\xa1v\x01\xa2ts\xd9 2025-02-07T20:40:05.042579+00:00\xa2id\xd9$1efe593b-6224-61e4-8004-3f03c3b39994\xaechannel_values\x82\xa8messages\x94\xc7\xd7\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9#What is the best noodle for a soup?\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$aa858d66-2cc4-4421-ad21-a2399373222a\xa7example\xc2\xb3model_validate_json\xc8\x0b\xb5\x05\x94\xbalangchain_core.messages.ai\xa9AIMessage\x8a\xa7content\xda\n5Choosing the best noodle for a soup depends on the type of soup you're making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:\n\n1. **Chicken Noodle Soup**:\n - **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n\n2. **Beef Noodle Soup**:\n - **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.\n - **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.\n\n3. **Japanese Soups (e.g., Ramen)**:\n - **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.\n - **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.\n - **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.\n\n4. **Vietnamese Soups (e.g., Pho)**:\n - **Rice Noodles (B\xc3\xa1nh Ph\xe1\xbb\x9f)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.\n\n5. **Italian Soups (e.g., Minestrone)**:\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n - **Orzo**: Rice-shaped pasta that adds a nice texture to soups.\n - **Stelline**: Star-shaped pasta that is great for lighter, clear soups.\n\n6. **Thai Soups (e.g., Tom Yum)**:\n - **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.\n\n7. **Korean Soups (e.g., Jjigae)**:\n - **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.\n\n8. **Mexican Soups (e.g., Sopa de Fideo)**:\n - **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.\n\n9. **Vegetable Soups**:\n - **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.\n - **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.\n\n10. **Miso Soup**:\n - **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.\n\nWhen selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.\xb1additional_kwargs\x80\xb1response_metadata\x83\xabtoken_usage\x83\xadprompt_tokens\x0e\xactotal_tokens\xcd\x03K\xb1completion_tokens\xcd\x03=\xa5model\xb4mistral-large-latest\xadfinish_reason\xa4stop\xa4type\xa2ai\xa4name\xc0\xa2id\xd9*run-9393c188-ab2b-4e38-9370-9c4ce4d88c24-0\xa7example\xc2\xaatool_calls\x90\xb2invalid_tool_calls\x90\xaeusage_metadata\x83\xacinput_tokens\x0e\xadoutput_tokens\xcd\x03=\xactotal_tokens\xcd\x03K\xb3model_validate_json\xc7\xf2\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9>What are the best veggies for a chicken soup? Please be brief.\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$330e6b15-2b99-432e-9da1-1d2cf00974fa\xa7example\xc2\xb3model_validate_json\xc8\x06\xa1\x05\x94\xbalangchain_core.messages.ai\xa9AIMessage\x8a\xa7content\xda\x05\x1dFor a classic chicken soup, consider using a mix of the following vegetables for a balanced and flavorful result:\n\n1. **Aromatics**:\n - Onions: Provide a savory base.\n - Carrots: Add sweetness and color.\n - Celery: Gives a slight crunch and fresh flavor.\n\n2. **Starchy Vegetables**:\n - Potatoes: Add heartiness and help thicken the soup.\n\n3. **Leafy Greens**:\n - Spinach or Kale: Add color, nutrients, and a touch of bitterness.\n\n4. **Other Vegetables**:\n - Peas: Provide sweetness and a pop of color.\n - Green Beans: Add a slight crunch and fresh flavor.\n - Corn: Adds sweetness and a touch of crunch.\n - Bell Peppers: Provide color, crunch, and a mildly sweet flavor.\n - Tomatoes: Add a touch of acidity and umami (use sparingly to avoid overwhelming the chicken flavor).\n\n5. **Herbs and Spices**:\n - Garlic: Adds depth of flavor.\n - Bay Leaves: Infuse the soup with a subtle, herbal flavor.\n - Thyme: Adds a delicate, earthy note.\n - Parsley: Provides freshness and color when used as a garnish.\n\nFor a simple, classic chicken soup, combine chicken, onions, carrots, celery, and potatoes. For added color, flavor, and nutrients, consider mixing in leafy greens and other vegetables from the list above. Adjust the combination based on your preferences and what you have on hand.\xb1additional_kwargs\x80\xb1response_metadata\x83\xabtoken_usage\x83\xadprompt_tokens\xcd\x03^\xactotal_tokens\xcd\x04\xe2\xb1completion_tokens\xcd\x01\x84\xa5model\xb4mistral-large-latest\xadfinish_reason\xa4stop\xa4type\xa2ai\xa4name\xc0\xa2id\xd9*run-922bc18b-5eda-4f24-b1f2-dfd48fba5b92-0\xa7example\xc2\xaatool_calls\x90\xb2invalid_tool_calls\x90\xaeusage_metadata\x83\xacinput_tokens\xcd\x03^\xadoutput_tokens\xcd\x01\x84\xactotal_tokens\xcd\x04\xe2\xb3model_validate_json\xa5model\xa5model\xb0channel_versions\x84\xa9__start__\xd9300000000000000000000000000000005.0.9248626949652146\xa8messages\xd9400000000000000000000000000000006.0.39514031510184944\xabstart:model\xd9300000000000000000000000000000006.0.4184466846450636\xa5model\xd9400000000000000000000000000000006.0.17796070290350752\xadversions_seen\x83\xa9__input__\x80\xa9__start__\x81\xa9__start__\xd9200000000000000000000000000000004.0.932258770044227\xa5model\x81\xabstart:model\xd9300000000000000000000000000000005.0.5330560344042499\xadpending_sends\x90"
metadata: b'{"source": "loop", "writes": {"model": {"messages": {"lc": 1, "type": "constructor", "id": ["langchain", "schema", "messages", "AIMessage"], "kwargs": {"content": "For a classic chicken soup, consider using a mix of the following vegetables for a balanced and flavorful result:\\n\\n1. **Aromatics**:\\n - Onions: Provide a savory base.\\n - Carrots: Add sweetness and color.\\n - Celery: Gives a slight crunch and fresh flavor.\\n\\n2. **Starchy Vegetables**:\\n - Potatoes: Add heartiness and help thicken the soup.\\n\\n3. **Leafy Greens**:\\n - Spinach or Kale: Add color, nutrients, and a touch of bitterness.\\n\\n4. **Other Vegetables**:\\n - Peas: Provide sweetness and a pop of color.\\n - Green Beans: Add a slight crunch and fresh flavor.\\n - Corn: Adds sweetness and a touch of crunch.\\n - Bell Peppers: Provide color, crunch, and a mildly sweet flavor.\\n - Tomatoes: Add a touch of acidity and umami (use sparingly to avoid overwhelming the chicken flavor).\\n\\n5. **Herbs and Spices**:\\n - Garlic: Adds depth of flavor.\\n - Bay Leaves: Infuse the soup with a subtle, herbal flavor.\\n - Thyme: Adds a delicate, earthy note.\\n - Parsley: Provides freshness and color when used as a garnish.\\n\\nFor a simple, classic chicken soup, combine chicken, onions, carrots, celery, and potatoes. For added color, flavor, and nutrients, consider mixing in leafy greens and other vegetables from the list above. Adjust the combination based on your preferences and what you have on hand.", "response_metadata": {"token_usage": {"prompt_tokens": 862, "total_tokens": 1250, "completion_tokens": 388}, "model": "mistral-large-latest", "finish_reason": "stop"}, "type": "ai", "id": "run-922bc18b-5eda-4f24-b1f2-dfd48fba5b92-0", "usage_metadata": {"input_tokens": 862, "output_tokens": 388, "total_tokens": 1250}, "tool_calls": [], "invalid_tool_calls": []}}}}, "thread_id": "bark1", "step": 4, "parents": {}}'
Looks like each row contains the complete conversation to that point in time. Though there appears to be some duplication between rows. Once with a “source” of “input”, followed by one with a “source” of “loop” in the metadata
field.
The source of the checkpoint.
- “input”: The checkpoint was created from an input to invoke/stream/batch.
- “loop”: The checkpoint was created from inside the pregel loop.
- “update”: The checkpoint was created from a manual state update.
- “fork”: The checkpoint was created as a copy of another checkpoint.
API Reference | checkpointer | CheckpointMetadata
So, whenever I input a prompt it is first saved and recorded as having a source of “input”. It is then recorded a 2nd time when it is passed to the graph app loop. AI output is only recorded once, and always has the source “loop”. At least that’s how I currently see it.
But if I want to display the full conversation it looks like I only need to get the last entry in the checkpoints
table.
Let’s see about getting that row from the table.
if do_conv:
n_rw = cur.execute(f"SELECT COUNT() FROM {tbl}").fetchone()[0]
if tbl == "checkpoints":
tmp = cur.execute(f'SELECT "checkpoint" FROM {tbl} ORDER BY ROWID DESC LIMIT 1').fetchall()
print(f"\nrow: {n_rw}")
print(tmp[0][0])
else:
continue
And, the ouput:
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
row: 6
b"\x87\xa1v\x01\xa2ts\xd9 2025-02-07T20:40:05.042579+00:00\xa2id\xd9$1efe593b-6224-61e4-8004-3f03c3b39994\xaechannel_values\x82\xa8messages\x94\xc7\xd7\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9#What is the best noodle for a soup?\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$aa858d66-2cc4-4421-ad21-a2399373222a\xa7example\xc2\xb3model_validate_json\xc8\x0b\xb5\x05\x94\xbalangchain_core.messages.ai\xa9AIMessage\x8a\xa7content\xda\n5Choosing the best noodle for a soup depends on the type of soup you're making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:\n\n1. **Chicken Noodle Soup**:\n - **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n\n2. **Beef Noodle Soup**:\n - **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.\n - **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.\n\n3. **Japanese Soups (e.g., Ramen)**:\n - **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.\n - **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.\n - **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.\n\n4. **Vietnamese Soups (e.g., Pho)**:\n - **Rice Noodles (B\xc3\xa1nh Ph\xe1\xbb\x9f)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.\n\n5. **Italian Soups (e.g., Minestrone)**:\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n - **Orzo**: Rice-shaped pasta that adds a nice texture to soups.\n - **Stelline**: Star-shaped pasta that is great for lighter, clear soups.\n\n6. **Thai Soups (e.g., Tom Yum)**:\n - **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.\n\n7. **Korean Soups (e.g., Jjigae)**:\n - **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.\n\n8. **Mexican Soups (e.g., Sopa de Fideo)**:\n - **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.\n\n9. **Vegetable Soups**:\n - **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.\n - **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.\n\n10. **Miso Soup**:\n - **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.\n\nWhen selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.\xb1additional_kwargs\x80\xb1response_metadata\x83\xabtoken_usage\x83\xadprompt_tokens\x0e\xactotal_tokens\xcd\x03K\xb1completion_tokens\xcd\x03=\xa5model\xb4mistral-large-latest\xadfinish_reason\xa4stop\xa4type\xa2ai\xa4name\xc0\xa2id\xd9*run-9393c188-ab2b-4e38-9370-9c4ce4d88c24-0\xa7example\xc2\xaatool_calls\x90\xb2invalid_tool_calls\x90\xaeusage_metadata\x83\xacinput_tokens\x0e\xadoutput_tokens\xcd\x03=\xactotal_tokens\xcd\x03K\xb3model_validate_json\xc7\xf2\x05\x94\xbdlangchain_core.messages.human\xacHumanMessage\x87\xa7content\xd9>What are the best veggies for a chicken soup? Please be brief.\xb1additional_kwargs\x80\xb1response_metadata\x80\xa4type\xa5human\xa4name\xc0\xa2id\xd9$330e6b15-2b99-432e-9da1-1d2cf00974fa\xa7example\xc2\xb3model_validate_json\xc8\x06\xa1\x05\x94\xbalangchain_core.messages.ai\xa9AIMessage\x8a\xa7content\xda\x05\x1dFor a classic chicken soup, consider using a mix of the following vegetables for a balanced and flavorful result:\n\n1. **Aromatics**:\n - Onions: Provide a savory base.\n - Carrots: Add sweetness and color.\n - Celery: Gives a slight crunch and fresh flavor.\n\n2. **Starchy Vegetables**:\n - Potatoes: Add heartiness and help thicken the soup.\n\n3. **Leafy Greens**:\n - Spinach or Kale: Add color, nutrients, and a touch of bitterness.\n\n4. **Other Vegetables**:\n - Peas: Provide sweetness and a pop of color.\n - Green Beans: Add a slight crunch and fresh flavor.\n - Corn: Adds sweetness and a touch of crunch.\n - Bell Peppers: Provide color, crunch, and a mildly sweet flavor.\n - Tomatoes: Add a touch of acidity and umami (use sparingly to avoid overwhelming the chicken flavor).\n\n5. **Herbs and Spices**:\n - Garlic: Adds depth of flavor.\n - Bay Leaves: Infuse the soup with a subtle, herbal flavor.\n - Thyme: Adds a delicate, earthy note.\n - Parsley: Provides freshness and color when used as a garnish.\n\nFor a simple, classic chicken soup, combine chicken, onions, carrots, celery, and potatoes. For added color, flavor, and nutrients, consider mixing in leafy greens and other vegetables from the list above. Adjust the combination based on your preferences and what you have on hand.\xb1additional_kwargs\x80\xb1response_metadata\x83\xabtoken_usage\x83\xadprompt_tokens\xcd\x03^\xactotal_tokens\xcd\x04\xe2\xb1completion_tokens\xcd\x01\x84\xa5model\xb4mistral-large-latest\xadfinish_reason\xa4stop\xa4type\xa2ai\xa4name\xc0\xa2id\xd9*run-922bc18b-5eda-4f24-b1f2-dfd48fba5b92-0\xa7example\xc2\xaatool_calls\x90\xb2invalid_tool_calls\x90\xaeusage_metadata\x83\xacinput_tokens\xcd\x03^\xadoutput_tokens\xcd\x01\x84\xactotal_tokens\xcd\x04\xe2\xb3model_validate_json\xa5model\xa5model\xb0channel_versions\x84\xa9__start__\xd9300000000000000000000000000000005.0.9248626949652146\xa8messages\xd9400000000000000000000000000000006.0.39514031510184944\xabstart:model\xd9300000000000000000000000000000006.0.4184466846450636\xa5model\xd9400000000000000000000000000000006.0.17796070290350752\xadversions_seen\x83\xa9__input__\x80\xa9__start__\x81\xa9__start__\xd9200000000000000000000000000000004.0.932258770044227\xa5model\x81\xabstart:model\xd9300000000000000000000000000000005.0.5330560344042499\xadpending_sends\x90"
Sadly, I have no idea how to convert that to something readable. Looking at the source for SqliteSaver, it seems the above is encoded using the langgraph.checkpoint.serde.jsonplus JsonPlusSerializer
. I have no intention of trying to figure out the code needed to deserialize and print the conversation history in a nice tidy way.
But having looked at the code and some of the documentation for checkpoints in the API reference, it is clear that SqliteSaver provides methods for getting that data in a tidier fashion. So, before calling this post done, let’s give that a go.
SqliteSaver.list
I am going to use the checkpoints list
method.
This method retrieves a list of checkpoint tuples from the SQLite database based on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).
from the method definition in
langgraph/libs/checkpoint-sqlite/langgraph/checkpoint/sqlite/init.py
Which I believe means we only need to look at the first item in the list to get the full conversation history to-date. And we are only interested in the checkpoint
field. And, only the messages
field in the channel_values
field of the checkpoint
.
if do_sls:
read_cfg = {"configurable": {"thread_id": "bark1"}}
with sqlite3.connect(db_fl, check_same_thread=False) as conn:
mem = SqliteSaver(conn)
mem_lst = list(mem.list(read_cfg))
print(f"{type(mem_lst)} -> {type(mem_lst[0])}")
print(f"last entry: {mem_lst[0].checkpoint["messages"]}")
And I got the following in the terminal window.
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
last entry: [HumanMessage(content='What is the best noodle for a soup?', additional_kwargs={}, response_metadata={}, id='aa858d66-2cc4-4421-ad21-a2399373222a'), AIMessage(content="Choosing the best noodle for a soup depends on the type of soup you're making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:\n\n1. **Chicken Noodle Soup**:\n - **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n\n2. **Beef Noodle Soup**:\n - **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.\n - **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.\n\n3. **Japanese Soups (e.g., Ramen)**:\n - **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.\n - **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.\n - **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.\n\n4. **Vietnamese Soups (e.g., Pho)**:\n - **Rice Noodles (Bánh Phở)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.\n\n5. **Italian Soups (e.g., Minestrone)**:\n - **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.\n - **Orzo**: Rice-shaped pasta that adds a nice texture to soups.\n - **Stelline**: Star-shaped pasta that is great for lighter, clear soups.\n\n6. **Thai Soups (e.g., Tom Yum)**:\n - **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.\n\n7. **Korean Soups (e.g., Jjigae)**:\n - **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.\n\n8. **Mexican Soups (e.g., Sopa de Fideo)**:\n - **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.\n\n9. **Vegetable Soups**:\n - **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.\n - **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.\n\n10. **Miso Soup**:\n - **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.\n\nWhen selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.", additional_kwargs={}, response_metadata={'token_usage': {'prompt_tokens': 14, 'total_tokens': 843, 'completion_tokens': 829}, 'model': 'mistral-large-latest', 'finish_reason': 'stop'}, id='run-9393c188-ab2b-4e38-9370-9c4ce4d88c24-0', usage_metadata={'input_tokens': 14, 'output_tokens': 829, 'total_tokens': 843}), HumanMessage(content='What are the best veggies for a chicken soup? Please be brief.', additional_kwargs={}, response_metadata={}, id='330e6b15-2b99-432e-9da1-1d2cf00974fa'), AIMessage(content='For a classic chicken soup, consider using a mix of the following vegetables for a balanced and flavorful result:\n\n1. **Aromatics**:\n - Onions: Provide a savory base.\n - Carrots: Add sweetness and color.\n - Celery: Gives a slight crunch and fresh flavor.\n\n2. **Starchy Vegetables**:\n - Potatoes: Add heartiness and help thicken the soup.\n\n3. **Leafy Greens**:\n - Spinach or Kale: Add color, nutrients, and a touch of bitterness.\n\n4. **Other Vegetables**:\n - Peas: Provide sweetness and a pop of color.\n - Green Beans: Add a slight crunch and fresh flavor.\n - Corn: Adds sweetness and a touch of crunch.\n - Bell Peppers: Provide color, crunch, and a mildly sweet flavor.\n - Tomatoes: Add a touch of acidity and umami (use sparingly to avoid overwhelming the chicken flavor).\n\n5. **Herbs and Spices**:\n - Garlic: Adds depth of flavor.\n - Bay Leaves: Infuse the soup with a subtle, herbal flavor.\n - Thyme: Adds a delicate, earthy note.\n - Parsley: Provides freshness and color when used as a garnish.\n\nFor a simple, classic chicken soup, combine chicken, onions, carrots, celery, and potatoes. For added color, flavor, and nutrients, consider mixing in leafy greens and other vegetables from the list above. Adjust the combination based on your preferences and what you have on hand.', additional_kwargs={}, response_metadata={'token_usage': {'prompt_tokens': 862, 'total_tokens': 1250, 'completion_tokens': 388}, 'model': 'mistral-large-latest', 'finish_reason': 'stop'}, id='run-922bc18b-5eda-4f24-b1f2-dfd48fba5b92-0', usage_metadata={'input_tokens': 862, 'output_tokens': 388, 'total_tokens': 1250})]
And then for each message we are really only interested in the content
field. Let’s see what we can do. Here’s the revised code.
if do_sls:
read_cfg = {"configurable": {"thread_id": "bark1"}}
with sqlite3.connect(db_fl, check_same_thread=False) as conn:
mem = SqliteSaver(conn)
mem_lst = list(mem.list(read_cfg))
tmp = mem_lst[0].checkpoint['channel_values']['messages']
for i, msg in (enumerate(tmp)):
if i % 2 == 0:
print(f"\nuser: {msg.content}")
else:
print(f"\nAI: {msg.content}")
And, here’s the result.
(llmpip-3.13) PS R:\learn\ds_llm> python sqlite_tst.py
user: What is the best noodle for a soup?
AI: Choosing the best noodle for a soup depends on the type of soup you're making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:
1. **Chicken Noodle Soup**:
- **Egg Noodles**: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.
- **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.
2. **Beef Noodle Soup**:
- **Wide Egg Noodles**: Thicker noodles that can stand up to the robust flavors of beef broth.
- **Pappardelle**: Broad, flat pasta that pairs well with hearty beef and vegetable soups.
3. **Japanese Soups (e.g., Ramen)**:
- **Ramen Noodles**: Fresh or dried, these wheat-based noodles are perfect for rich, flavorful broths. They come in various thicknesses and styles.
- **Udon Noodles**: Thick, chewy noodles made from wheat flour, ideal for lighter broths.
- **Soba Noodles**: Made from buckwheat, these noodles have a distinctive flavor and are great for dashi-based soups.
4. **Vietnamese Soups (e.g., Pho)**:
- **Rice Noodles (Bánh Phở)**: Thin, flat noodles made from rice flour, perfect for the aromatic broths of pho.
5. **Italian Soups (e.g., Minestrone)**:
- **Ditalini**: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.
- **Orzo**: Rice-shaped pasta that adds a nice texture to soups.
- **Stelline**: Star-shaped pasta that is great for lighter, clear soups.
6. **Thai Soups (e.g., Tom Yum)**:
- **Rice Vermicelli (Mai Fun)**: Thin, delicate noodles that pair well with the fragrant broths of Thai soups.
7. **Korean Soups (e.g., Jjigae)**:
- **Glass Noodles (Dangmyeon)**: Made from sweet potato starch, these noodles are great for soups and stews.
8. **Mexican Soups (e.g., Sopa de Fideo)**:
- **Fideo**: Thin, short noodles made from wheat flour, perfect for tomato-based soups.
9. **Vegetable Soups**:
- **Whole Wheat Pasta**: Adds a nutty flavor and more fiber to vegetable-based soups.
- **Rotini or Fusilli**: Spiral-shaped pasta that can trap bits of vegetables and broth.
10. **Miso Soup**:
- **Somen Noodles**: Thin, wheat-based noodles that are delicate and pair well with the light miso broth.
When selecting a noodle, consider the cooking time and whether you prefer to cook the noodles separately or directly in the soup. Some noodles, like egg noodles and pasta, can be cooked in the broth, while others, like rice noodles, are better cooked separately to avoid overcooking.
user: What are the best veggies for a chicken soup? Please be brief.
AI: For a classic chicken soup, consider using a mix of the following vegetables for a balanced and flavorful result:
1. **Aromatics**:
- Onions: Provide a savory base.
- Carrots: Add sweetness and color.
- Celery: Gives a slight crunch and fresh flavor.
2. **Starchy Vegetables**:
- Potatoes: Add heartiness and help thicken the soup.
3. **Leafy Greens**:
- Spinach or Kale: Add color, nutrients, and a touch of bitterness.
4. **Other Vegetables**:
- Peas: Provide sweetness and a pop of color.
- Green Beans: Add a slight crunch and fresh flavor.
- Corn: Adds sweetness and a touch of crunch.
- Bell Peppers: Provide color, crunch, and a mildly sweet flavor.
- Tomatoes: Add a touch of acidity and umami (use sparingly to avoid overwhelming the chicken flavor).
5. **Herbs and Spices**:
- Garlic: Adds depth of flavor.
- Bay Leaves: Infuse the soup with a subtle, herbal flavor.
- Thyme: Adds a delicate, earthy note.
- Parsley: Provides freshness and color when used as a garnish.
For a simple, classic chicken soup, combine chicken, onions, carrots, celery, and potatoes. For added color, flavor, and nutrients, consider mixing in leafy greens and other vegetables from the list above. Adjust the combination based on your preferences and what you have on hand.
And it looks like, given the item headings being surrounded by **s, it is formatted/written in Markdown.
Let’s look at a bit of it as markdown.
AI: Choosing the best noodle for a soup depends on the type of soup you’re making, as different noodles have unique textures and flavors that complement specific soup bases. Here are some recommendations:
Chicken Noodle Soup:
- Egg Noodles: These are a classic choice for chicken noodle soup. They have a rich, eggy flavor and a hearty texture that holds up well in the broth.
- Ditalini: Small, tube-shaped pasta that works well in soups with lots of vegetables and meat.
Beef Noodle Soup:
- Wide Egg Noodles: Thicker noodles that can stand up to the robust flavors of beef broth.
- Pappardelle: Broad, flat pasta that pairs well with hearty beef and vegetable soups.
Done
Okay, that’s enough for this post. Way too much repeated and lengthy terminal output. But in the end, we did manage to display the chat history stored in the database in a reasonably tidy way. And, I must admit, I enjoyed this bit of research and coding.
Next time, I am going to look at somehow allowing multiple users to store conversations in the database. And for each conversation I would also like to store the topic of the conversation, hopefully one word. Maybe two. I think I can use the thread_id
field for user names. But I don’t see any easy way of saving a conversation topic using SqliteSaver. At this point I likely won’t be adding any authentication for users. I will just let them enter their id in a text input in the sidebar. An input field for the topic will be there as well.
Until we meet again, happy coding.
Resources
- API reference | Library | Checkpointers
- API reference | Library | Checkpointers | SqliteSaver
- langgraph/libs/checkpoint-sqlite/langgraph/checkpoint/
- SQLite PRAGMA Statements
- SQLite Rowid Tables
- 4 Ways to Get Information about a Table’s Structure in SQLite
- Top 5 Methods to Handle sqlite3 Concurrency Issues in Python ↺