Agent Graph with ToolNode
توسعه عاملهای هوشمند (Agentic Ai) با Langchain و Langgraph
- Use MessagesState for an agent workflow.
- Connect an LLM node, ToolNode, conditional continuation, and terminal response.
- Enforce a bounded recursion limit and diagnose unintended loops.
چرا Agent Graph؟
### مسئله: تصمیمگیری و اجرای ابزار
در 6.2، مدل یاد گرفت `tool call` بسازد؛ در 7.2، گراف را با `node`، `edge` و `compile` ساختیم. اینجا آن دو را ترکیب میکنیم: مدل تصمیم میگیرد، `ToolNode` اجرا میکند، و نتیجه دوباره به مدل برمیگردد.
```mermaid
flowchart LR
S([START]) --> L["LLM node"]
L --> Q{"tool_calls دارد؟"}
Q -- بله --> T["ToolNode"]
T --> L
Q -- خیر --> E([END])
```
**مثال کوتاه:** برای پرسش «سه و چهار چند میشود؟»، مدل ممکن است بهجای حدسزدن، `add(a=3, b=4)` را درخواست کند؛ `ToolNode` مقدار `7` را برمیگرداند.
- **پیشنیاز ← 7.2:** هر مرحله یک `node` و هر انتقال یک `edge` است؛ `compile` گراف قابلاجرا میسازد.
- **پیشنیاز ← 6.2:** وجود `tool_calls` سیگنال ادامهدادن حلقه است.
- **مسیر بعدی → 8.1:** همین چرخه کنار `retrieval` و `citation` قرار میگیرد.
MessagesState و ToolNode
### state استاندارد برای پیامها
```python
from langgraph.graph import MessagesState
from langgraph.prebuilt import ToolNode
llm_with_tools = llm.bind_tools(tools)
tool_node = ToolNode(tools)
```
- `MessagesState` فیلد اصلی `messages` را دارد.
- هر `node` معمولاً دیکشنریای مانند `{"messages": [...]}` برمیگرداند.
- مدل پیامهای قبلی را میخواند و `AIMessage` میسازد.
- `ToolNode`، `tool_calls` آخرین `AIMessage` را میخواند، ابزار متناظر را اجرا میکند و `ToolMessage` میسازد.
```mermaid
flowchart TD
M["HumanMessage"] --> A["AIMessage"]
A --> C["tool_call: name + args + id"]
C --> X["ToolNode"]
X --> R["ToolMessage: result + tool_call_id"]
R --> A2["LLM نتیجه را میبیند"]
```
**مثال دادهای:** اگر درخواست ابزار شناسه `c1` داشته باشد، نتیجه باید با `tool_call_id="c1"` برگردد. این اتصال جلوی اشتباهگرفتن نتایج چند درخواست را میگیرد.
**نکته:** `ToolNode` انتخاب ابزار نمیکند؛ نام ابزار را از درخواست میخواند و اجرای متناظر را انجام میدهد.
ساخت گراف عامل
### تعریف nodeها و مسیرها
```python
from typing import Literal
from langgraph.graph import StateGraph, START, END
def llm_call(state: MessagesState):
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: MessagesState) -> Literal["tools", END]:
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tools"
return END
builder = StateGraph(MessagesState)
builder.add_node("llm_call", llm_call)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "llm_call")
builder.add_conditional_edges(
"llm_call", should_continue, ["tools", END]
)
builder.add_edge("tools", "llm_call")
agent = builder.compile()
```
```mermaid
flowchart LR
A([START]) --> B["llm_call"]
B -->|"tool_calls غیرخالی"| C["tools / ToolNode"]
C --> B
B -->|"tool_calls خالی"| D(["END: پاسخ نهایی"])
```
**مثال مسیر:** اگر آخرین پیام `AIMessage` یک `tool_call` داشته باشد، `should_continue` مقدار `tools` را برمیگرداند؛ اگر `tool_calls` فهرست خالی باشد، مقدار `END` را برمیگرداند.
- `routing` مقصد edge بعدی را تعیین میکند.
- `ToolNode` خودِ ابزار را اجرا میکند.
مثال کامل: جمع دو عدد
### تعریف ابزار و ورودی
```python
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
tools = [add]
llm_with_tools = llm.bind_tools(tools)
tool_node = ToolNode(tools)
messages = [HumanMessage(content="Add 3 and 4.")]
result = agent.invoke(
{"messages": messages},
{"recursion_limit": 6}
)
```
### اجرای گامبهگام
1. ورودی: `HumanMessage(content="Add 3 and 4.")`
2. مدل درخواست میسازد: `AIMessage(tool_calls=[{"name": "add", "args": {"a": 3, "b": 4,}, "id": "c1"}])`
3. چون `tool_calls` غیرخالی است، مقصد `tools` میشود.
4. `ToolNode` ورودیها را جایگذاری میکند: `add(3, 4)`
5. محاسبهٔ ابزار: `3 + 4 = 7`
6. خروجی ابزار: `ToolMessage(content="7", tool_call_id="c1")`
7. edge برگشتی، `llm_call` را دوباره اجرا میکند.
8. مدل نتیجه را میبیند و `AIMessage(content="7")` میسازد.
9. اکنون `tool_calls = []` است؛ مقصد `END` میشود.
### تاریخچهٔ نهایی
```text
HumanMessage: Add 3 and 4.
AIMessage: tool_call add(a=3, b=4), id=c1
ToolMessage: 7, tool_call_id=c1
AIMessage: 7
```
پایان درست و حلقه بینهایت
### دو حفاظ متفاوت
- **شرط پایان:** منطق اصلی پایان؛ وقتی `tool_calls == []`، مسیر `END` است.
- **`recursion_limit`:** سقف ایمنی اجرای گراف؛ برای خطاهایی مانند `GRAPH_RECURSION_LIMIT`.
```mermaid
flowchart TD
A["AIMessage"] --> B{"tool_calls؟"}
B -- خالی --> C(["END"])
B -- غیرخالی --> D["ToolNode"]
D --> E{"نتیجه معتبر و پیشرفت؟"}
E -- بله --> A
E -- خیر --> F["ثبت خطا یا توقف کنترلشده"]
```
### مثال حلقهٔ ناخواسته
```python
builder.add_edge("a", "b")
builder.add_edge("b", "a")
```
اگر مدل در هر دور همان `tool_call` را تولید کند، `tool_calls` هرگز خالی نمیشود و شرط پایان فعال نمیشود. افزایش limit فقط توقف خطا را عقب میاندازد.
### اجرای محدود
```python
graph.invoke(
{"messages": [HumanMessage(content="Add 3 and 4.")]},
{"recursion_limit": 6}
)
```
- **مسیر بعدی → 8.1:** همین termination و چرخهٔ ابزار، پایهٔ مسیر retrieval و پاسخ دارای citation خواهد بود.
Back to course