Chapter 10: RAG and agentic apps with LangGraph and Streamlit
This chapter introduces building a Streamlit customer-service chatbot that uses LangGraph, private data retrieval, and real-world actions to provide business value.
After this chapter you will be able to
- Business-ready AI requirements. Explain why a useful real-world AI application needs more than a call to a pre-trained LLM: it needs operating context, connections to external data, and the ability to take meaningful actions.
- Customer service chatbot scope. Describe the chapter's customer-service chatbot as an application that retrieves real company data, helps customers track and cancel orders, and decides when to escalate problems to a human agent.
- Nibby support bot motivation. Describe the customer-support crisis at Note n’ Nib and explain that Nibby is proposed as a Streamlit bot to automate customer support.
- Nibby support responsibilities. Describe Nibby as a frontline customer support chatbot for Note n’ Nib that holds human-like conversations, answers product questions from a custom knowledge base, handles basic service requests, and redirects unresolved requests to human agents.
- Service request boundaries. State that Nibby’s enabled service requests are tracking an order and canceling an order, while actual order-tracking and cancellation systems are not implemented in this chapter.
- Nibby chat interface flow. Describe Nibby’s UI as a conventional chat or instant-messaging interface, with a text box at the bottom for user messages and a conversation view above. Explain that each user message triggers an AI response that is appended to the conversation.
- Private knowledge augmentation. Explain how Nibby augments a customer message by retrieving relevant context from a private company knowledge store and sending that context to the LLM with the message.
- Tool-based action workflow. Describe how Nibby exposes actions as tools that the LLM knows about, including what each tool does and how to call it, and explain the two possible paths for an input: a direct reply or a tool call followed by processing its result.
- LangGraph and LangChain purpose. Explain why the chapter introduces LangGraph and LangChain: they work together to streamline the creation of more sophisticated AI-driven applications.
- Modern AI application complexity. Describe the kinds of complexity that go beyond a simple LLM response: multi-step workflows such as retrieving information before answering, external-tool integration, and retaining context across conversation turns.
- LangChain and LangGraph roles. Distinguish LangChain from LangGraph: LangChain supplies LLM building blocks such as prompt management, memory, tool integration, and provider abstractions, while LangGraph extends it by organizing workflows as graphs with branching, decisions, and multiple steps.
- LangGraph state transformations. Explain that a LangGraph application is a graph of actions: each node receives the current graph state, modifies it, and returns an updated state. State is a collection of named attributes and values, analogous to Streamlit session state.
- Directed execution flow. Trace how directed edges determine execution order in LangGraph, from the initial state passed to START through nodes to END, producing the final state from the last executed node. Distinguish these directed edges from the undirected friendship edges in the social-network example.
- One-node LLM graph. Describe how a one-node LangGraph application passes a list of messages to an LLM and appends the AI response before reaching END. State that the graph runs from START to an assistant node and then to END.
- LangGraph message state. Explain how a graph state type defines the fields shared by nodes, including a messages list of LangChain message objects. Distinguish the replacement behavior of a basic TypedDict state from the append behavior of MessagesState.
- Single-turn chat frontend. Describe how the Streamlit frontend stores one Bot instance in st.session_state using API keys from st.secrets, obtains submitted text with st.chat_input, and displays the human and AI texts with st.chat_message. State that this first version handles each prompt independently and does not yet retain a conversation.
- Single-turn conversation limitation. Describe why the initial Nibby chatbot fails as a conversational bot: it handles one message and one response at a time, so it does not retain information from prior messages.
- Conversation state persistence. Explain why invoking the original one-node graph with a follow-up message creates an independent execution, and why multi-turn chat requires persisting graph state between invocations.
- MemorySaver checkpointer setup. Describe how compiling the LangGraph with LangGraph’s MemorySaver checkpointer stores graph state in memory at graph steps, enabling a later invocation to resume saved state.
- Thread ID conversation retrieval. Trace how passing the same thread ID in the invocation configuration causes LangGraph to retrieve that thread’s saved state and append a follow-up message to it rather than begin from a clean slate.
- Conversation history backend access. Trace how the full conversation is exposed from the graph to the bot layer. Explain that SupportAgentGraph.get_conversation retrieves the current state for the configured conversation thread and returns its messages list, while bot.get_history passes that list through to its caller.
- Streamlit history rendering. Describe how frontend.py displays the complete conversation history. Explain that it calls bot.get_history(), iterates over the returned messages, and renders each message's content in its own st.chat_message container using the message type as the chat-message role.
- Cost of frivolous requests. Explain why Nibby should stay focused on customer support: every cloud-based LLM interaction consumes tokens and costs the company money, so long responses to frivolous requests can add unnecessary expense.
- Base system prompt purpose. Explain how the BASE_SYS_MSG prompt establishes that Note n’ Nib exists and that Nibby is its customer-support bot. Describe that this prompt supplies the LLM with use-case context before the graph incorporates it.
- Out-of-scope request refusal. Describe the instruction in BASE_SYS_MSG that makes Nibby refuse requests unrelated to Note n’ Nib. Explain that this restriction is intended to keep the bot focused on customer support rather than acting as a general-purpose assistant.
- Base context graph flow. Describe how the graph is changed to run a base-context node between START and the assistant node. Explain that this node places the base system prompt into state before the assistant calls the LLM.
- System message prompt ordering. Explain how assistant_node constructs the LLM input by creating a SystemMessage from state['sys_msg_text'] and prepending it to state['messages']. State that putting this message first makes the base prompt available to the LLM and causes Nibby to reject irrelevant requests.
- Limits of pretrained knowledge. Explain that models such as GPT-5.1 are pre-trained on large amounts of publicly available material, which lets them handle broad topics but does not give them access to an organization’s private information.
- Nibby hallucination risk. Describe why Nibby’s fountain-pen answer is a hallucination: it was given no information about Note n’ Nib’s pen inventory, so it invented details such as brands rather than answering from known facts.
- RAG purpose and workflow. Explain how Retrieval Augmented Generation supplements an LLM with custom knowledge by retrieving relevant context for a user question and supplying that context with the question to the LLM. State the essential sequence: read the question, retrieve relevant knowledge-base context, augment the question with that context, and generate an answer.
- Selective context rationale. Justify why supplying all custom information in every prompt is usually a poor solution even when a model has a large context window. Describe both information overload degrading LLM performance and token-based costs increasing when large knowledge bases are repeatedly included.
- Embeddings and vector retrieval. Describe embeddings as numerical vectors that represent the meaning of text, so semantically similar text has embeddings that are close together. Explain that a vector database stores knowledge-base embeddings and retrieves entries near a user-question embedding, using similarity calculations such as cosine similarity.
- Pinecone knowledge base setup. Describe how the Note n’ Nib support articles become a searchable Pinecone knowledge base, including the index configuration and the information stored in it.
- Document ingestion pipeline. Trace how VectorStore.ingest_folder loads text files, splits each article into overlapping chunks, generates embeddings, and adds the chunks to Pinecone in a one-time offline ingestion step.
- RAG graph node flow. Describe the RAG path through the SupportAgentGraph: retrieve forms a query from conversation messages and saves relevant chunks in retrieved_docs, then augment inserts their content into the system prompt before the assistant responds.
- Agentic customer support. Explain why Nibby needs agentic capabilities for customer support rather than relying only on informational answers from its knowledge base. Describe that customers often need help with their own situation or order, such as checking a delayed order or cancelling it.
- Agent real-world interaction. Describe an agent as an AI application that can interact with the real world by connecting to company systems to retrieve the right information or take an appropriate action. Distinguish this from a purely informational bot that relies on static text.
- Agentic app capabilities. Explain how an AI agent, or agentic app, differs from a traditional question-answer chatbot. Describe that an agent can reason, plan, and interact with external systems to complete a user task rather than only provide information.
- Tool-enabled task execution. Describe tool use as an agent's ability to call APIs, run database queries, or trigger real-world workflows in order to act on information. Explain this with a customer-support task such as fetching order tracking details or processing a cancellation.
- ReAct reasoning-action cycle. Explain the ReAct framework as an agent behavior pattern that interleaves reasoning with concrete actions. Describe how the agent reasons about a query, acts to obtain data or perform a task, incorporates the result into further reasoning, repeats if needed, and responds when no further steps are necessary.
- Agent tool concept. Explain what a tool means for a ReAct AI agent: a function or API the agent can call to perform a real-world action.
- Nibby customer-service tools. Describe the four tools Nibby can call and the information each requires: retrieve_user_id, get_order_id, get_order_status, and cancel_order.
- Tool metadata and export list. Explain why tool functions use type hints and detailed docstrings, and state that tools.py exports the callable functions as a list named tools for later use in the graph.
- Tool-aware LLM responses. Explain how binding the tools from tools.py to the LLM enables it to produce AIMessage tool calls with the parameters needed to use available tools. Describe how tool docstrings and type hints tell the LLM what each tool does and how to call it.
- Conditional agent graph loop. Trace the agentic graph flow: the assistant response goes to tools when tools_condition finds tool calls, otherwise it goes to END; ToolNode executes the calls, appends ToolMessages, and routes back to assistant. Explain that this loop lets the LLM use a tool result to decide whether to call another tool, reply to the customer, or request more user information.
What it builds on
- Calling the OpenAI API in Python (chapter 9) · Calling the OpenAI API in Python as the basic pre-trained-model capability that this chapter extends.
- Apps that use Generative AI like LLMs (chapter 1) · Streamlit can be used to build applications that use generative AI such as LLMs.
- Using AI to generate trivia questions (chapter 9) · Python applications can call an LLM to provide AI-driven behavior.
- Defining what the app won’t do (chapter 3) · Defining what an app will not do establishes the practice of managing scope.
- Implementing multiple questions (chapter 9) · The idea that an AI application can handle multiple questions rather than one isolated response.
- Writing an LLM class (chapter 9) · Encapsulating LLM interactions in a Python class.
- st.session_state (chapter 4) · The comparison to st.session_state assumes the idea of named values that persist across Streamlit reruns.
- Structured Outputs (chapter 9) · The roles of user, assistant, and system messages in an LLM interaction.
Section by section
Introduction
This chapter introduces building a Streamlit customer-service chatbot that uses LangGraph, private data retrieval, and real-world actions to provide business value.
- Business-ready AI requirements: Explain why a useful real-world AI application needs more than a call to a pre-trained LLM: it needs operating context, connections to external data, and the ability to take meaningful actions.
Why it matters: These capabilities let AI support domain-specific work instead of only producing generic answers. - Customer service chatbot scope: Describe the chapter's customer-service chatbot as an application that retrieves real company data, helps customers track and cancel orders, and decides when to escalate problems to a human agent.
Why it matters: This concrete bot shows how private knowledge, business actions, and human escalation fit together in an AI application. - Chapter technology roadmap: State that the chapter will use Streamlit chat elements for the frontend, LangGraph and LangChain to streamline the advanced AI application, embeddings and vector databases for retrieval, RAG to augment LLM knowledge, and agent capabilities for real-world actions.
Why it matters: The roadmap connects each technology to a distinct part of the customer-service application.
Key terms: pre-trained model, external data sources, domain-specific queries, business systems, personalized assistance, customer service chatbot, company data, track orders, cancel orders, human agent
10.1 Nibby: A customer service bot
This section introduces the fictional customer-support problem that motivates building Nibby, a Streamlit-based support bot.
- Nibby support bot motivation: Describe the customer-support crisis at Note n’ Nib and explain that Nibby is proposed as a Streamlit bot to automate customer support.
Why it matters: This business problem provides the purpose for every feature added to Nibby in the rest of the chapter.
Key terms: Nibby, Streamlit support bot, customer support
10.1.1 Stating the concept and requirements
Defines Nibby’s customer-support role, required capabilities, deliberate scope limits, and the learning focus of its toy service actions.
- Nibby support responsibilities: Describe Nibby as a frontline customer support chatbot for Note n’ Nib that holds human-like conversations, answers product questions from a custom knowledge base, handles basic service requests, and redirects unresolved requests to human agents.
Why it matters: These responsibilities define what the chatbot is expected to handle before escalating to a person. - Service request boundaries: State that Nibby’s enabled service requests are tracking an order and canceling an order, while actual order-tracking and cancellation systems are not implemented in this chapter.
Why it matters: The narrow request set keeps the example focused on how an AI app identifies and triggers an action rather than on building business systems. - Project scope and learning focus: Distinguish the project’s intentional exclusions—remembering prior user conversations and actions beyond tracking or canceling orders—from its learning focus: a complex AI system that converses, uses custom knowledge, and intelligently chooses an action from free-form conversation.
Why it matters: Knowing what is out of scope prevents confusing this focused learning example with a complete production customer-support platform.
Key terms: Nibby, customer support chatbot, custom knowledge base, frontline agent, human customer support agent, tracking an order, canceling an order, toy logic, real-world actions, out of scope
10.1.2 Visualizing the user experience
This subsection sketches Nibby as a familiar chat interface where user messages produce AI replies in an accumulating conversation view.
- Nibby chat interface flow: Describe Nibby’s UI as a conventional chat or instant-messaging interface, with a text box at the bottom for user messages and a conversation view above. Explain that each user message triggers an AI response that is appended to the conversation.
Why it matters: This interaction flow defines the frontend behavior the chatbot must implement.
Key terms: text box, AI response, conversation view, chatbot
10.1.3 Brainstorming the implementation
This section outlines Nibby’s backend design for grounding customer-support answers in private knowledge and letting the bot execute real-world actions through tools.
- Private knowledge augmentation: Explain how Nibby augments a customer message by retrieving relevant context from a private company knowledge store and sending that context to the LLM with the message.
Why it matters: This gives the bot access to company knowledge that a general-purpose LLM would not otherwise have. - Tool-based action workflow: Describe how Nibby exposes actions as tools that the LLM knows about, including what each tool does and how to call it, and explain the two possible paths for an input: a direct reply or a tool call followed by processing its result.
Why it matters: Tools allow the bot to do things in the real world rather than only generate text. - Customer support backend complexity: Distinguish the simple prompt-to-response intelligence in the earlier trivia app from Nibby’s more involved backend, which must both use private knowledge and connect to outside actions.
Why it matters: This distinction explains why a customer-support bot needs architecture beyond a single LLM prompt.
Key terms: private knowledge store, vector database, context, LLM, tools, tool call, conversation view, backend, private knowledge
10.1.4 Installing dependencies
10.2 Creating a basic chatbot
This section introduces LangGraph and LangChain as libraries for reducing the complexity of sophisticated AI applications beyond simple OpenAI API integrations.
- LangGraph and LangChain purpose: Explain why the chapter introduces LangGraph and LangChain: they work together to streamline the creation of more sophisticated AI-driven applications.
Why it matters: These libraries provide the foundation for the chapter's later RAG and agent-based chatbot work. - Complex AI application challenges: Describe the contrast between simple OpenAI API applications and more sophisticated AI-driven applications, including RAG and agent-based workflows, which add development complexity.
Why it matters: Recognizing this complexity explains why an ecosystem of higher-level AI development tools has emerged.
Key terms: LangGraph, LangChain, AI-driven applications, OpenAI API, Retrieval-Augmented Generation, RAG, agent-based workflows
10.2.1 Introduction to LangGraph and LangChain
This section introduces LangChain and LangGraph as Python libraries for managing the multi-step, stateful, tool-using workflows needed in modern LLM applications.
- LLM prompt interaction: Explain that the core interaction with an LLM is giving it a prompt for the model to complete, and that more advanced application behavior is built around this interaction.
Why it matters: This establishes the simple LLM interaction that LangChain and LangGraph extend into richer application workflows. - Modern AI application complexity: Describe the kinds of complexity that go beyond a simple LLM response: multi-step workflows such as retrieving information before answering, external-tool integration, and retaining context across conversation turns.
Why it matters: These needs explain why an application benefits from workflow libraries instead of manually coordinating every LLM step. - LangChain and LangGraph roles: Distinguish LangChain from LangGraph: LangChain supplies LLM building blocks such as prompt management, memory, tool integration, and provider abstractions, while LangGraph extends it by organizing workflows as graphs with branching, decisions, and multiple steps.
Why it matters: Knowing the division of responsibilities helps explain why Nibby uses LangGraph while still importing many underlying LangChain classes and functions.
Key terms: LLM, prompt, multi-step workflows, external tools, conversation context, LangChain, LangGraph, prompt management, memory, tool integration
10.2.2 Graphs, nodes, edges, and state
This section explains how LangGraph models an application as a directed graph whose nodes transform shared state from an initial input to a final output.
- Graph structure and terminology: Describe a graph as interconnected vertices or nodes joined by edges, and explain how this model can represent real-world relationships such as friendships in a social network.
Why it matters: This vocabulary provides the foundation for understanding how LangGraph represents an application's flow. - LangGraph state transformations: Explain that a LangGraph application is a graph of actions: each node receives the current graph state, modifies it, and returns an updated state. State is a collection of named attributes and values, analogous to Streamlit session state.
Why it matters: The state passed and transformed by nodes is how data moves through a LangGraph application. - Directed execution flow: Trace how directed edges determine execution order in LangGraph, from the initial state passed to START through nodes to END, producing the final state from the last executed node. Distinguish these directed edges from the undirected friendship edges in the social-network example.
Why it matters: Following directed edges lets you predict which action runs next and how one node's output becomes another's input. - Sum-of-squares graph trace: Trace the toy sum-of-squares graph for input [3, 4]: squaring_node stores [9, 16] in squares, then summing_node stores 25 in answer, following START to squaring_node to summing_node to END.
Why it matters: This concrete example shows exactly how state changes propagate across a simple LangGraph path.
Key terms: graph, vertices, nodes, edges, graph theory, LangGraph, state, graph state, node, st.session_state
10.2.3 A one-node LLM graph
This section builds Nibby’s first single-turn chatbot by implementing a one-node LangGraph LLM graph, wrapping it in backend classes, and connecting it to a Streamlit chat interface.
- One-node LLM graph: Describe how a one-node LangGraph application passes a list of messages to an LLM and appends the AI response before reaching END. State that the graph runs from START to an assistant node and then to END.
Why it matters: This is the smallest working graph pattern that the later conversational and agentic versions extend. - LangGraph message state: Explain how a graph state type defines the fields shared by nodes, including a messages list of LangChain message objects. Distinguish the replacement behavior of a basic TypedDict state from the append behavior of MessagesState.
Why it matters: Understanding how state updates merge prevents earlier conversation messages from being accidentally discarded. - Graph class encapsulation: Describe how SupportAgentGraph encapsulates graph construction and invocation while receiving its LLM as a constructor dependency. Explain why get_assistant_node returns an inner assistant_node function that closes over self.llm instead of making the node a normal instance method.
Why it matters: This design keeps graph details organized while satisfying LangGraph’s requirement that a node accept graph state as its argument. - Bot backend interface: Explain how the Bot class acts as the Streamlit frontend’s single backend entry point by creating a ChatOpenAI LLM, supplying it to SupportAgentGraph, and exposing chat as a string-in, string-out method. State that max_tokens=2000 limits response length to help control cost and concision.
Why it matters: A simple backend interface lets the frontend chat without needing to manage LangGraph state or LLM details. - Single-turn chat frontend: Describe how the Streamlit frontend stores one Bot instance in st.session_state using API keys from st.secrets, obtains submitted text with st.chat_input, and displays the human and AI texts with st.chat_message. State that this first version handles each prompt independently and does not yet retain a conversation.
Why it matters: This connects the backend graph to a usable chat UI while making clear why conversation memory is the next enhancement.
Key terms: StateGraph, messages, assistant_node, START, END, invoke, TypedDict, MyGraphState, MessagesState, AnyMessage
10.3 Multi-turn conversations
This section introduces the limitation of Nibby’s single-turn chatbot and frames the work needed to support remembered multi-turn conversations.
- Single-turn conversation limitation: Describe why the initial Nibby chatbot fails as a conversational bot: it handles one message and one response at a time, so it does not retain information from prior messages.
Why it matters: Remembering earlier messages is necessary for a chatbot to respond coherently across a real conversation.
Key terms: multi-turn conversations, conversation history, frontend
10.3.1 Adding memory to the graph
This section adds in-memory conversational memory to Nibby’s LangGraph so repeated invocations in one Streamlit session retain prior messages.
- Conversation state persistence: Explain why invoking the original one-node graph with a follow-up message creates an independent execution, and why multi-turn chat requires persisting graph state between invocations.
Why it matters: Without persisted state, the LLM receives only the latest message and cannot use earlier conversation context. - MemorySaver checkpointer setup: Describe how compiling the LangGraph with LangGraph’s MemorySaver checkpointer stores graph state in memory at graph steps, enabling a later invocation to resume saved state.
Why it matters: The checkpointer is the component that makes prior graph state available instead of discarding it after each call. - Thread ID conversation retrieval: Trace how passing the same thread ID in the invocation configuration causes LangGraph to retrieve that thread’s saved state and append a follow-up message to it rather than begin from a clean slate.
Why it matters: A consistent thread ID connects each user turn to the correct saved conversation state. - Single-session thread rationale: Justify why this app can always use thread ID "1": a browser refresh or new tab rebuilds the SupportAgentGraph and creates a new in-memory MemorySaver, so no thread from a prior browser session is available to that graph instance.
Why it matters: This explains why a fixed ID is safe in this example while also clarifying the limits of in-memory conversation storage.
Key terms: state, graph invocation, multi-turn conversations, checkpointer, MemorySaver, compile, graph state, thread ID, self.config, configurable
10.3.2 Displaying the conversation history
This section exposes a LangGraph conversation's stored messages through the backend and renders every message in the Streamlit chat interface.
- Conversation history backend access: Trace how the full conversation is exposed from the graph to the bot layer. Explain that SupportAgentGraph.get_conversation retrieves the current state for the configured conversation thread and returns its messages list, while bot.get_history passes that list through to its caller.
Why it matters: The frontend can display prior turns only after the backend makes the graph's stored message history available. - Streamlit history rendering: Describe how frontend.py displays the complete conversation history. Explain that it calls bot.get_history(), iterates over the returned messages, and renders each message's content in its own st.chat_message container using the message type as the chat-message role.
Why it matters: Rendering every stored turn lets users see the full conversation rather than only the newest question and response.
Key terms: SupportAgentGraph, get_conversation, get_history, self.graph.get_state(self.config), messages, conversation thread, bot.get_history(), st.chat_message, HumanMessage, AIMessage
10.4 Restricting the bot to customer support
This section motivates restricting Nibby from a general chatbot to a strictly customer-support-focused bot so frivolous LLM requests do not create unnecessary costs.
- General chatbot behavior: Describe how Nibby behaves before customer-support restrictions: it can answer requests beyond support, such as singing, solving math problems, or writing an essay.
Why it matters: Recognizing the bot's unrestricted behavior clarifies the problem that the following prompt and graph changes solve. - Cost of frivolous requests: Explain why Nibby should stay focused on customer support: every cloud-based LLM interaction consumes tokens and costs the company money, so long responses to frivolous requests can add unnecessary expense.
Why it matters: Keeping the bot on task helps prevent the company from paying for irrelevant LLM usage.
Key terms: general chatbot, customer support bot, Nibby, cloud-based LLM, LLM tokens, frivolous request, strictly business
10.4.1 Creating a base prompt
This subsection defines a reusable base system prompt that tells Nibby its customer-support role for Note n’ Nib and makes it refuse unrelated requests.
- Base system prompt purpose: Explain how the BASE_SYS_MSG prompt establishes that Note n’ Nib exists and that Nibby is its customer-support bot. Describe that this prompt supplies the LLM with use-case context before the graph incorporates it.
Why it matters: The base prompt gives the chatbot a defined identity and job instead of leaving its behavior completely generic. - Out-of-scope request refusal: Describe the instruction in BASE_SYS_MSG that makes Nibby refuse requests unrelated to Note n’ Nib. Explain that this restriction is intended to keep the bot focused on customer support rather than acting as a general-purpose assistant.
Why it matters: Restricting unrelated requests helps keep the bot aligned with the support task it was built to perform. - Prompt module organization: State that the base prompt is placed in a file named prompts.py as BASE_SYS_MSG, following the provided listing. Describe this as storing the prompt separately so it can be imported when the graph is updated next.
Why it matters: Keeping the prompt in its own module makes the graph code able to reuse a clearly named prompt definition.
Key terms: BASE_SYS_MSG, base prompt, system message, Note n’ Nib, Nibby, refuse, unrelated requests, customer support, prompts.py, Listing 10.6
10.4.2 Inserting a base context node in the graph
This subsection adds a base-context node to Nibby’s LangGraph so a system prompt is stored in graph state and sent before conversation messages, keeping the bot focused on customer support.
- Base context graph flow: Describe how the graph is changed to run a base-context node between START and the assistant node. Explain that this node places the base system prompt into state before the assistant calls the LLM.
Why it matters: Running the context step first ensures the assistant has its customer-support instructions for every response. - System message prompt ordering: Explain how assistant_node constructs the LLM input by creating a SystemMessage from state['sys_msg_text'] and prepending it to state['messages']. State that putting this message first makes the base prompt available to the LLM and causes Nibby to reject irrelevant requests.
Why it matters: Message order is how the app supplies the model with the behavioral constraint that keeps Nibby on task. - Extended graph state behavior: Describe the AgentState extension of MessagesState with a sys_msg_text field, and distinguish its replacement update behavior from the append behavior of messages. Explain that a node returns only the state keys it changes, so base_context_node can set sys_msg_text without removing messages.
Why it matters: Understanding partial state updates prevents mistaken assumptions that a node must return or preserve the entire conversation state itself. - Static method node choice: Justify making base_context_node a static method inside SupportAgentGraph rather than a regular method or closure. Explain that graph nodes need state as their first argument, and this node needs no self access, unlike assistant_node, which needs self.llm and therefore uses a closure.
Why it matters: Choosing the simplest node form that has the dependencies it needs keeps graph code organized and avoids a conflicting self parameter.
Key terms: base_context_node, base_context, START, assistant, BASE_SYS_MSG, StateGraph, assistant_node, SystemMessage, sys_msg_text, messages
10.5 Retrieval Augmented Generation
This section motivates Retrieval Augmented Generation by showing why Nibby needs private, custom knowledge rather than relying only on an LLM’s public pre-training.
- Limits of pretrained knowledge: Explain that models such as GPT-5.1 are pre-trained on large amounts of publicly available material, which lets them handle broad topics but does not give them access to an organization’s private information.
Why it matters: A useful business AI app often needs knowledge that was never part of the model’s public training data. - Nibby hallucination risk: Describe why Nibby’s fountain-pen answer is a hallucination: it was given no information about Note n’ Nib’s pen inventory, so it invented details such as brands rather than answering from known facts.
Why it matters: Recognizing unsupported, invented answers is essential before relying on a customer-service bot. - Custom knowledge augmentation: State that the coming approach augments Nibby’s existing general knowledge with custom information supplied for the specific use case, so it can better serve Note n’ Nib customers.
Why it matters: Adding relevant custom knowledge makes an AI application fit the real organization it serves.
Key terms: pre-trained, publicly available information, private information, GPT-5.1, hallucinated, hallucination, Note n’ Nib, InkStream, RoyalQuill, augment
10.5.1 What is Retrieval Augmented Generation?
This section explains why Retrieval Augmented Generation selectively retrieves relevant custom knowledge for an LLM and introduces embeddings and vector databases as the retrieval mechanism.
- RAG purpose and workflow: Explain how Retrieval Augmented Generation supplements an LLM with custom knowledge by retrieving relevant context for a user question and supplying that context with the question to the LLM. State the essential sequence: read the question, retrieve relevant knowledge-base context, augment the question with that context, and generate an answer.
Why it matters: RAG lets a bot answer from your organization’s information without putting the entire knowledge base into every prompt. - Selective context rationale: Justify why supplying all custom information in every prompt is usually a poor solution even when a model has a large context window. Describe both information overload degrading LLM performance and token-based costs increasing when large knowledge bases are repeatedly included.
Why it matters: Selective retrieval keeps answers focused while avoiding the performance and cost problems of enormous prompts. - Embeddings and vector retrieval: Describe embeddings as numerical vectors that represent the meaning of text, so semantically similar text has embeddings that are close together. Explain that a vector database stores knowledge-base embeddings and retrieves entries near a user-question embedding, using similarity calculations such as cosine similarity.
Why it matters: Embeddings and vector search provide the practical way for RAG to identify the small pieces of knowledge that match a question.
Key terms: Retrieval Augmented Generation, RAG, knowledge base, context, retrieve, augment, context window length, tokens, information overload, custom knowledge base
10.5.2 Implementing RAG in our app
This section implements RAG for Nibby by ingesting support articles into Pinecone and adding retrieval and prompt augmentation nodes to its LangGraph workflow.
- Pinecone knowledge base setup: Describe how the Note n’ Nib support articles become a searchable Pinecone knowledge base, including the index configuration and the information stored in it.
Why it matters: A RAG bot can only retrieve useful support information after its source documents have been embedded and stored in a vector index. - Document ingestion pipeline: Trace how VectorStore.ingest_folder loads text files, splits each article into overlapping chunks, generates embeddings, and adds the chunks to Pinecone in a one-time offline ingestion step.
Why it matters: Chunking and ingestion make a large support knowledge base searchable without placing entire articles into every LLM prompt. - VectorStore retrieval interface: Explain that VectorStore connects Pinecone, OpenAI embeddings, and the chosen index, and that its retrieve method uses similarity_search to return Document chunks relevant to a query.
Why it matters: This wrapper gives the graph a simple way to request relevant knowledge-base content without embedding database details in graph logic. - RAG graph node flow: Describe the RAG path through the SupportAgentGraph: retrieve forms a query from conversation messages and saves relevant chunks in retrieved_docs, then augment inserts their content into the system prompt before the assistant responds.
Why it matters: The retrieve-and-augment sequence is what supplies grounded support context to the LLM at answer time. - Dependency injection and app wiring: Explain how the app wires RAG dependencies together: Bot creates VectorStore from secrets-based configuration, passes it into SupportAgentGraph, and the Streamlit frontend passes config when constructing Bot.
Why it matters: Passing dependencies into the graph keeps graph logic focused and makes it easier to test.
Key terms: Pinecone, index, vector database, embeddings, text-embedding-3-small, dimension, VectorStore, ingest_folder, DirectoryLoader, TextLoader
10.6 Turning the bot into an agent
This section explains why Nibby must become an agent to handle customer-specific requests that require accessing company systems or taking actions, not merely providing information.
- Agentic customer support: Explain why Nibby needs agentic capabilities for customer support rather than relying only on informational answers from its knowledge base. Describe that customers often need help with their own situation or order, such as checking a delayed order or cancelling it.
Why it matters: An agent can help resolve a customer’s actual situation instead of only describing general support information. - Agent real-world interaction: Describe an agent as an AI application that can interact with the real world by connecting to company systems to retrieve the right information or take an appropriate action. Distinguish this from a purely informational bot that relies on static text.
Why it matters: This distinction identifies when an AI app needs system access and actions rather than just generated answers.
Key terms: agent, customer support knowledge base, static textual articles, agents, AI applications, interact with the real world, retrieve the right information, take the appropriate action
10.6.1 What are agents?
This section distinguishes action-taking AI agents from informational chatbots and introduces ReAct as a framework for dynamic reasoning and action.
- Agentic app capabilities: Explain how an AI agent, or agentic app, differs from a traditional question-answer chatbot. Describe that an agent can reason, plan, and interact with external systems to complete a user task rather than only provide information.
Why it matters: This distinction explains why an agent can provide personalized help that a static informational bot cannot. - Tool-enabled task execution: Describe tool use as an agent's ability to call APIs, run database queries, or trigger real-world workflows in order to act on information. Explain this with a customer-support task such as fetching order tracking details or processing a cancellation.
Why it matters: Tools let an agent interact with the systems people already use, turning a response into practical assistance. - ReAct framework purpose: State that ReAct means Reasoning + Acting and describe its purpose as enabling an agent to reason and decide its next steps dynamically.
Why it matters: ReAct provides the decision-making approach that connects an agent's reasoning to its actions.
Key terms: AI agents, agentic apps, traditional AI chatbots, reason, plan, external systems, tool use, APIs, database queries, workflows
10.6.2 The ReAct framework
This section explains how the ReAct framework makes an AI agent alternate between reasoning and actions until it can give the user a final response.
- ReAct reasoning-action cycle: Explain the ReAct framework as an agent behavior pattern that interleaves reasoning with concrete actions. Describe how the agent reasons about a query, acts to obtain data or perform a task, incorporates the result into further reasoning, repeats if needed, and responds when no further steps are necessary.
Why it matters: This cycle is what lets an agent do more than retrieve facts by deciding and carrying out the steps needed to answer a user. - Order-status ReAct example: Trace how Nibby could handle an order-status question with ReAct: identify the need for order details, call Note n’ Nib’s order management system, interpret the shipped and in-transit result, and give the customer tracking details and an estimated delivery date.
Why it matters: The order-status scenario shows how abstract reasoning and tool actions become a useful customer-service response.
Key terms: ReAct framework, Reason, Act, Respond, Nibby, Note n’ Nib, order management system, order status
10.6.3 Creating tools an agent can use
This section supplies Nibby with mock order data and four well-described callable tools so an agent can identify customers, inspect orders, and cancel them.
- Agent tool concept: Explain what a tool means for a ReAct AI agent: a function or API the agent can call to perform a real-world action.
Why it matters: Tools let an agent do more than generate text by connecting it to actions and external information. - Nibby customer-service tools: Describe the four tools Nibby can call and the information each requires: retrieve_user_id, get_order_id, get_order_status, and cancel_order.
Why it matters: The agent must combine these narrow operations to track or cancel a customer's order. - Tool metadata and export list: Explain why tool functions use type hints and detailed docstrings, and state that tools.py exports the callable functions as a list named tools for later use in the graph.
Why it matters: The model relies on function metadata to decide which tool to call and how to supply its inputs. - Static customer order data: Describe the role of database.py as mock data containing users and orders dictionaries keyed by user ID and order ID, and distinguish it from a real production database.
Why it matters: Static example data keeps the example focused on agent mechanics rather than database implementation.
Key terms: tool, ReAct AI agent, function, API, retrieve_user_id, get_order_id, get_order_status, cancel_order, user ID, order ID
10.6.4 Making our graph agentic
This section turns Nibby into an agent by wiring LangGraph tool execution, conditional routing, and internal-message filtering into its conversation graph.
- Tool-aware LLM responses: Explain how binding the tools from tools.py to the LLM enables it to produce AIMessage tool calls with the parameters needed to use available tools. Describe how tool docstrings and type hints tell the LLM what each tool does and how to call it.
Why it matters: An agent can only select useful actions when the LLM knows which tools exist and what inputs they require. - Conditional agent graph loop: Trace the agentic graph flow: the assistant response goes to tools when tools_condition finds tool calls, otherwise it goes to END; ToolNode executes the calls, appends ToolMessages, and routes back to assistant. Explain that this loop lets the LLM use a tool result to decide whether to call another tool, reply to the customer, or request more user information.
Why it matters: The conditional loop is what lets Nibby alternate between reasoning, acting through tools, and conversing until it can provide a final response. - Internal message filtering: Describe why get_conversation filters internal AIMessage tool calls and ToolMessages out of the history returned to the Streamlit frontend. State that is_internal_message identifies messages as internal when their type is "tool" or when they have a tool_calls property in additional_kwargs, leaving only customer-facing conversation messages.
Why it matters: Filtering preserves a clean customer chat experience while the graph still retains its tool-execution messages internally.
Key terms: bind_tools, AIMessage, tool_calls, docstrings, type hints, ToolNode, tools_condition, conditional edge, assistant, tools
10.7 Summary
This summary recaps how Nibby combines Streamlit chat UI, LangGraph workflows and memory, RAG knowledge retrieval, and agent tools to create an advanced customer-service bot.
Where this is used later
- Go forth and develop! (chapter 12) · The Nibby customer-service bot
- Testing the application (chapter 11) · Testing the app requires understanding that LLM responses may request tool calls.
- Testing the integration between components (chapter 11) · Integration testing can exercise the interaction among assistant responses, routing, and tool execution.
- Unit testing the Streamlit frontend (chapter 11) · Frontend tests can verify that the customer-facing conversation excludes internal graph messages.
Concepts explained in this chapter
- LangGraphLangGraph is a framework for building stateful applications as workflows composed of interconnected graph nodes, including applications driven by language models.
- stateA graph state is the structured mutable data associated with a graph execution, including the messages for a conversation thread.
- language modelAn LLM is a large language model that generates or transforms text from input sequences.
- promptA prompt is an input supplied to a language model, typically consisting of one or more conversational messages.
- vector databaseA vector database is a data-storage system optimized for storing, indexing, and searching numerical vector representations by similarity.
- knowledge baseA knowledge base is an organized collection of information that a system can consult to answer questions about a defined subject.
- embeddingA text embedding is a numerical vector representation of text that captures features useful for semantic comparison.
- retrieval-augmented generationRetrieval-augmented generation is a technique that retrieves relevant external information and supplies it to a language model when generating a response.
- MessagesStateMessagesState is a built-in LangGraph state class whose messages updates are appended to the existing message list.
- chat_messagest.chat_message is a Streamlit display widget that renders a conversational message container styled for a specified speaker role.
- cosine similarityCosine similarity is a metric that compares vectors by measuring the cosine of the angle between them.
- chat_inputst.chat_input is a Streamlit widget that collects a conversational message through a bottom-pinned text input and send control.
- ChatOpenAIChatOpenAI is a LangChain class that provides an interface to OpenAI chat language models.
- StateGraphStateGraph is a LangGraph graph-builder API for defining nodes, state, and transitions in a stateful computation graph.
- AI agentAn AI agent is a software system that uses artificial intelligence to interpret context, choose actions, and carry out tasks toward a goal.
- toolA tool is a callable software interface that an application or language model can invoke with structured parameters to perform an operation or retrieve information.
- DirectoryLoaderDirectoryLoader is a LangChain loader that discovers and loads files from a specified directory.
- DocumentA Document is a LangChain data structure containing source text and associated metadata.
- PineconePinecone is a managed vector database service optimized for scalable similarity search over vector representations.
- RecursiveCharacterTextSplitterRecursiveCharacterTextSplitter is a LangChain utility that divides documents into overlapping text chunks while attempting to preserve meaningful context.
- dependency injectionDependency injection is a software design pattern in which an object receives the components it depends on from outside rather than constructing them internally.
- document loaderA document loader is a component that reads source data and converts it into structured documents for downstream processing.
- text-embedding-3-smalltext-embedding-3-small is an OpenAI model that converts text into numerical vector representations.
- vector indexA vector index is a searchable Pinecone collection that stores text records, metadata, and their vector embeddings.
- tool usageTool use is the capability of an AI system to invoke external software functions or services while solving a task.
- nodeA graph node is a vertex in a graph that represents an entity, state, or executable step in a workflow.
- ToolNodeToolNode is a LangGraph prebuilt node that executes tool calls from the latest message and appends the resulting tool messages to graph state.
- tools_conditiontools_condition is a LangGraph routing function that sends execution to a tool node when the latest language-model message contains tool calls and otherwise sends execution to END.
- conditional edgeA conditional edge is a graph transition whose next node is selected by evaluating a specified condition.
- graphA graph is a network of vertices or nodes connected by edges, and in LangGraph it models application execution and relationships between actions.
- context windowContext window length is the maximum number of tokens that a language model can process as input in a single prompt.
- vectorizationA text embedding model is a machine-learning model that converts text into numerical vectors representing its semantic content.
- memoryApplication memory is the transient information retained by a running application while it executes.
- checkpointerA checkpointer is a component that saves snapshots of graph state during execution so that later invocations can resume or recall prior state.
- ReActA ReAct agent is an AI agent pattern that interleaves reasoning about a task with actions performed through callable tools.
- edgeA graph edge is a connection between two graph nodes that represents a relationship or execution transition.
- InMemorySaverMemorySaver is a LangGraph checkpointer that stores graph-state snapshots in process memory.
- thread idA thread ID is an identifier that associates multiple graph invocations with the same persisted conversation state.
- conversational aiA chatbot is a software application that conducts conversational interactions with users through natural-language input and output.
- type hintsA type hint is Python syntax that annotates the expected types of function parameters, return values, or other expressions.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
