In the previous section, we learned about the parameters of create_agent().
\\nThis section delves into the internals of the Agent to understand how it worksβhow the model and tools collaborate, and when the Agent stops.
\\n\\n
Agent Execution Loop
\\nThe core of an Agent is a simple loop: call the model β check if tools are needed β execute tools β repeat. Until the model no longer requests tool calls, the Agent stops and returns the final result.
\\nBelow, we trace each step of the Agent to understand this process.
\\nExample
\\nfrom dotenv import load_dotenv\\n\\n load_dotenv()\\n\\nfrom langchain.tools import tool\\n\\nfrom langchain.agents import create_agent\\n\\nfrom langchain.chat_models import init_chat_model\\n\\nfrom langchain.messages import HumanMessage\\n\\n@tool\\n\\ndef get_weather(city: str) ->str:\\n\\n"""Query the weather for a specified city.\\nArgs:\\n\\n city: City name\\n\\n """\\n\\n weather_data ={\\n\\n"Hangzhou": "SunnyοΌ25Β°C",\\n\\n"Beijing": "Cloudy, 18Β°C",\\n\\n}\\n\\nreturn weather_data.get(city, f"NotFound {city} 's weather data")\\n\\n@tool\\n\\ndef get_time(city: str) ->str:\\n\\n"""Query the current time for a specified city.\\nArgs:\\n\\n city: City name\\n\\n """\\n\\n time_data ={\\n\\n"Hangzhou": "14:30",\\n\\n"Beijing": "14:30",\\n\\n"New York": "02:30",\\n\\n}\\n\\nreturn time_data.get(city, f"NotFound {city} 's time data")\\n\\nmodel = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)\\n\\n agent = create_agent(\\n\\n model=model,\\n\\n tools=[get_weather, get_time],\\n\\n system_prompt="You are a helpful assistant.",\\n\\n)\\n\\n# Using stream_mode="updates" You can see each step\\n\\nprint("=== Agent Execution process tracing ===n")\\n\\n step =0\\n\\nfor chunk in agent.stream(\\n\\n{"messages": [HumanMessage(content="HangzhouNow How's the weather?What time is it?")]},\\n\\n stream_mode="updates",\\n\\n):\\n\\n step +=1\\n\\nprint(f"--- Step {step} ---")\\n\\nfor node_name, update in chunk.items():\\n\\nprint(f"Node: {node_name}")\\n\\nif"messages"in update:\\n\\nfor msg in update:\\n\\nif hasattr(msg,'tool_calls')and msg.tool_calls:\\n\\n# AI Message contains tool calls\\n\\nfor tc in msg.tool_calls:\\n\\nprint(f" β Tool call request: {tc['name']}({tc['args']})")\\n\\nelif msg.type=="tool":\\n\\nprint(f" β Tool result [{msg.name}]: {msg.content}")\\n\\nelif msg.type=="ai"and msg.content:\\n\\nprint(f" β AI Response: {msg.content[:100]}")\\nExecution result:
\\n=== Agent Execution process tracing ===--- Step 1 ---Node: model β Tool call request: get_weather({'city': 'Hangzhou'}) β Tool call request: get_time({'city': 'Hangzhou'})--- Step 2 ---Node: tools β Tool result : SunnyοΌ25Β°C β Tool result : 14:30--- Step 3 ---Node: model β AI Response: HangzhouCurrent weather is Sunny, temperature 25Β°CοΌCurrent time is 14:30γ\\nFrom this trace, we can see that the Agent executed 3 steps:
\\n- \\n
- Step 1 (model node): The model receives the question, determines that
get_weatherandget_timetools need to be called, and returns two tool_calls \\n - Step 2 (tools node): Execute both tools to get weather and time results \\n
- Step 3 (model node): The model receives the tool results, determines that information is sufficient, and generates the final response \\n
\\n
stream_mode Details
\\nstream() supports multiple stream_mode options, each providing different levels of granularity:
| Mode | \\nReturn Content | \\nApplicable Scenario | \\n
|---|---|---|
| updates | \\nState updates after each node execution | \\nTracing Agent execution steps, displaying intermediate results | \\n
| values | \\nComplete state after each node execution | \\nNeed to see complete message history at each step | \\n
| messages | \\nToken-by-token message stream | \\nFrontend streaming display of AI typing effect | \\n
| custom | \\nCustom events | \\nMiddleware sending custom events via stream_writer | \\n
stream_mode="values" β Viewing Complete State Changes
\\nExample
\\nprint("=== stream_mode='values' ===n")\\n\\nfor i,chunk in enumerate(agent.stream(\\n\\n{"messages": [HumanMessage(content="HangzhouHow's the weather?")]},\\n\\n stream_mode="values",\\n\\n)):\\n\\n messages =chunk.get("messages",[])\\n\\nprint(f"State {i}: {len(messages)} messages")\\n\\nfor msg in messages:\\n\\nprint(f" [{msg.type}] {str(msg.content)[:80]}")\\n\\nif i >=3: # Only look at the first few States\\n\\nbreak\\nExecution result:
\\n=== stream_mode='values' ===State 0: 1 messages HangzhouHow's the weather?State 1: 2 messages HangzhouHow's the weather? State 2: 3 messages HangzhouHow's the weather? SunnyοΌ25Β°C State 3: 4 messages HangzhouHow's the weather? SunnyοΌ25Β°C HangzhouToday's weather is Sunny, temperature 25Β°CοΌSuitable for outdoor activities.\\n
stream_mode="messages" β Token-by-Token Streaming Output
\\nExample
\\nprint("=== stream_mode='messages'οΌstreaming typing effect)===n")\\n\\nfor msg_chunk, metadata in agent.stream(\\n\\n{"messages": [HumanMessage(content="Introduce TUTORIAL in one sentence")]},\\n\\n stream_mode="messages",\\n\\n):\\n\\n# msg_chunk is AIMessageChunkοΌEach contains only one Token\\n\\nif hasattr(msg_chunk,'content')and msg_chunk.content:\\n\\nprint(msg_chunk.content, end="", flush=True)\\n\\nprint()\\n\\n
Agent Exit Conditions
\\nWhen does the Agent stop? There are mainly the following situations:
\\n| Exit Condition | \\nDescription | \\nExample | \\n
|---|---|---|
| No tool calls | \\nThe AIMessage returned by the model has empty tool_calls | \\nModel considers task complete, replies directly | \\n
| return_direct=True | \\nTool marked for direct return, ends immediately after execution | \\nQuery tools where result is the final answer | \\n
| structured_response | \\nModel produces structured output | \\nStructured output specified by response_format is complete | \\n
| jump_to="end" | \\nMiddleware actively ends through state control | \\nDetecting out-of-bounds questions, terminating early | \\n
Example
\\nfrom langchain.tools import tool\\n\\nfrom langchain.agents import create_agent\\n\\nfrom langchain.chat_models import init_chat_model\\n\\nfrom langchain.messages import HumanMessage\\n\\n# Case 1: Model responds directly (no tools needed)\\n\\n model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)\\n\\n agent = create_agent(\\n\\n model=model,\\n\\n tools=[],# No tool\\n\\n)\\n\\nresult = agent.invoke({\\n\\n"messages": [HumanMessage(content="Introduce in one sentence")]\\n\\n})\\n\\n# Model responds directly, loop executes only once\\n\\nprint(f"No-tool scenario, message count: {len(result['messages'])}")\\n\\nprint(f"Response: {result['messages'].content[:80]}...")\\n\\n# Case 2: Tool call required (multi-turn loop)\\n\\n@tool\\n\\ndef search_course(keyword: str) ->str:\\n\\n"""Search Course"""\\n\\nreturn f"Found {keyword} 3 related courses"\\n\\nagent_with_tools = create_agent(\\n\\n model=model,\\n\\n tools=,\\n\\n)\\n\\nresult = agent_with_tools.invoke({\\n\\n"messages": [HumanMessage(content="Search Python courses")]\\n\\n})\\n\\nprint(f"n Tool scenario, message count: {len(result['messages'])}")\\n\\n# Usually includes: human, ai(tool_call), tool(result), ai(final)\\n\\nfor msg in result:\\n\\nprint(f" [{msg.type}]", end="")\\n\\nif hasattr(msg,'tool_calls')and msg.tool_calls:\\n\\nprint(f" tool_calls: {[tc['name'] for tc in msg.tool_calls]}")\\n\\nelse:\\n\\nprint(f" {str(msg.content)[:50]}")\\nExecution result:
\\nNo-tool scenario, message count: 2Response: isA programming learning platform for beginners, offering rich tutorials and examples...Tool scenario, message count: 4 Search Python courses tool_calls: ['search_course'] Found python 3 related courses Search in "Python Course" Found 3 related courses...\\n
\\n
invoke vs stream Comparison
\\n| Method | \\nReturn Timing | \\nApplicable Scenario | \\nUser Experience | \\n
|---|---|---|---|
| invoke() | \\nReturns all at once after completion | \\nScripts, API interfaces, batch processing | \\nSee complete result after waiting | \\n
| stream() | \\nReturns intermediate states progressively | \\nChat interfaces, need to show process | \\nSee progress in real-time | \\n
| ainvoke() | \\nReturns asynchronously after completion | \\nWeb services, async frameworks | \\nDoes not block event loop | \\n
| astream() | \\nReturns asynchronously and progressively | \\nWebSocket, SSE push | \\nServer real-time push | \\n
\\n
Passing Thread ID in with_config
\\nIf you use a checkpointer (detailed in subsequent chapters), you need to pass thread_id through config to manage conversation threads:
Example
\\n# config Used to pass runtime configuration\\n\\n# thread_id Used to distinguish different conversation threads\\n\\n config ={"configurable": {"thread_id": "conversation-001"}}\\n\\n# invoke Method\\n\\n result = agent.invoke(\\n\\n{"messages": [HumanMessage(content="Hello")]},\\n\\n config=config,\\n\\n)\\n\\n# stream MethodAlso supports config\\n\\nfor chunk in agent.stream(\\n\\n{"messages": [HumanMessage(content="Hello")]},\\n\\n config=config,\\n\\n stream_mode="updates",\\n\\n):\\n\\nprint(chunk)
YouTip