Answering from what you found
Two nodes. One finds the page, the other answers from it. That is the whole shape of a documentation assistant, and you built both kinds of node earlier in this course.
The state
Four keys: what was asked, what was found, which file it came from, and what to say.
from typing_extensions import TypedDict
class State(TypedDict):
question: str
found: str
source: str
answer: strThe finding node
It is the search function from the last lesson, wrapped so it reads from the state and writes back to it. Assume DOCS, words and search are in the file already.
def retrieve(state):
name, body = search(state["question"])
return {"source": name or "", "found": body}The answering node
This is the only genuinely new idea in the lesson. You do not send the model the question on its own. You send it the page as well, and tell it to answer out of that.
from pretend_model import PretendModel
from langchain_core.messages import HumanMessage
model = PretendModel()
def answer(state):
prompt = f"Context: {state['found']}\n\nQuestion: {state['question']}"
return {"answer": model.invoke([HumanMessage(prompt)]).content}Handing over the passage along with the question is the entire idea behind retrieval. The model is not being asked what it knows. It is being asked to read something and answer from it, which is why it can answer about a product it has never heard of.
Wire the two together
from langgraph.graph import StateGraph, START
builder = StateGraph(State)
builder.add_node("retrieve", retrieve)
builder.add_node("answer", answer)
builder.add_edge(START, "retrieve")
builder.add_edge("retrieve", "answer")
graph = builder.compile()Two nodes and one edge between them. That is lesson 4, with a search in the first node and a model in the second.
result = graph.invoke({"question": "How do I reset my password?", "found": "", "source": "", "answer": ""})
print(result["answer"])The answer came out of the page, not out of the model's memory. Our stand-in quotes the passage back rather than rewriting it, and a real model would put it in its own words. What matters is where the words came from, and that does not change with the model.
Now ask it something it cannot know
missed = graph.invoke({"question": "Who won the cricket?", "found": "", "source": "", "answer": ""})
print(repr(missed["answer"]))There is the bug. The search correctly found nothing, the answering node cheerfully handed the model an empty page, and out came an answer that says nothing at all while sounding like it should.
A real model does something worse here. Given an empty passage and a confident instruction, it falls back on whatever it happens to believe and states it as though it came from your documentation. That is the failure people mean when they talk about a model making things up.
- Print
result["found"]and confirm the answer came from that page. - Ask about billing and watch a different file get used.
- Print the prompt before sending it, and read exactly what the model is given.
Slow is fine. Stopping is the only problem.