Task #36

๐Ÿ”ง Bug Fix Report: `tool_use ids without tool_result` 400 ์—๋Ÿฌ

์ž‘์—… ๋ช…๋ น์–ด
[๐Ÿ”ด HIGH] telegram_bot.py์˜ _chat_with_tools ํ•จ์ˆ˜์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ˜๋ณต์ ์ธ 400 ์—๋Ÿฌ ("tool_use ids were found without tool_result blocks") ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜๋ผ. ## ๋ฌธ์ œ ๋ถ„์„ `_chat_with_tools` ๋ฃจํ”„ (1677~1816๋ผ์ธ)์—์„œ Claude API๊ฐ€ `web_search` (server_tool_use) ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ 400 ์—๋Ÿฌ๊ฐ€ ๋ฐ˜๋ณต ๋ฐœ์ƒํ•œ๋‹ค. ### ํ•ต์‹ฌ ์›์ธ 1. **`web_search_tool_result`๊ฐ€ ์ด๋ฏธ assistant content์— ํฌํ•จ๋˜๋Š” ์ผ€์ด์Šค ๋ฏธ์ฒ˜๋ฆฌ** - Claude API๋Š” `web_search` ์‚ฌ์šฉ ์‹œ, `server_tool_use` ๋ธ”๋ก **AND** `web_search_tool_result` ๋ธ”๋ก์„ **๋ชจ๋‘ assistant message content์— ํฌํ•จ**ํ•ด์„œ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Œ - ํ˜„์žฌ ์ฝ”๋“œ(1700~1714๋ผ์ธ)์—์„œ `web_search_tool_result` ๋ธ”๋ก ์ฒ˜๋ฆฌ: ```python elif block.type == "web_search_tool_result": assistant_content.append(block.model_dump()) ``` - ์ด ์ผ€์ด์Šค๊ฐ€ ๋งž๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ ๊ฐ™์ง€๋งŒ, `pending_server_ids` ์ˆ˜์ง‘ ์‹œ ์ด๋ฏธ ๊ฒฐ๊ณผ๊ฐ€ ํฌํ•จ๋œ `server_tool_use` ID๊นŒ์ง€ ํฌํ•จํ•ด์„œ dummy๋ฅผ ์ค‘๋ณต ์ฃผ์ž…ํ•œ๋‹ค. 2. **`pending_server_ids` ๋กœ์ง ์˜ค๋ฅ˜ (1750~1766๋ผ์ธ)** ```python pending_server_ids = [ b["id"] for b in assistant_content if isinstance(b, dict) and b.get("type") == "server_tool_use" ] ``` - `assistant_content`์— `server_tool_use`๊ฐ€ ์žˆ์œผ๋ฉด **๋ฌด์กฐ๊ฑด** dummy `web_search_tool_result`๋ฅผ ์‚ฝ์ž…ํ•จ - ํ•˜์ง€๋งŒ ๊ฐ™์€ assistant message content ์•ˆ์— ์ด๋ฏธ ํ•ด๋‹น ID์˜ `web_search_tool_result`๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ์ด๋ฅผ ์ฒดํฌํ•˜์ง€ ์•Š์•„ ๋ถˆํ•„์š”ํ•œ dummy๊ฐ€ ์ƒ์„ฑ๋จ - ์ด๋กœ ์ธํ•ด working_msgs์— ๊ตฌ์กฐ์  ๋ชจ์ˆœ์ด ์Œ“์—ฌ ๋‹ค์Œ API ํ˜ธ์ถœ ์‹œ 400 ๋ฐœ์ƒ 3. **`_ensure_tool_results` ํ•จ์ˆ˜์˜ ๋งน์  (1559~1647๋ผ์ธ)** - `assistant_content` ๋‚ด๋ถ€์—์„œ `server_tool_use`์™€ `web_search_tool_result`๊ฐ€ ํ•จ๊ป˜ ์žˆ๋Š” ์ผ€์ด์Šค๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•จ - resolved_server ์ฒดํฌ๋ฅผ ๋‹ค์Œ user message์—์„œ๋งŒ ํ•˜๋Š”๋ฐ, ์‹ค์ œ result๊ฐ€ ๊ฐ™์€ assistant message ์•ˆ์— ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ๋†“์นจ ## ์ˆ˜์ • ๋ฐฉ๋ฒ• ### Fix 1: `pending_server_ids` ๋กœ์ง ์ˆ˜์ • (1750๋ผ์ธ ๋ถ€๊ทผ) `server_tool_use` ID ์ค‘์—์„œ **๊ฐ™์€ assistant_content ์•ˆ์— ์ด๋ฏธ `web_search_tool_result`๋กœ ํ•ด๊ฒฐ๋œ ๊ฒƒ**์€ ์ œ์™ธํ•˜๊ณ , ์ง„์งœ pending๋งŒ ์ฒ˜๋ฆฌ: ```python # assistant_content ์•ˆ์—์„œ ์ด๋ฏธ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ๋Š” server tool IDs resolved_in_content = { b.get("tool_use_id") for b in assistant_content if isinstance(b, dict) and b.get("type") == "web_search_tool_result" } pending_server_ids = [ b["id"] for b in assistant_content if isinstance(b, dict) and b.get("type") == "server_tool_use" and b["id"] not in resolved_in_content ] ``` ### Fix 2: `_ensure_tool_results` ์ˆ˜์ • (1559๋ผ์ธ ๋ถ€๊ทผ) `_ensure_tool_results`์—์„œ server_ids ํ•ด๊ฒฐ ์—ฌ๋ถ€ ์ฒดํฌ ์‹œ, **๊ฐ™์€ assistant message content ์•ˆ์˜ `web_search_tool_result`**๋„ resolved๋กœ ์ธ์ •: ```python # server_tool_use IDs resolved IN the same assistant message content resolved_in_same_msg = { b.get("tool_use_id") for b in content if isinstance(b, dict) and b.get("type") == "web_search_tool_result" } server_ids = [ b["id"] for b in content if isinstance(b, dict) and b.get("type") == "server_tool_use" and b["id"] not in resolved_in_same_msg # ์ด๋ฏธ ํ•ด๊ฒฐ๋œ ๊ฑด ์ œ์™ธ ] ``` ## ๊ตฌํ˜„ ์ง€์นจ 1. `read_file("telegram_bot.py", 1559, 1647)` ๋กœ `_ensure_tool_results` ์žฌํ™•์ธ 2. `read_file("telegram_bot.py", 1697, 1780)` ๋กœ tool processing ๋ฃจํ”„ ์žฌํ™•์ธ 3. ์œ„ ๋‘ ๊ณณ์„ ๋ชจ๋‘ ์ˆ˜์ •ํ•œ ์™„์„ฑ ๋ฒ„์ „์„ `write_file`๋กœ ์ ์šฉ 4. ์ˆ˜์ •์€ **ํ•ด๋‹น ํ•จ์ˆ˜๋งŒ** ํƒ€๊ฒŸํŒ… โ€” ์ „์ฒด ํŒŒ์ผ ์žฌ์ž‘์„ฑ ๊ธˆ์ง€ 5. ์ˆ˜์ • ํ›„ `/errors` ๋กœ๊ทธ์™€ ํŒจํ„ด์„ ํ™•์ธํ•ด์„œ ๋™์ผ ์—๋Ÿฌ๊ฐ€ ์žฌ๋ฐœํ•˜์ง€ ์•Š๋Š”์ง€ ๊ฒ€์ฆ ๋ฐฉ๋ฒ•์„ ๋ฉ”๋ชจ์— ๋‚จ๊ธธ ๊ฒƒ ## ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ ์ค‘๋™ ์ „์Ÿ ์—์Šค์ปฌ๋ ˆ์ด์…˜ ๋“ฑ web_search๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฆฌ์„œ์น˜ ํƒœ์Šคํฌ์—์„œ 400 ์—๋Ÿฌ ์—†์ด ์™„์ฃผํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.