LangGraphLangGraph 1.2 · Python 3.10+
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
37 small wins to finish your pathNext lesson

When it does not know

An assistant that answers everything is worse than one that answers most things and says so about the rest. The fix is a conditional edge from lesson 6.

Everything from the last lesson stays. Assume DOCS, search, the state and the two nodes are already in the file.

Ask one question about what was found

python
def did_we_find_anything(state):
    return "answer" if state["found"] else "say_no"

It is the same shape as pick_team in lesson 6. Look at the state, return the name of the node that should run next.

Somewhere to go when the answer is no

python
def say_no(state):
    return {"answer": "I could not find anything about that in the documentation."}

No model call. There is nothing to answer from, so there is nothing to ask a model about, and a fixed sentence is both cheaper and more honest than anything it would produce.

Say where the answer came from

While you are here, put the file name on the answer. A documentation assistant that cannot tell you which page it read is asking to be trusted for no reason.

python
from pretend_model import PretendModel
from langchain_core.messages import HumanMessage

def answer(state):
    prompt = f"Context: {state['found']}\n\nQuestion: {state['question']}"
    reply = PretendModel().invoke([HumanMessage(prompt)]).content
    return {"answer": f"{reply}\n\nSource: {state['source']}"}

Rewire it with the branch

python
from langgraph.graph import StateGraph, START

builder = StateGraph(State)
builder.add_node("retrieve", retrieve)
builder.add_node("answer", answer)
builder.add_node("say_no", say_no)
builder.add_edge(START, "retrieve")
builder.add_conditional_edges("retrieve", did_we_find_anything, ["answer", "say_no"])
graph = builder.compile()

One edge became a branch, and the graph now has two ways to finish. Nothing else about it changed.

Example
for question in ["How do I reset my password?", "Who won the cricket?"]:
    result = graph.invoke({"question": question, "found": "", "source": "", "answer": ""})
    print(f"Q: {question}\n{result['answer']}\n")

One answer with a source under it, and one honest refusal. The refusal cost nothing, because no model was called on the path that had nothing to say.

What you have actually built

That is a working documentation assistant, and it is about forty lines all together. Everything in it is something you learned separately: a state, two nodes, a conditional edge, a model call and a prompt built from retrieved text.

The assistant, and the two ways a run can end
retrievescore every pagereturn nothing when nothing matchesfound somethinghand the page to the modelanswer, with the sourcefound nothingno model call at allsay so plainlydocumentation assistant

It also has the property people care about most and rarely get. When it does not know, it says so, and when it does know, it tells you where it read it.

The other way to build this

Here the search always runs, before the model gets a say. The alternative is to hand the model a search tool, as in lesson 18, and let it decide whether to look anything up.

Search as a nodeSearch as a tool
Who decidesYou do. It always runs.The model does, each time.
CostOne model callAt least two, sometimes more
Good forQuestions about a known set of documentsAssistants that also chat, calculate, or act
PredictableCompletelyAs predictable as the model

Neither is more advanced than the other. For a documentation assistant the node is usually the right answer, because you already know every question is about the documents.

Where to take it
Swap the stand-in for a real model, from lesson 30, and this becomes genuinely useful on your own notes or your team's handbook. The search is the part to improve first, not the prompt.
Try it yourself
  • Ask something half covered, such as how long a reset link lasts, and see which page it uses.
  • Make say_no suggest the closest page anyway, and decide whether you prefer it.
  • Turn the search into a tool with @tool and give it to the agent loop from lesson 18.

This is what real progress feels like.