Fix empty tool-use response handling
@@ -143,7 +143,7 @@ Claude Code = 一つの agent loop
|
||||
|
||||
User --> messages[] --> LLM --> response
|
||||
|
|
||||
stop_reason == "tool_use"?
|
||||
tool_use block を含む?
|
||||
/ \
|
||||
yes no
|
||||
| |
|
||||
@@ -210,18 +210,20 @@ def agent_loop(messages):
|
||||
messages.append({"role": "assistant",
|
||||
"content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
@@ -349,7 +351,7 @@ flowchart TD
|
||||
|
||||
| セッション | トピック | キーコンセプト |
|
||||
|---|---|---|
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `stop_reason` |
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `tool_use` |
|
||||
| [s02](./s02_tool_use/) | Tool Use | `TOOL_HANDLERS` / dispatch map / 並行性 |
|
||||
| [s03](./s03_permission/) | Permission | `PermissionRule` / 承認パイプライン |
|
||||
| [s04](./s04_hooks/) | Hooks | `PreToolUse` / `PostToolUse` / 拡張ポイント |
|
||||
|
||||
@@ -143,7 +143,7 @@ Claude Code = 一个 agent loop
|
||||
|
||||
User --> messages[] --> LLM --> response
|
||||
|
|
||||
stop_reason == "tool_use"?
|
||||
包含 tool_use block?
|
||||
/ \
|
||||
yes no
|
||||
| |
|
||||
@@ -210,18 +210,20 @@ def agent_loop(messages):
|
||||
messages.append({"role": "assistant",
|
||||
"content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
@@ -350,7 +352,7 @@ flowchart TD
|
||||
|
||||
| 章节 | 主题 | 关键概念 |
|
||||
|---|---|---|
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `stop_reason` |
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `tool_use` |
|
||||
| [s02](./s02_tool_use/) | Tool Use | `TOOL_HANDLERS` / dispatch map / 并发 |
|
||||
| [s03](./s03_permission/) | Permission | `PermissionRule` / 审批管线 |
|
||||
| [s04](./s04_hooks/) | Hooks | `PreToolUse` / `PostToolUse` / 扩展点 |
|
||||
|
||||
@@ -115,7 +115,7 @@ The takeaway is not "copy Claude Code." The takeaway is: **the best agent produc
|
||||
|
||||
User --> messages[] --> LLM --> response
|
||||
|
|
||||
stop_reason == "tool_use"?
|
||||
contains tool_use block?
|
||||
/ \
|
||||
yes no
|
||||
| |
|
||||
@@ -142,18 +142,20 @@ def agent_loop(messages):
|
||||
messages.append({"role": "assistant",
|
||||
"content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = TOOL_HANDLERS[block.name](**block.input)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
@@ -300,7 +302,7 @@ flowchart TD
|
||||
|
||||
| Chapter | Topic | Key Concepts |
|
||||
|---|---|---|
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `stop_reason` |
|
||||
| [s01](./s01_agent_loop/) | Agent Loop | `messages` / `while True` / `tool_use` |
|
||||
| [s02](./s02_tool_use/) | Tool Use | `TOOL_HANDLERS` / dispatch map / concurrency |
|
||||
| [s03](./s03_permission/) | Permission System | `PermissionRule` / approval pipeline |
|
||||
| [s04](./s04_hooks/) | Hook System | `PreToolUse` / `PostToolUse` / extension points |
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||

|
||||
|
||||
一つの `while True` ループ — モデルがツールを呼べば続き、呼ばなければ停止。全体でたった 2 つのシグナル:
|
||||
一つの `while True` ループ — モデルがツールを呼べば続き、呼ばなければ停止。ループは response の content block を直接確認する:
|
||||
|
||||
| シグナル | 意味 | ループの動作 |
|
||||
|----------|------|-------------|
|
||||
| `stop_reason == "tool_use"` | モデルが「ツールが必要」と挙手 | 実行 → 結果を戻す → 続行 |
|
||||
| `stop_reason != "tool_use"` | モデルが「完了」と宣言 | ループ終了 |
|
||||
| `tool_use` block を含む | モデルがツール呼び出しを要求 | 実行 → 結果を戻す → 続行 |
|
||||
| `tool_use` block を含まない | モデルがツールを呼ばなかった | ループ終了 |
|
||||
|
||||
---
|
||||
|
||||
@@ -57,22 +57,26 @@ response = client.messages.create(
|
||||
|
||||
```python
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
```
|
||||
|
||||
実際の `tool_use` block だけが実行段階に進むため、空の tool result メッセージは追加されない。
|
||||
|
||||
**ステップ 4**:モデルが要求したツールを実行し、結果を収集する。
|
||||
|
||||
```python
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
```
|
||||
|
||||
**ステップ 5**:ツールの結果を新しいメッセージとして追加し、ステップ 2 に戻る。
|
||||
@@ -92,22 +96,24 @@ def agent_loop(messages):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
30 行未満 — これが最小実行可能な agent harness のカーネルだ。これは知能そのものではなく、モデルが継続的に行動できるための最小ランタイムフレームワーク。モデルが決定し(ツールを呼ぶか、どれを呼ぶか)、harness が実行を担う(ツールを呼び出し、結果を新しいメッセージとして追加する)。次の 16 章はすべてこのループの上に仕組みを積み重ねていく。ループ自体は永遠に変わらない。
|
||||
30 行あまり — これが最小実行可能な agent harness のカーネルだ。これは知能そのものではなく、モデルが継続的に行動できるための最小ランタイムフレームワーク。モデルが決定し(ツールを呼ぶか、どれを呼ぶか)、harness が実行を担う(ツールを呼び出し、結果を新しいメッセージとして追加する)。次の 16 章はすべてこのループの上に仕組みを積み重ねていく。ループ自体は永遠に変わらない。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ Every round-trip, you're the middle layer. Automating that is what this chapter
|
||||
|
||||

|
||||
|
||||
A `while True` loop: keep going when the model calls a tool, stop when it doesn't. The entire process hinges on two signals:
|
||||
A `while True` loop: keep going when the model calls a tool, stop when it doesn't. The loop checks the response content blocks directly:
|
||||
|
||||
| Signal | Meaning | Loop Action |
|
||||
|--------|---------|-------------|
|
||||
| `stop_reason == "tool_use"` | Model raises hand: "I need a tool" | Execute → feed result back → continue |
|
||||
| `stop_reason != "tool_use"` | Model says: "I'm done" | Exit loop |
|
||||
| Contains a `tool_use` block | Model requests a tool call | Execute → feed result back → continue |
|
||||
| Contains no `tool_use` block | Model did not call a tool | Exit loop |
|
||||
|
||||
---
|
||||
|
||||
@@ -57,22 +57,26 @@ response = client.messages.create(
|
||||
|
||||
```python
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
```
|
||||
|
||||
Only concrete `tool_use` blocks enter the execution stage, so the loop never appends an empty tool-result message.
|
||||
|
||||
**Step 4**: Execute the tool the model requested and collect the results.
|
||||
|
||||
```python
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
```
|
||||
|
||||
**Step 5**: Append the tool results as a new message and go back to Step 2.
|
||||
@@ -92,22 +96,24 @@ def agent_loop(messages):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
Under 30 lines — that's the minimal runnable agent harness kernel. It's not intelligence itself, but the smallest runtime framework that lets the model keep acting. The model decides (whether to call a tool, which one), the harness executes (calls the tool and appends the result as a new message). The next 16 chapters all add mechanisms on top of this loop. The loop itself never changes.
|
||||
Just over 30 lines — that's the minimal runnable agent harness kernel. It's not intelligence itself, but the smallest runtime framework that lets the model keep acting. The model decides (whether to call a tool, which one), the harness executes (calls the tool and appends the result as a new message). The next 16 chapters all add mechanisms on top of this loop. The loop itself never changes.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
|
||||

|
||||
|
||||
一个 `while True` 循环,模型调用工具就继续,不调用就停。整个过程只有两个信号:
|
||||
一个 `while True` 循环,模型调用工具就继续,不调用就停。循环直接检查响应里的内容块:
|
||||
|
||||
| 信号 | 含义 | 循环动作 |
|
||||
|------|------|---------|
|
||||
| `stop_reason == "tool_use"` | 模型举手说"我要用工具" | 执行 → 结果喂回去 → 继续 |
|
||||
| `stop_reason != "tool_use"` | 模型说"我做完了" | 退出循环 |
|
||||
| 包含 `tool_use` block | 模型要求调用工具 | 执行 → 结果喂回去 → 继续 |
|
||||
| 不包含 `tool_use` block | 模型没有调用工具 | 退出循环 |
|
||||
|
||||
---
|
||||
|
||||
@@ -57,22 +57,26 @@ response = client.messages.create(
|
||||
|
||||
```python
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
```
|
||||
|
||||
只有实际存在的 `tool_use` block 才会进入执行阶段,因此不会追加空的工具结果消息。
|
||||
|
||||
**第 4 步**:执行模型要求的工具,收集结果。
|
||||
|
||||
```python
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
```
|
||||
|
||||
**第 5 步**:把工具结果作为新消息追加,回到第 2 步。
|
||||
@@ -92,22 +96,24 @@ def agent_loop(messages):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
output = run_bash(block.input["command"])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
messages.append({"role": "user", "content": results})
|
||||
```
|
||||
|
||||
不到 30 行,这就是最小可运行的 agent harness 内核。它为模型提供持续行动的最小运行框架:模型负责决策(要不要调工具、调哪个),harness 负责执行(调用工具,把结果作为新消息追加)。后面 16 个章节都在这个循环上叠加机制,循环本身始终不变。
|
||||
三十多行,这就是最小可运行的 agent harness 内核。它为模型提供持续行动的最小运行框架:模型负责决策(要不要调工具、调哪个),harness 负责执行(调用工具,把结果作为新消息追加)。后面 16 个章节都在这个循环上叠加机制,循环本身始终不变。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ s01_agent_loop.py - The Agent Loop
|
||||
|
||||
The entire secret of an AI coding agent in one pattern:
|
||||
|
||||
while stop_reason == "tool_use":
|
||||
while True:
|
||||
response = LLM(messages, tools)
|
||||
if response contains no tool_use:
|
||||
break
|
||||
execute tools
|
||||
append results
|
||||
|
||||
@@ -93,21 +95,23 @@ def agent_loop(messages: list):
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
# If the model didn't call a tool, we're done
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
# Execute each tool call, collect results
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
print(f"\033[33m$ {block.input['command']}\033[0m")
|
||||
output = run_bash(block.input["command"])
|
||||
print(output[:200])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
for block in tool_calls:
|
||||
print(f"\033[33m$ {block.input['command']}\033[0m")
|
||||
output = run_bash(block.input["command"])
|
||||
print(output[:200])
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
"content": output,
|
||||
})
|
||||
|
||||
# Feed tool results back, loop continues
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">Model reads message history</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">Decision: Need a tool?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">Returns stop_reason signal</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">Returns content blocks</text>
|
||||
|
||||
<!-- Arrow: LLM → Decision (down) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== Decision Diamond ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">present?</text>
|
||||
|
||||
<!-- Arrow: No → End (right) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">モデルがメッセージ履歴を読む</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">判断:ツールが必要か?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">stop_reason シグナルを返す</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">content block を返す</text>
|
||||
|
||||
<!-- 矢印:LLM → 判定(下) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== 判定ダイヤモンド ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">あり?</text>
|
||||
|
||||
<!-- 矢印:いいえ → 終了(右) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">模型阅读消息历史</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">判断:需要工具吗?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">返回 stop_reason 信号</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">返回内容块</text>
|
||||
|
||||
<!-- 箭头:LLM → 判断(向下) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== 判断菱形 ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">存在?</text>
|
||||
|
||||
<!-- 箭头:否 → 结束(向右) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -21,7 +21,7 @@ s01 の Agent には bash 一つのツールしかない。ファイルを読む
|
||||
|
||||

|
||||
|
||||
s01 のループは完全に保持される(LLM 呼び出し、stop_reason 判定、メッセージ追加 — 一文字も変更なし)。唯一の変更点はツール実行の 1 行:`run_bash()` が `TOOL_HANDLERS[block.name]()` の検索ディスパッチに置き換わる。
|
||||
s01 のループは完全に保持される(LLM 呼び出し、`tool_use` block 判定、メッセージ追加 — 一文字も変更なし)。唯一の変更点はツール実行の 1 行:`run_bash()` が `TOOL_HANDLERS[block.name]()` の検索ディスパッチに置き換わる。
|
||||
|
||||
Agent にツールを追加するには、たった二つ:
|
||||
|
||||
@@ -91,11 +91,10 @@ TOOL_HANDLERS = {
|
||||
}
|
||||
|
||||
# ループ内で変更されたのは一行だけ — ハードコードの run_bash から検索ディスパッチへ:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
handler = TOOL_HANDLERS[block.name] # 検索
|
||||
output = handler(**block.input) # 呼び出し
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
handler = TOOL_HANDLERS[block.name] # 検索
|
||||
output = handler(**block.input) # 呼び出し
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
ツールの追加 = `TOOLS` 配列に一条 + `TOOL_HANDLERS` 辞書に一行。ループは変わらない。
|
||||
@@ -128,7 +127,7 @@ for block in response.content:
|
||||
| ツール数 | 1 (bash) | 5 (+read, write, edit, glob) |
|
||||
| ツール実行 | ハードコード `run_bash()` | TOOL_HANDLERS 検索ディスパッチ |
|
||||
| パス安全性 | なし | safe_path 検証(file tools のみ) |
|
||||
| ループ | `while True` + `stop_reason` | s01 と完全に同一 |
|
||||
| ループ | `while True` + `tool_use` block | s01 と完全に同一 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ The model thinks "read this file" but has to spell out `cat path/to/file`. An ex
|
||||
|
||||

|
||||
|
||||
The s01 loop is fully preserved (LLM call, stop_reason check, message append — not a single word changed). The only change is in that one line of tool execution: `run_bash()` is replaced with `TOOL_HANDLERS[block.name]()` dispatch lookup.
|
||||
The s01 loop is fully preserved (LLM call, `tool_use` block check, message append — not a single word changed). The only change is in that one line of tool execution: `run_bash()` is replaced with `TOOL_HANDLERS[block.name]()` dispatch lookup.
|
||||
|
||||
Adding a tool to the Agent requires just two things:
|
||||
|
||||
@@ -91,11 +91,10 @@ TOOL_HANDLERS = {
|
||||
}
|
||||
|
||||
# Only one line changed in the loop — from hardcoded run_bash to dispatch lookup:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
handler = TOOL_HANDLERS[block.name] # lookup
|
||||
output = handler(**block.input) # call
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
handler = TOOL_HANDLERS[block.name] # lookup
|
||||
output = handler(**block.input) # call
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
Adding a tool = one entry in `TOOLS` array + one line in `TOOL_HANDLERS` dict. The loop stays the same.
|
||||
@@ -128,7 +127,7 @@ Calls are executed one by one in their original `response.content` order.
|
||||
| Tool count | 1 (bash) | 5 (+read, write, edit, glob) |
|
||||
| Tool execution | Hardcoded `run_bash()` | TOOL_HANDLERS dispatch lookup |
|
||||
| Path safety | None | safe_path validation (file tools only) |
|
||||
| Loop | `while True` + `stop_reason` | Identical to s01 |
|
||||
| Loop | `while True` + `tool_use` block | Identical to s01 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ s01 的 Agent 只有一个 bash 工具。读文件要 `cat`,写文件要 `echo
|
||||
|
||||

|
||||
|
||||
s01 的循环完全保留(LLM 调用、stop_reason 判断、消息追加)。唯一的变动在工具执行那 1 行:`run_bash()` 替换为 `TOOL_HANDLERS[block.name]()` 查表分发。
|
||||
s01 的循环完全保留(LLM 调用、`tool_use` block 判断、消息追加)。唯一的变动在工具执行那 1 行:`run_bash()` 替换为 `TOOL_HANDLERS[block.name]()` 查表分发。
|
||||
|
||||
给 Agent 加一个工具只需要做两件事:
|
||||
|
||||
@@ -91,11 +91,10 @@ TOOL_HANDLERS = {
|
||||
}
|
||||
|
||||
# 循环里只改了一行——从硬编码 run_bash 变成查表:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
handler = TOOL_HANDLERS[block.name] # 查表
|
||||
output = handler(**block.input) # 调用
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
handler = TOOL_HANDLERS[block.name] # 查表
|
||||
output = handler(**block.input) # 调用
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
加一个工具 = 在 `TOOLS` 数组加一条 + 在 `TOOL_HANDLERS` 字典加一行。循环不变。
|
||||
@@ -128,7 +127,7 @@ for block in response.content:
|
||||
| 工具数量 | 1 (bash) | 5 (+read, write, edit, glob) |
|
||||
| 工具执行 | 硬编码 `run_bash()` | TOOL_HANDLERS 查表分发 |
|
||||
| 路径安全 | 无 | safe_path 校验(仅 file tools) |
|
||||
| 循环 | `while True` + `stop_reason` | 与 s01 完全一致 |
|
||||
| 循环 | `while True` + `tool_use` block | 与 s01 完全一致 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -154,17 +154,19 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
print(f"\033[33m> {block.name}\033[0m")
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
print(str(output)[:200])
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
|
||||
for block in tool_calls:
|
||||
print(f"\033[33m> {block.name}\033[0m")
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
print(str(output)[:200])
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
|
||||
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason check</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block check</text>
|
||||
|
||||
<!-- Arrow: LLM → Decision -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Decision Diamond -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → Return -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判定</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block 判定</text>
|
||||
|
||||
<!-- 矢印:LLM → 判定 -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 判定ダイヤモンド -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- いいえ → 返却 -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">大模型 (LLM)</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判断</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">检查 tool_use block</text>
|
||||
|
||||
<!-- 箭头:LLM → 判断 -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 判断菱形 -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -108,13 +108,12 @@ def check_permission(block) -> bool:
|
||||
return True
|
||||
|
||||
# agent_loop で — s02 のループに 1 行追加するだけ:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
if not check_permission(block): # ← 新規
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 既存
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
if not check_permission(block): # ← 新規
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 既存
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -108,13 +108,12 @@ def check_permission(block) -> bool:
|
||||
return True
|
||||
|
||||
# In agent_loop — s02's loop with just one line added:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
if not check_permission(block): # ← NEW
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 original
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
if not check_permission(block): # ← NEW
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 original
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -108,13 +108,12 @@ def check_permission(block) -> bool:
|
||||
return True
|
||||
|
||||
# 在 agent_loop 中——s02 的循环只加了一行:
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
if not check_permission(block): # ← 新增
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 原有
|
||||
results.append(...)
|
||||
for block in tool_calls:
|
||||
if not check_permission(block): # ← 新增
|
||||
results.append({... "content": "Permission denied."})
|
||||
continue
|
||||
output = TOOL_HANDLERS[block.name](**block.input) # s02 原有
|
||||
results.append(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -197,14 +197,14 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
print(f"\033[36m> {block.name}\033[0m")
|
||||
|
||||
# s03 change: run through permission pipeline before executing
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → return -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → 戻る -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -130,7 +130,7 @@ register_hook("PreToolUse", log_hook)
|
||||
register_hook("PostToolUse", large_output_hook)
|
||||
```
|
||||
|
||||
**Stop** はループが終了する直前に発火する(`stop_reason != "tool_use"`)。以下の hook は終了時の統計を出力する:
|
||||
**Stop** はループが終了する直前に発火する。以下の hook は終了時の統計を出力する:
|
||||
|
||||
```python
|
||||
def summary_hook(messages: list) -> str | None:
|
||||
@@ -147,7 +147,10 @@ register_hook("Stop", summary_hook)
|
||||
agent_loop 内では、終了前に発火:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages) # ← 終了する前に
|
||||
if force:
|
||||
# フックがメッセージを返した → 注入して続行
|
||||
@@ -159,10 +162,7 @@ if response.stop_reason != "tool_use":
|
||||
**ループ内で変更されたのは一箇所だけ**:s03 は直接 `check_permission(block)` を呼び出していたが、s04 は `trigger_hooks("PreToolUse", block)` に置き換えた:
|
||||
|
||||
```python
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
# s03: if not check_permission(block): ...
|
||||
# s04: フックがハードコードを代替
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
|
||||
@@ -130,7 +130,7 @@ register_hook("PreToolUse", log_hook)
|
||||
register_hook("PostToolUse", large_output_hook)
|
||||
```
|
||||
|
||||
**Stop** triggers when the loop is about to exit (`stop_reason != "tool_use"`). The following hook prints a cleanup summary:
|
||||
**Stop** triggers when the loop is about to exit. The following hook prints a cleanup summary:
|
||||
|
||||
```python
|
||||
def summary_hook(messages: list) -> str | None:
|
||||
@@ -147,7 +147,10 @@ register_hook("Stop", summary_hook)
|
||||
In agent_loop, triggered before exit:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages) # ← before exiting
|
||||
if force:
|
||||
# hook returned a message → inject it and continue
|
||||
@@ -159,10 +162,7 @@ if response.stop_reason != "tool_use":
|
||||
**Only one change in the loop**: s03 directly called `check_permission(block)`, s04 replaces it with `trigger_hooks("PreToolUse", block)`:
|
||||
|
||||
```python
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
# s03: if not check_permission(block): ...
|
||||
# s04: hooks replace hardcoding
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
|
||||
@@ -130,7 +130,7 @@ register_hook("PreToolUse", log_hook)
|
||||
register_hook("PostToolUse", large_output_hook)
|
||||
```
|
||||
|
||||
**Stop** 在循环即将退出时触发(`stop_reason != "tool_use"`)。以下 hook 打印收尾统计:
|
||||
**Stop** 在循环即将退出时触发。以下 hook 打印收尾统计:
|
||||
|
||||
```python
|
||||
def summary_hook(messages: list) -> str | None:
|
||||
@@ -147,7 +147,10 @@ register_hook("Stop", summary_hook)
|
||||
在 agent_loop 中,退出前触发:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages) # ← 退出之前
|
||||
if force:
|
||||
# hook returned a message → inject it and continue
|
||||
@@ -159,10 +162,7 @@ if response.stop_reason != "tool_use":
|
||||
**循环里只改了一处**:s03 直接调用 `check_permission(block)`,s04 改为 `trigger_hooks("PreToolUse", block)`:
|
||||
|
||||
```python
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
# s03: if not check_permission(block): ...
|
||||
# s04: hook 替代硬编码
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
|
||||
@@ -205,7 +205,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -213,10 +216,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
# s04 change: hook replaces hard-coded check_permission()
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → Return -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → 返却 -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM 否 → 返回 -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -284,7 +284,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -293,10 +296,7 @@ def agent_loop(messages: list):
|
||||
|
||||
results = []
|
||||
used_todo = False
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → Return -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → 返却 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -42,14 +42,16 @@ def run_subagent(prompt: str) -> str:
|
||||
messages=messages, tools=SUB_TOOLS, max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return extract_text(response.content) or "(no summary)"
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
return "Subagent stopped after 30 turns without a final answer."
|
||||
|
||||
@@ -42,14 +42,16 @@ def run_subagent(prompt: str) -> str:
|
||||
messages=messages, tools=SUB_TOOLS, max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return extract_text(response.content) or "(no summary)"
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
return "Subagent stopped after 30 turns without a final answer."
|
||||
|
||||
@@ -42,14 +42,16 @@ def run_subagent(prompt: str) -> str:
|
||||
messages=messages, tools=SUB_TOOLS, max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
return extract_text(response.content) or "(no summary)"
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type == "tool_use":
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
results.append({... "content": output})
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
return "Subagent stopped after 30 turns without a final answer."
|
||||
|
||||
@@ -268,7 +268,10 @@ def run_subagent(prompt: str) -> str:
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -277,9 +280,7 @@ def run_subagent(prompt: str) -> str:
|
||||
return extract_text(response.content) or "(no summary)"
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block, SUB_HANDLERS)
|
||||
print(f" \033[90m[sub] {block.name}: {output[:100]}\033[0m")
|
||||
results.append({
|
||||
@@ -320,7 +321,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -328,9 +332,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block, TOOL_HANDLERS)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -336,7 +336,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -344,9 +347,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -255,13 +255,13 @@ def agent_loop(messages, active_request):
|
||||
1 回の応答には、ファイル書き込みと圧縮のように複数のツール呼び出しが含まれることがあります。Harness はまず一括処理をすべて実行し、各 `tool_use` に対応する `tool_result` を追加します。そのターンが完結してから要約します。
|
||||
|
||||
```python
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
results = []
|
||||
compact_requested = False
|
||||
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
if block.name == "compact":
|
||||
output = "Compaction requested after this tool batch."
|
||||
compact_requested = True
|
||||
|
||||
@@ -255,13 +255,13 @@ An automatic threshold knows only how large the context is. The model can also c
|
||||
A response may request several tools at once, such as writing a file and then compacting. The Harness first executes the complete batch and appends one `tool_result` for every `tool_use`. It summarizes only after that turn is complete:
|
||||
|
||||
```python
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
results = []
|
||||
compact_requested = False
|
||||
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
if block.name == "compact":
|
||||
output = "Compaction requested after this tool batch."
|
||||
compact_requested = True
|
||||
|
||||
@@ -255,13 +255,13 @@ def agent_loop(messages, active_request):
|
||||
一次响应可以同时包含多个工具调用,例如先写文件再请求压缩。Harness 必须先执行完整批次,并为每个 `tool_use` 追加对应的 `tool_result`,然后再摘要这个已经闭合的回合:
|
||||
|
||||
```python
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
results = []
|
||||
compact_requested = False
|
||||
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
for block in tool_calls:
|
||||
if block.name == "compact":
|
||||
output = "Compaction requested after this tool batch."
|
||||
compact_requested = True
|
||||
|
||||
@@ -432,7 +432,10 @@ def agent_loop(messages: list, active_request: str):
|
||||
raise
|
||||
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -441,9 +444,7 @@ def agent_loop(messages: list, active_request: str):
|
||||
|
||||
results = []
|
||||
compact_requested = False
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
print(f"\033[36m> {block.name}\033[0m")
|
||||
if block.name == "compact":
|
||||
output = "Compaction requested after this tool batch."
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → Return -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → 返却 -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM 否 → 返回 -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -96,7 +96,10 @@ system = build_system(relevant_memories)
|
||||
ユーザーが毎回「覚えて」と言うとは限らない。Agent が現在の返答を終えた後、`extract_memories()` は会話を確認し、今後も役立つ可能性がある情報だけを取り出す。
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
|
||||
@@ -96,7 +96,10 @@ system = build_system(relevant_memories)
|
||||
Users do not always say "remember this." After the Agent finishes the current response, `extract_memories()` inspects the conversation and keeps only information likely to help later:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
|
||||
@@ -96,7 +96,10 @@ system = build_system(relevant_memories)
|
||||
用户不一定会明确说“请记住”。`extract_memories()` 在 Agent 完成本轮回答后检查当前对话,只提取以后仍可能有用的信息:
|
||||
|
||||
```python
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
|
||||
@@ -714,7 +714,10 @@ def agent_loop(messages: list):
|
||||
"content": response.content,
|
||||
})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -724,9 +727,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → no → return result -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → no → return result -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → 否 → 返回结果 -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
@@ -489,7 +489,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -497,9 +500,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -457,7 +457,10 @@ def agent_loop(messages: list):
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -465,9 +468,7 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -658,7 +658,10 @@ def agent_loop(messages: list, context: dict | None = None):
|
||||
print(f" [cron] acknowledgement failed: {error}")
|
||||
waiting_for_ack = []
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
@@ -666,9 +669,7 @@ def agent_loop(messages: list, context: dict | None = None):
|
||||
return context
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = execute_tool(block)
|
||||
results.append({
|
||||
"type": "tool_result",
|
||||
|
||||
@@ -1220,11 +1220,12 @@ class TeammateRuntime:
|
||||
|
||||
self.messages.append({"role": "assistant",
|
||||
"content": response.content})
|
||||
if response.stop_reason == "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if tool_calls:
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = _run_teammate_tool(
|
||||
self.name, block, self.handlers
|
||||
)
|
||||
@@ -1710,15 +1711,16 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
release_completed_assignment("agent")
|
||||
trigger_hooks("Stop", messages)
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
print(f"> {block.name}")
|
||||
output = execute_tool(block)
|
||||
print(output[:300])
|
||||
|
||||
@@ -487,14 +487,15 @@ def agent_loop(messages: list):
|
||||
return
|
||||
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason != "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if not tool_calls:
|
||||
trigger_hooks("Stop", messages)
|
||||
return
|
||||
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
print(f"> {block.name}")
|
||||
output = execute_tool(block, handlers)
|
||||
print(output[:300])
|
||||
|
||||
@@ -1499,11 +1499,12 @@ def spawn_teammate_thread(name: str, role: str, prompt: str,
|
||||
f"{type(exc).__name__}: {exc}", "error")
|
||||
break
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
if response.stop_reason == "tool_use":
|
||||
tool_calls = [
|
||||
block for block in response.content if block.type == "tool_use"
|
||||
]
|
||||
if tool_calls:
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
for block in tool_calls:
|
||||
output = _run_teammate_tool(name, block, sub_handlers)
|
||||
results.append({"type": "tool_result",
|
||||
"tool_use_id": block.id,
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Before Tools</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -33,7 +33,7 @@
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Tool 前</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@@ -41,7 +41,7 @@
|
||||
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,331 @@
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
LESSONS = tuple(
|
||||
ROOT / chapter / "code.py"
|
||||
for chapter in (
|
||||
"s01_agent_loop",
|
||||
"s02_tool_use",
|
||||
"s03_permission",
|
||||
"s04_hooks",
|
||||
"s05_todo_write",
|
||||
"s06_subagent",
|
||||
"s07_skill_loading",
|
||||
"s08_context_compact",
|
||||
"s09_memory",
|
||||
"s10_task_system",
|
||||
"s11_background_tasks",
|
||||
"s12_cron_scheduler",
|
||||
"s13_agent_teams",
|
||||
"s14_mcp_plugin",
|
||||
)
|
||||
)
|
||||
INTEGRATED_LESSON = ROOT / "s15_integrated_harness" / "code.py"
|
||||
|
||||
|
||||
class FakeMessagesApi:
|
||||
def __init__(self, responses):
|
||||
self.responses = list(responses)
|
||||
self.calls = 0
|
||||
|
||||
def create(self, **_kwargs):
|
||||
self.calls += 1
|
||||
if not self.responses:
|
||||
raise AssertionError("agent loop requested another model turn")
|
||||
return self.responses.pop(0)
|
||||
|
||||
|
||||
def load_lesson(workdir: Path, lesson_path: Path):
|
||||
fake_anthropic = types.ModuleType("anthropic")
|
||||
fake_dotenv = types.ModuleType("dotenv")
|
||||
|
||||
class FakeAnthropic:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.messages = FakeMessagesApi([])
|
||||
|
||||
fake_anthropic.Anthropic = FakeAnthropic
|
||||
fake_dotenv.load_dotenv = lambda override=True: None
|
||||
|
||||
previous_modules = {
|
||||
"anthropic": sys.modules.get("anthropic"),
|
||||
"dotenv": sys.modules.get("dotenv"),
|
||||
}
|
||||
previous_cwd = Path.cwd()
|
||||
previous_model = os.environ.get("MODEL_ID")
|
||||
module_name = f"agent_loop_boundary_{lesson_path.parent.name}_{time.time_ns()}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, lesson_path)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
|
||||
sys.modules["anthropic"] = fake_anthropic
|
||||
sys.modules["dotenv"] = fake_dotenv
|
||||
sys.modules[module_name] = module
|
||||
try:
|
||||
os.chdir(workdir)
|
||||
os.environ["MODEL_ID"] = "test-model"
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
finally:
|
||||
os.chdir(previous_cwd)
|
||||
if previous_model is None:
|
||||
os.environ.pop("MODEL_ID", None)
|
||||
else:
|
||||
os.environ["MODEL_ID"] = previous_model
|
||||
for name, previous in previous_modules.items():
|
||||
if previous is None:
|
||||
sys.modules.pop(name, None)
|
||||
else:
|
||||
sys.modules[name] = previous
|
||||
sys.modules.pop(module_name, None)
|
||||
|
||||
|
||||
def empty_tool_use_response(content=None):
|
||||
return types.SimpleNamespace(
|
||||
stop_reason="tool_use",
|
||||
content=(
|
||||
[types.SimpleNamespace(type="text", text="")]
|
||||
if content is None else content
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def disable_lesson_side_effects(lesson):
|
||||
if hasattr(lesson, "trigger_hooks"):
|
||||
lesson.trigger_hooks = lambda *_args, **_kwargs: None
|
||||
if hasattr(lesson, "inject_background_results"):
|
||||
lesson.inject_background_results = lambda _messages: None
|
||||
if hasattr(lesson, "consume_cron_queue"):
|
||||
lesson.consume_cron_queue = lambda: []
|
||||
if hasattr(lesson, "extract_memories"):
|
||||
lesson.extract_memories = lambda _messages: False
|
||||
if hasattr(lesson, "release_completed_assignment"):
|
||||
lesson.release_completed_assignment = lambda _owner: None
|
||||
if hasattr(lesson, "assemble_tool_pool"):
|
||||
lesson.assemble_tool_pool = lambda: ([], {})
|
||||
if hasattr(lesson, "assemble_system_prompt"):
|
||||
lesson.assemble_system_prompt = lambda: "test system"
|
||||
if hasattr(lesson, "COMPACTOR"):
|
||||
lesson.COMPACTOR.prepare = lambda messages, _request: messages
|
||||
|
||||
|
||||
def use_successful_bash_handler(lesson):
|
||||
if hasattr(lesson, "run_bash"):
|
||||
lesson.run_bash = lambda *_args, **_kwargs: "tool output"
|
||||
if hasattr(lesson, "check_permission"):
|
||||
lesson.check_permission = lambda _block: True
|
||||
if hasattr(lesson, "execute_tool"):
|
||||
lesson.execute_tool = lambda *_args, **_kwargs: "tool output"
|
||||
if hasattr(lesson, "TOOL_HANDLERS"):
|
||||
lesson.TOOL_HANDLERS["bash"] = lambda **_kwargs: "tool output"
|
||||
|
||||
|
||||
def bash_tool_call():
|
||||
return types.SimpleNamespace(
|
||||
type="tool_use",
|
||||
id="tool_1",
|
||||
name="bash",
|
||||
input={"command": "true"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lesson_path", LESSONS, ids=lambda path: path.parent.name)
|
||||
@pytest.mark.parametrize("content", ([], None), ids=("empty-content", "empty-text"))
|
||||
def test_parent_loop_does_not_append_an_empty_tool_result_turn(
|
||||
lesson_path: Path, content):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), lesson_path)
|
||||
disable_lesson_side_effects(lesson)
|
||||
api = FakeMessagesApi([empty_tool_use_response(content)])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
messages = [{"role": "user", "content": "hello"}]
|
||||
|
||||
if lesson_path.parent.name == "s08_context_compact":
|
||||
lesson.agent_loop(messages, "hello")
|
||||
else:
|
||||
lesson.agent_loop(messages)
|
||||
|
||||
assert api.calls == 1
|
||||
assert messages[-1]["role"] == "assistant"
|
||||
assert not any(
|
||||
message.get("role") == "user" and message.get("content") == []
|
||||
for message in messages
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("lesson_path", LESSONS, ids=lambda path: path.parent.name)
|
||||
def test_parent_loop_executes_a_real_tool_call_even_if_stop_reason_disagrees(
|
||||
lesson_path: Path):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), lesson_path)
|
||||
disable_lesson_side_effects(lesson)
|
||||
use_successful_bash_handler(lesson)
|
||||
api = FakeMessagesApi([
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[bash_tool_call()],
|
||||
),
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[types.SimpleNamespace(type="text", text="done")],
|
||||
),
|
||||
])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
messages = [{"role": "user", "content": "hello"}]
|
||||
|
||||
if lesson_path.parent.name == "s08_context_compact":
|
||||
lesson.agent_loop(messages, "hello")
|
||||
else:
|
||||
lesson.agent_loop(messages)
|
||||
|
||||
assert api.calls == 2
|
||||
tool_result_turns = [
|
||||
message for message in messages
|
||||
if message.get("role") == "user"
|
||||
and isinstance(message.get("content"), list)
|
||||
]
|
||||
assert len(tool_result_turns) == 1
|
||||
assert tool_result_turns[0]["content"][0]["tool_use_id"] == "tool_1"
|
||||
|
||||
|
||||
def test_subagent_stops_without_an_empty_tool_result_turn():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), ROOT / "s06_subagent" / "code.py")
|
||||
disable_lesson_side_effects(lesson)
|
||||
api = FakeMessagesApi([empty_tool_use_response()])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
|
||||
assert lesson.run_subagent("inspect the repository") == "(no summary)"
|
||||
assert api.calls == 1
|
||||
|
||||
|
||||
def test_subagent_still_executes_a_real_tool_call_with_text_present():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), ROOT / "s06_subagent" / "code.py")
|
||||
disable_lesson_side_effects(lesson)
|
||||
tool_call = types.SimpleNamespace(
|
||||
type="tool_use",
|
||||
id="tool_1",
|
||||
name="read_file",
|
||||
input={"path": "README.md"},
|
||||
)
|
||||
api = FakeMessagesApi([
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[types.SimpleNamespace(type="text", text=""), tool_call],
|
||||
),
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[types.SimpleNamespace(type="text", text="done")],
|
||||
),
|
||||
])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
lesson.execute_tool = lambda _block, _handlers: "tool output"
|
||||
|
||||
assert lesson.run_subagent("inspect the repository") == "done"
|
||||
assert api.calls == 2
|
||||
|
||||
|
||||
def test_s13_teammate_does_not_continue_with_an_empty_tool_result_turn():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), ROOT / "s13_agent_teams" / "code.py")
|
||||
api = FakeMessagesApi([empty_tool_use_response()])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
runtime = lesson.TeammateRuntime(
|
||||
"alice", "reviewer", "inspect the repository", None, False
|
||||
)
|
||||
|
||||
assert runtime.work() == "idle"
|
||||
assert api.calls == 1
|
||||
assert not any(
|
||||
message.get("role") == "user" and message.get("content") == []
|
||||
for message in runtime.messages
|
||||
)
|
||||
|
||||
|
||||
def test_s13_teammate_executes_a_real_tool_call_when_stop_reason_disagrees():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), ROOT / "s13_agent_teams" / "code.py")
|
||||
api = FakeMessagesApi([
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[bash_tool_call()],
|
||||
)
|
||||
])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
lesson._run_teammate_tool = lambda *_args: "tool output"
|
||||
runtime = lesson.TeammateRuntime(
|
||||
"alice", "reviewer", "inspect the repository", None, False
|
||||
)
|
||||
|
||||
assert runtime.work() == "continue"
|
||||
assert api.calls == 1
|
||||
assert runtime.messages[-1]["content"][0]["tool_use_id"] == "tool_1"
|
||||
|
||||
|
||||
def stop_s15_teammate_when_idle(lesson, name: str):
|
||||
deadline = time.monotonic() + 2
|
||||
while time.monotonic() < deadline:
|
||||
with lesson.team_lock:
|
||||
state = lesson.active_teammates.get(name)
|
||||
if state == "idle":
|
||||
lesson.run_request_shutdown(name)
|
||||
break
|
||||
if state is None:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
deadline = time.monotonic() + 2
|
||||
while time.monotonic() < deadline:
|
||||
with lesson.team_lock:
|
||||
if name not in lesson.active_teammates:
|
||||
return
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
def test_s15_teammate_does_not_request_another_turn_for_empty_tool_use():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), INTEGRATED_LESSON)
|
||||
api = FakeMessagesApi([empty_tool_use_response()])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
|
||||
lesson.spawn_teammate_thread("alice", "reviewer", "inspect the repository")
|
||||
stop_s15_teammate_when_idle(lesson, "alice")
|
||||
|
||||
assert api.calls == 1
|
||||
with lesson.team_lock:
|
||||
assert "alice" not in lesson.active_teammates
|
||||
|
||||
|
||||
def test_s15_teammate_executes_a_real_tool_call_when_stop_reason_disagrees():
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
lesson = load_lesson(Path(tmp), INTEGRATED_LESSON)
|
||||
api = FakeMessagesApi([
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[bash_tool_call()],
|
||||
),
|
||||
types.SimpleNamespace(
|
||||
stop_reason="end_turn",
|
||||
content=[types.SimpleNamespace(type="text", text="done")],
|
||||
),
|
||||
])
|
||||
lesson.client = types.SimpleNamespace(messages=api)
|
||||
calls = []
|
||||
lesson._run_teammate_tool = lambda *_args: calls.append("bash") or "ok"
|
||||
|
||||
lesson.spawn_teammate_thread("alice", "reviewer", "inspect the repository")
|
||||
stop_s15_teammate_when_idle(lesson, "alice")
|
||||
|
||||
assert api.calls == 2
|
||||
assert calls == ["bash"]
|
||||
with lesson.team_lock:
|
||||
assert "alice" not in lesson.active_teammates
|
||||
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">Model reads message history</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">Decision: Need a tool?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">Returns stop_reason signal</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">Returns content blocks</text>
|
||||
|
||||
<!-- Arrow: LLM → Decision (down) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== Decision Diamond ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">present?</text>
|
||||
|
||||
<!-- Arrow: No → End (right) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">モデルがメッセージ履歴を読む</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">判断:ツールが必要か?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">stop_reason シグナルを返す</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">content block を返す</text>
|
||||
|
||||
<!-- 矢印:LLM → 判定(下) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== 判定ダイヤモンド ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">あり?</text>
|
||||
|
||||
<!-- 矢印:いいえ → 終了(右) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -45,15 +45,15 @@
|
||||
<line x1="310" y1="176" x2="450" y2="176" stroke="#e2e8f0" stroke-width="1"/>
|
||||
<text x="380" y="194" fill="#475569" font-size="11" text-anchor="middle">模型阅读消息历史</text>
|
||||
<text x="380" y="210" fill="#475569" font-size="11" text-anchor="middle">判断:需要工具吗?</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">返回 stop_reason 信号</text>
|
||||
<text x="380" y="228" fill="#64748b" font-size="10" text-anchor="middle">返回内容块</text>
|
||||
|
||||
<!-- 箭头:LLM → 判断(向下) -->
|
||||
<line x1="380" y1="236" x2="380" y2="276" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== 判断菱形 ===== -->
|
||||
<polygon points="380,280 470,316 380,352 290,316" fill="#fff8f0" stroke="#d97706" stroke-width="2"/>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">stop_reason</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">== "tool_use"?</text>
|
||||
<text x="380" y="312" fill="#92400e" font-size="12" font-weight="600" text-anchor="middle">tool_use block</text>
|
||||
<text x="380" y="326" fill="#92400e" font-size="10" text-anchor="middle">存在?</text>
|
||||
|
||||
<!-- 箭头:否 → 结束(向右) -->
|
||||
<line x1="470" y1="316" x2="540" y2="316" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason check</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block check</text>
|
||||
|
||||
<!-- Arrow: LLM → Decision -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Decision Diamond -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → Return -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判定</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block 判定</text>
|
||||
|
||||
<!-- 矢印:LLM → 判定 -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 判定ダイヤモンド -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- いいえ → 返却 -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -40,14 +40,14 @@
|
||||
<!-- LLM -->
|
||||
<rect x="270" y="82" width="150" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="345" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">大模型 (LLM)</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason 判断</text>
|
||||
<text x="345" y="122" fill="#64748b" font-size="10" text-anchor="middle">检查 tool_use block</text>
|
||||
|
||||
<!-- 箭头:LLM → 判断 -->
|
||||
<line x1="345" y1="134" x2="345" y2="162" stroke="#555" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- 判断菱形 -->
|
||||
<polygon points="345,166 415,196 345,226 275,196" fill="#fff8f0" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use?</text>
|
||||
<text x="345" y="194" fill="#92400e" font-size="10" font-weight="600" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="415" y1="196" x2="475" y2="196" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → return -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → 戻る -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="230" y="84" width="130" height="48" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="295" y="104" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">stop_reason?</text>
|
||||
<text x="295" y="122" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="295" y1="132" x2="295" y2="156" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → Return -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → 返却 -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -39,7 +39,7 @@
|
||||
<!-- ② LLM -->
|
||||
<rect x="200" y="108" width="120" height="64" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="260" y="134" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="260" y="154" fill="#64748b" font-size="10" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM 否 → 返回 -->
|
||||
<line x1="260" y1="172" x2="260" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → Return -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- No → 返却 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -36,7 +36,7 @@
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → Return -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM No → 返却 -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -85,7 +85,7 @@
|
||||
<!-- ===== ③ LLM ===== -->
|
||||
<rect x="440" y="132" width="100" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="490" y="155" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
<text x="490" y="172" fill="#64748b" font-size="9" text-anchor="middle">tool_use block?</text>
|
||||
|
||||
<!-- LLM 否 → 返回 -->
|
||||
<line x1="490" y1="184" x2="490" y2="278" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → no → return result -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → no → return result -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -57,8 +57,8 @@
|
||||
<!-- ===== LLM ===== -->
|
||||
<rect x="475" y="96" width="80" height="52" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="515" y="114" fill="#1e3a5f" font-size="14" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">stop_reason</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">=tool_use?</text>
|
||||
<text x="515" y="132" fill="#64748b" font-size="9" text-anchor="middle">tool_use</text>
|
||||
<text x="515" y="144" fill="#64748b" font-size="9" text-anchor="middle">block?</text>
|
||||
|
||||
<!-- LLM → 否 → 返回结果 -->
|
||||
<line x1="515" y1="148" x2="515" y2="178" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
@@ -33,7 +33,7 @@
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Before Tools</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -33,7 +33,7 @@
|
||||
<line x1="375" y1="152" x2="417" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<rect x="575" y="118" width="150" height="68" rx="7" fill="#fff7ed" stroke="#ea580c" stroke-width="1.6"/>
|
||||
<text x="650" y="140" text-anchor="middle" fill="#9a3412" font-size="11" font-weight="700">Tool 前</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@@ -41,7 +41,7 @@
|
||||
|
||||
<rect x="420" y="118" width="110" height="68" rx="7" fill="#fff" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<text x="475" y="146" text-anchor="middle" fill="#1e3a5f" font-size="12" font-weight="700">LLM</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">stop_reason=tool_use?</text>
|
||||
<text x="475" y="164" text-anchor="middle" fill="#64748b" font-size="8.5">tool_use block?</text>
|
||||
|
||||
<line x1="530" y1="152" x2="572" y2="152" stroke="#475569" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |