Skip to content

Commit 5c91dc1

Browse files
SonAIengineclaude
andcommitted
fix: Anthropic SSE 파싱 — content_block_stop에서 텍스트 중복 삽입 버그 수정
- content_block_stop이 tool_use 블록 이후에도 이전 텍스트를 중복 push - 원인: current_text를 push 후 clear 안 함 + 현재 블록 타입 미추적 - current_block_is_text 플래그 추가 → 텍스트 블록일 때만 push + 즉시 clear - tool_result ID 매칭 안전장치 추가 — 누락된 tool_use_id에 placeholder 생성 - 증상: [text, tool_use, text_dup, tool_use, text_dup] → API에서 tool_result 매칭 실패 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1e56e76 commit 5c91dc1

1 file changed

Lines changed: 29 additions & 1 deletion

File tree

src-tauri/src/services/llm_client.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
140140
let mut buffer = String::new();
141141
let mut content_blocks: Vec<Value> = Vec::new();
142142
let mut current_text = String::new();
143+
let mut current_block_is_text = false;
143144
let mut stop_reason: Option<String> = None;
144145

145146
while let Some(chunk) = stream.next().await {
@@ -158,6 +159,7 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
158159
"content_block_start" => {
159160
let block = &event["content_block"];
160161
if block["type"].as_str() == Some("tool_use") {
162+
current_block_is_text = false;
161163
content_blocks.push(serde_json::json!({
162164
"type": "tool_use",
163165
"id": block["id"],
@@ -170,6 +172,7 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
170172
data: serde_json::json!({"name": block["name"]}),
171173
});
172174
} else {
175+
current_block_is_text = true;
173176
current_text.clear();
174177
}
175178
}
@@ -193,11 +196,14 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
193196
}
194197
}
195198
"content_block_stop" => {
196-
if !current_text.is_empty() {
199+
// Only push text block when the current block IS a text block
200+
if current_block_is_text && !current_text.is_empty() {
197201
content_blocks.push(serde_json::json!({
198202
"type": "text", "text": current_text.clone(),
199203
}));
204+
current_text.clear();
200205
}
206+
current_block_is_text = false;
201207
}
202208
"message_delta" => {
203209
if let Some(r) = event["delta"]["stop_reason"].as_str() {
@@ -826,6 +832,28 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
826832
}
827833
}
828834

835+
// Validate: every tool_use must have a matching tool_result
836+
let tool_use_ids: Vec<String> = content.iter()
837+
.filter(|b| b["type"].as_str() == Some("tool_use"))
838+
.filter_map(|b| b["id"].as_str().map(|s| s.to_string()))
839+
.collect();
840+
let tool_result_ids: Vec<String> = tool_results.iter()
841+
.filter_map(|r| r["tool_use_id"].as_str().map(|s| s.to_string()))
842+
.collect();
843+
log::info!("tool_use IDs: {:?}, tool_result IDs: {:?}", tool_use_ids, tool_result_ids);
844+
845+
// Safety: add placeholder tool_result for any missing IDs
846+
for use_id in &tool_use_ids {
847+
if !tool_result_ids.contains(use_id) {
848+
log::warn!("Missing tool_result for tool_use_id: {}", use_id);
849+
tool_results.push(serde_json::json!({
850+
"type": "tool_result",
851+
"tool_use_id": use_id,
852+
"content": "Error: tool execution skipped",
853+
}));
854+
}
855+
}
856+
829857
messages.push(ChatMessage {
830858
role: "user".into(),
831859
content: Value::Array(tool_results),

0 commit comments

Comments
 (0)