Display the final answer and source list
The pipeline is done. The last piece is presentation. Show the question, show the answer with its citations intact, and print a numbered source list so the user can click [1] in the text and find [1] at the bottom. This is the difference between a research tool and a chatbot.
def display_results(final_state):
"""Display the results in a formatted way."""
print("\nFINAL RESULTS")
print("=" * 50)
print(f"\nQUESTION: {final_state['question']}")
print("\nANSWER:")
print("-" * 50)
print(final_state["final_answer"])
print("-" * 50)
if final_state["sources"]:
print(f"\nSOURCES ({len(final_state['sources'])}):")
for i, source in enumerate(final_state["sources"], 1):
print(f"\n[{i}] {source['title']}")
print(f" URL: {source['url']}")
print(f" Snippet: {source['snippet'][:100]}...")
else:
print("\nNo sources available")
print(f"\nSTATS:")
print(f" Search results: {len(final_state['search_results'])}")
print(f" Sources extracted: {len(final_state['extracted_content'])}")
print(f" Answer length: {len(final_state['final_answer'])} characters")A simple print layout is enough for a notebook. If you later wrap the pipeline in a web UI, the same data shape renders as a numbered list with clickable links. State flows, presentation changes.
# End-to-end: ask a question and print the result
question = "Tell me about the DeepSeek OCR model"
final_state = run_research_assistant(question)
display_results(final_state)Every piece we built clicks together in three lines. The graph does the heavy lifting. You just give it a question and render the result.
That is a real failure mode. The model invented a citation that maps to nothing. The cheap defense is to validate after refinement: scan the final answer for [n] references, make sure each n is within the source count, and strip or flag any that are not. If you see it often, tighten the system prompt: "Only cite sources that appear in the context above, numbered 1 through N."
Checkpoint: Synthesis phase checkpoint
Loading practice…