chore: Spring AI 重构

This commit is contained in:
abel533
2026-03-25 00:15:00 +08:00
parent a9c71002d2
commit 2afa4712cb
124 changed files with 11777 additions and 3530 deletions
+70 -42
View File
@@ -1,4 +1,4 @@
# s10: Team Protocols
# s10: Team Protocols (チームプロトコル)
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12`
@@ -8,13 +8,13 @@
## 問題
s09ではチームメイトが作業し通信するが、構造化された協調がない:
s09 ではチームメイトが作業し通信するが、構造化された協調がない:
**シャットダウン**: スレッドを強制終了するとファイルが中途半端に書かれ、config.jsonが不正な状態になる。ハンドシェイクが必要 -- リーダーが要求し、チームメイトが承認(完了して退出)か拒否(作業継続)する。
**シャットダウン**: スレッドを強制終了するとファイルが中途半端に書かれ、config.json が不正な状態になる。ハンドシェイクが必要 -- リーダーが要求し、チームメイトが承認完了して退出か拒否作業継続する。
**プラン承認**: リーダーが「認証モジュールをリファクタリングして」と言うと、チームメイトは即座に開始する。リスクの高い変更では、実行前にリーダーが計画をレビューすべきだ。
**プラン承認**: リーダーが「認証モジュールをリファクタリングして」と言うと、チームメイトは即座に開始する。リスクの高い変更では、実行前にレビューすべきだ。
両方とも同じ構造: 一方がユニークIDを持つリクエストを送り、他方がそのIDで応答する。
両方とも同じ構造: 一方がユニーク ID を持つリクエストを送り、他方がその ID で応答する。
## 解決策
@@ -42,65 +42,93 @@ Trackers:
## 仕組み
1. リーダーがrequest_idを生成し、インボックス経由でシャットダウンを開始する。
1. リーダーが request_id を生成し、インボックス経由でシャットダウンを開始する。
```python
shutdown_requests = {}
```java
// src/main/java/io/mybatis/learn/s10/ProtocolTracker.java
// Python は辞書 + threading.Lock を使用、Java は ConcurrentHashMap で天然スレッドセーフ
private final ConcurrentHashMap<String, Map<String, String>> shutdownRequests
= new ConcurrentHashMap<>();
def handle_shutdown_request(teammate: str) -> str:
req_id = str(uuid.uuid4())[:8]
shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
BUS.send("lead", teammate, "Please shut down gracefully.",
"shutdown_request", {"request_id": req_id})
return f"Shutdown request {req_id} sent (status: pending)"
public String handleShutdownRequest(String teammate) {
String reqId = UUID.randomUUID().toString().substring(0, 8);
shutdownRequests.put(reqId, new ConcurrentHashMap<>(Map.of(
"target", teammate, "status", "pending")));
bus.send("lead", teammate, "Please shut down gracefully.",
"shutdown_request", Map.of("request_id", reqId));
return "Shutdown request " + reqId + " sent to '" + teammate
+ "' (status: pending)";
}
```
2. チームメイトがリクエストを受信し、承認または拒否で応答する。
```python
if tool_name == "shutdown_response":
req_id = args["request_id"]
approve = args["approve"]
shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
BUS.send(sender, "lead", args.get("reason", ""),
"shutdown_response",
{"request_id": req_id, "approve": approve})
```java
// TeammateProtocolTool - チームメイトが @Tool アノテーションでシャットダウン要求に応答
@Tool(description = "Respond to a shutdown request")
public String shutdownResponse(
@ToolParam(description = "The request_id") String requestId,
@ToolParam(description = "true to approve") boolean approve,
@ToolParam(description = "Reason for decision") String reason) {
return tracker.respondToShutdown(name, requestId, approve, reason);
}
// ProtocolTracker - トラッカー更新 + レスポンスメッセージ送信
public String respondToShutdown(String sender, String requestId,
boolean approve, String reason) {
var req = shutdownRequests.get(requestId);
if (req != null) {
req.put("status", approve ? "approved" : "rejected");
}
bus.send(sender, "lead", reason != null ? reason : "",
"shutdown_response",
Map.of("request_id", requestId, "approve", approve));
return "Shutdown " + (approve ? "approved" : "rejected");
}
```
3. プラン承認も同一パターン。チームメイトがプランを提出(request_idを生成)、リーダーがレビュー(同じrequest_idを参照)
3. プラン承認もまったく同じパターン。チームメイトがプランを提出request_id を生成、リーダーがレビュー同じ request_id を参照
```python
plan_requests = {}
```java
// ProtocolTracker - 同じ request_id 関連パターン、2つの用途
private final ConcurrentHashMap<String, Map<String, String>> planRequests
= new ConcurrentHashMap<>();
def handle_plan_review(request_id, approve, feedback=""):
req = plan_requests[request_id]
req["status"] = "approved" if approve else "rejected"
BUS.send("lead", req["from"], feedback,
"plan_approval_response",
{"request_id": request_id, "approve": approve})
public String reviewPlan(String requestId, boolean approve, String feedback) {
var req = planRequests.get(requestId);
if (req == null) return "Error: Unknown plan request_id '" + requestId + "'";
req.put("status", approve ? "approved" : "rejected");
bus.send("lead", req.get("from"), feedback != null ? feedback : "",
"plan_approval_response",
Map.of("request_id", requestId, "approve", approve,
"feedback", feedback != null ? feedback : ""));
return "Plan " + req.get("status") + " for '" + req.get("from") + "'";
}
```
1つのFSM、2つの用。同じ`pending -> approved | rejected`状態機械が、あらゆるリクエスト-レスポンスプロトコルに適用できる。
1つの FSM、2つの用。同じ `pending -> approved | rejected` 状態機械が、あらゆるリクエスト-レスポンスプロトコルに適用できる。
## s09からの変更点
## s09 からの変更点
| Component | Before (s09) | After (s10) |
|----------------|------------------|------------------------------|
| Tools | 9 | 12 (+shutdown_req/resp +plan)|
| Shutdown | Natural exit only| Request-response handshake |
| Plan gating | None | Submit/review with approval |
| Correlation | None | request_id per request |
| FSM | None | pending -> approved/rejected |
| コンポーネント | 変更前 (s09) | 変更後 (s10) |
|----------------|------------------|--------------------------------------|
| Tools | 9 | 12 (+shutdown_req/resp +plan) |
| シャットダウン | 自然終了のみ | リクエスト-レスポンスハンドシェイク |
| プランゲーティング | なし | 提出/レビューと承認 |
| 関連付け | なし | リクエストごとに request_id |
| FSM | なし | pending -> approved/rejected |
## 試してみる
```sh
cd learn-claude-code
python agents/s10_team_protocols.py
mvn exec:java -Dexec.mainClass=io.mybatis.learn.s10.S10TeamProtocols
```
以下のプロンプトを試してみよう (英語プロンプトの方が LLM に効果的だが、日本語でも可):
1. `Spawn alice as a coder. Then request her shutdown.`
2. `List teammates to see alice's status after shutdown approval`
3. `Spawn bob with a risky refactoring task. Review and reject his plan.`
4. `Spawn charlie, have him submit a plan, then approve it.`
5. `/team`と入力してステータスを監視する
5. `/team` と入力してステータスを監視する