Loading the notebook…
Loading the notebook…
Notebooks · In-depth guides · Guide 13
A chat app keeps the conversation somewhere (your database, a session store) and hands it to the agent on each request. This guide covers every way history gets into and out of a shipit agent.
Run end to end on shipit-agent 3.0 with real model calls (Haiku 5.5 on Anthropic, Gemma 4 31B on Bedrock). Every output below is from that run.
import json
import os
import shutil
from pathlib import Path
from shipit_agent import Agent
from shipit_agent.llms import build_llm_from_env
from shipit_agent.llms.factory import build_llm_from_settings
from shipit_agent.models import Message
os.environ["SHIPIT_ANTHROPIC_MODEL"] = "claude-haiku-5-5"
haiku = build_llm_from_env("anthropic")
gemma = build_llm_from_settings({"model": "google.gemma-4-31b"}, provider="bedrock")
PROMPT = "You are a friendly travel assistant. Be brief."
WORK = Path("chat_demo")
shutil.rmtree(WORK, ignore_errors=True)
WORK.mkdir()Runs on the same Agent share a conversation: the second run sees the first.
agent = Agent(llm=haiku, prompt=PROMPT)
agent.run("I'm planning 4 days in Lisbon in May, on a mid-range budget.")
print(agent.run("Which neighbourhood should I stay in?").output)For your itinerary, I'd stay in **Baixa or Chiado**. They're central, flat, and close to Rossio Station (for Sintra) and several metro lines, so you can reach Alfama, Bairro Alto, and Belém easily. Other good options: - **Príncipe Real:** Quieter and leafy, with boutique hotels and good restaurants, and a short walk to Bairro Alto. - **Alfama:** The most atmospheric, and you'll be close to Day 1's sights, but it's very steep, streets are narrow, and it can be noisy at night. - **Bairro Alto:** Lively nightlife, but it can be loud on weekends, so it's better if you want to be in the middle of the action. - **Avenidas Novas or Saldanha:** Quieter and more modern, with better value for mid-range hotels, but less charm and a longer ride to the old town. If you want a central base with good value and an easy walk to most things, choose **Baixa or Chiado**.
In a web app each request usually builds a new agent, so you pass the earlier turns with history=. A turn
is a Message with a role (user or assistant) and content.
history = [
Message(role="user", content="I'm planning 4 days in Lisbon in May, on a mid-range budget."),
Message(role="assistant", content="Great choice. May is warm and not too crowded. How can I help?"),
Message(role="user", content="I don't like steep hills, I'm travelling with my parents."),
Message(role="assistant", content="Noted: flat areas and easy transport matter most."),
]
agent = Agent(llm=haiku, prompt=PROMPT, history=history)
print(agent.run("So which neighbourhood should we stay in?").output)Stay in **Baixa**, the flat downtown grid near the river. It's walkable to most sights, has several metro stations (Baixa-Chiado, Rossio, Terreiro do Paço), and offers good mid-range hotels. For a quieter, still-flat alternative, try **Avenidas Novas** around Saldanha or Marquês de Pombal. It has wide, level boulevards and a metro stop nearby. Avoid Alfama, Bairro Alto, and Graça for this trip. They're charming but very steep and stair-heavy.
Most apps store messages as rows. Turning them into Messages is one line.
rows = [ # e.g. SELECT role, body FROM chat_messages WHERE chat_id = ? ORDER BY created_at
{"role": "user", "body": "Do I need a visa for Portugal? I have a Canadian passport."},
{"role": "assistant", "body": "No visa for stays up to 90 days in the Schengen area."},
]
history = [Message(role=r["role"], content=r["body"]) for r in rows]
print(Agent(llm=haiku, prompt=PROMPT, history=history).run("And how long can I stay, again?").output)You can stay up to 90 days within any 180-day period in the Schengen area, which includes Portugal. Canadian citizens don't need a visa for short tourist, business, or family visits.
result.messages is the conversation the model saw, including this turn. Keep the user and assistant
text to store it.
result = Agent(llm=haiku, prompt=PROMPT, history=history).run("What's the best month to visit Porto?")
turns = [{"role": m.role, "content": m.content} for m in result.messages if m.role in ("user", "assistant") and m.content]
for t in turns:
print(f"{t['role']:9} {t['content'][:80]}")
(WORK / "chat.json").write_text(json.dumps(turns, indent=1))user Do I need a visa for Portugal? I have a Canadian passport. assistant No visa for stays up to 90 days in the Schengen area. user What's the best month to visit Porto? assistant Late spring (May to June) and early autumn (September to October) are the best t
559
The next request loads the file and carries on.
saved = json.loads((WORK / "chat.json").read_text())
agent = Agent(llm=haiku, prompt=PROMPT, history=[Message(**t) for t in saved])
print(agent.run("Remind me: which city did I ask about last, and when should I go?").output)You asked about Porto, and the best times to go are late spring (May to June) or early autumn (September to October).
chat_session() keeps the history between send() calls and gives it back with history().
chat = Agent(llm=haiku, prompt=PROMPT).chat_session(session_id="trip-planner")
chat.send("I land in Lisbon on a Friday evening.")
print(chat.send("What should I do on Saturday morning?").output)
print("\nhistory:", [(m.role, len(m.content or "")) for m in chat.history()])Saturday morning in Lisbon is lovely, especially before the day gets warm. Here are a few good options:
- **Breakfast at a local café:** Try a *pastel de nata* (custard tart) with a coffee, or a *bica* (espresso). Pastéis de Belém is famous, but local neighborhood cafés are just as good and less crowded.
- **Alfama and the castle:** Climb up to São Jorge Castle early for the views and cooler temperatures. Wander the narrow streets of Alfama afterward.
- **Miradouros (viewpoints):** Miradouro da Senhora do Monte or Miradouro das Portas do Sol offer great panoramas over the red rooftops and the Tagus River.
- **Saturday market:** Check out the Feira da Ladra flea market in Campo de Santa Clara (Tuesdays and Saturdays), where you can browse antiques, vintage goods, and local crafts.
Tip: Book your castle or tram tickets online if you can, since queues can get long later in the day.
history: [('system', 46), ('user', 37), ('assistant', 669), ('user', 37), ('assistant', 893)]With a session_store, each turn is saved; a new agent with the same session_id continues the chat.
from shipit_agent import FileSessionStore
store = FileSessionStore(WORK / "sessions")
Agent(llm=haiku, prompt=PROMPT, session_store=store, session_id="chat-ada").run(
"My name is Ada and I'm vegetarian. I'm visiting Lisbon.")
after_restart = Agent(llm=haiku, prompt=PROMPT, session_store=store, session_id="chat-ada")
print(after_restart.run("Suggest a dinner place for me tonight.").output)
print("\nstored messages:", len(store.load("chat-ada").messages))Tonight, I'd try **Ervas Aromáticas**. It's a well-loved vegetarian restaurant with creative, plant-based dishes. Book ahead if you can, since it fills up in the evenings. If it's full, **Cantinho do Aziz** in Mouraria is a good backup. It has a lively atmosphere and plenty of vegetarian options. Hours can change, so check HappyCow or Google Maps before you go. Would you like something closer to a specific area, or a different price range? stored messages: 5
Give each user their own session_id: histories never mix.
Agent(llm=haiku, prompt=PROMPT, session_store=store, session_id="chat-lin").run("I'm Lin, going to Porto for the wine.")
for user in ("chat-ada", "chat-lin"):
a = Agent(llm=haiku, prompt=PROMPT, session_store=store, session_id=user)
print(f"{user}: {a.run('In five words: who am I and where am I going?').output}")chat-ada: Ada, vegetarian, visiting Lisbon.
chat-lin: Lin, traveling to Porto.
Every turn is re-sent each request, so long chats grow expensive. The simplest cap is to pass only the last few turns.
long_history = []
for day in range(1, 11):
long_history += [Message(role="user", content=f"Day {day}: what should I do?"),
Message(role="assistant", content=f"Day {day}: visit spot number {day}.")]
for keep in (len(long_history), 6):
result = Agent(llm=haiku, prompt=PROMPT, history=long_history[-keep:]).run("What did you suggest for the last day?")
print(f"last {keep:2} messages -> input tokens {result.metadata['usage']['prompt_tokens']:4} | {result.output[:70]}")last 20 messages -> input tokens 387 | For the last day (Day 10), I suggested visit spot number 10.
last 6 messages -> input tokens 142 | For Day 10, I suggested visiting spot number 10. That answer was gener
ConversationMemory(strategy="summary") keeps recent turns and folds older ones into a summary written by a
model, so the agent keeps the gist at a fraction of the size.
from shipit_agent import ConversationMemory
memory = ConversationMemory(strategy="summary", window_size=4, summary_llm=haiku)
memory.add_many([
Message(role="user", content="I'm Ada, vegetarian, travelling with my parents who dislike hills."),
Message(role="assistant", content="Got it: vegetarian food, flat areas."),
*long_history,
])
compact = memory.get_messages()
print(len(long_history) + 2, "messages ->", len(compact), "messages")
print(compact[0].content[:400])
print("\n" + Agent(llm=haiku, prompt=PROMPT, history=compact).run("Any dietary needs I mentioned?").output)22 messages -> 5 messages Previous conversation summary: Ada is planning an 8-day trip with her parents. She's vegetarian, and the group wants flat areas because her parents dislike hills. The assistant has given a placeholder suggestion for each day (spots 1 through 8) without naming any actual places.
Yes, you mentioned that you're vegetarian. You also said your parents dislike hills, so the group prefers flat areas. I'm sorry my earlier replies were just placeholders. I can give you real suggestions for Days 9 and 10 that are flat and have good vegetarian food. Do you know the destination yet?
When a run used tools, result.messages also holds the tool calls and their results. Keep them to give the
model the facts it found; drop them (keep only text) to store a smaller history.
from shipit_agent.tools import FunctionTool
def weather(city: str) -> str:
"""Forecast for a city."""
return {"Lisbon": "24°C, sunny", "Porto": "19°C, cloudy"}.get(city, "no forecast")
result = Agent(llm=haiku, prompt=PROMPT, tools=[FunctionTool.from_callable(weather, read_only=True)]).run(
"What's the weather in Lisbon tomorrow?")
print([m.role for m in result.messages])
full = [m for m in result.messages if m.role != "system"]
text_only = [m for m in full if m.role in ("user", "assistant") and m.content]
follow_up = "Should I pack a jacket for tomorrow?"
print("with tool turns:", Agent(llm=haiku, prompt=PROMPT, history=full).run(follow_up).output[:160])['system', 'user', 'assistant', 'tool', 'assistant']
with tool turns: Probably not. At 24°C and sunny, you won't need a jacket during the day. Lisbon evenings can get breezy, though, so a light layer like a thin sweater or overshi
History is plain messages, so a conversation started on one model can continue on another. Here Haiku 5.5 started it and Gemma 4 picks it up.
saved = [Message(**t) for t in json.loads((WORK / "chat.json").read_text())]
print(Agent(llm=gemma, prompt=PROMPT, history=saved).run("Which city were we talking about, and what month did you suggest?").output)We were talking about Porto, and I suggested May, June, September, or October.
shutil.rmtree(WORK, ignore_errors=True)Agent object keeps its conversation; a new agent per request takes history=[Message(...)].result.messages gives the conversation back; store the user and assistant turns as JSON or rows.chat_session() and session_store + session_id keep history between calls and restarts, per user.ConversationMemory(strategy="summary").