📖 Lesson content
Summary
Building multi-turn conversations with tool use requires handling different response types from Claude. When Claude responds, it might need to use a tool, or it might provide a direct answer. Your code needs to handle both scenarios gracefully.
The Problem with Simple Tool Integration
If you just add tool results to every conversation, you'll run into issues. When Claude answers a simple question like "What is 1+1?", it doesn't need any tools. But if your code always tries to process tool results, you'll end up adding empty messages to your conversation history.
The solution is to check the stop_reason that comes back with every Claude response. This tells you why Claude stopped generating - whether it finished naturally or because it wants to use a tool.
Stop Reasons

Claude can stop for several reasons:
"tool_use"- The model wants to call a tool"end_turn"- Model finished generating its response"max_tokens"- Hit the output limit"stop_sequence"- Encountered a stop sequence you provided
Improving the Chat Function
First, update your chat function to return more information. Instead of just returning text and parts separately, return a dictionary with everything you need:
def chat(messages, tools=None, system=None, **kwargs):
# ... existing code ...
return {
"parts": parts,
"stop_reason": response["stopReason"],
"text": "\n".join([p["text"] for p in parts if "text" in p])
}
This approach extracts all text content from the response parts, which is more robust than assuming the first part is always text.
Building a Conversation Loop
Create a function that handles the full conversation flow:
def run_conversation(messages):
while True:
result = chat(messages, tools=[get_current_datetime_schema])
add_assistant_message(messages, result["parts"])
print(result["text"])
if result["stop_reason"] != "tool_use":
break
tool_result_parts = run_tools(result["parts"])
add_user_message(messages, tool_result_parts)
return messages
This loop continues until Claude stops for a reason other than tool use. Each iteration:
- Sends the current messages to Claude
- Adds Claude's response to the message history
- Checks if Claude wants to use a tool
- If so, runs the tools and adds results back to the conversation
- If not, exits the loop
Testing the Implementation
This approach handles both tool-requiring and simple questions:
messages = []
add_user_message(messages, "What time is it?")
run_conversation(messages)
messages = []
add_user_message(messages, "What is 1+1?")
run_conversation(messages)
For time questions, Claude will use the datetime tool. For math questions, it responds directly without any tool calls. The conversation loop adapts automatically based on Claude's stop reason.
This pattern scales well when you add more tools - the same loop handles any combination of tool use and direct responses, making your conversational AI more robust and natural.
🔁 Related lessons
- Next: Adding multiple tools
- Previous: Sending tool results
- Same section: Overview of Claude Models · Accessing the API · Making a request
- Part of paths: Path C
- Reference docs: Glossary · Skills atlas · By use-case
📚 Source & attribution
- Original Anthropic Academy lesson: https://anthropic.skilljar.com/claude-in-amazon-bedrock/276759
- © 2025 Anthropic. Educational fair-use only.