[End-Gamers] AWS Bedrock ์ฐ๋ ์ค ๋ง๋ URL ์ด์ค ์ธ์ฝ๋ฉ
๋ค์ด๊ฐ๋ฉฐ
fix ์ปค๋ฐ์ด 5๊ฐ ๋์๋ค.
incident-autopsy์ AWS Bedrock์ ๋ถ์ด๋ ์์
์ด์๋ค.
Claude API๋ฅผ ์ง์ ์ฐ๋ฉด ํ ๋ฒ์ ๋๊ฒ ์ง๋ง,
ํ ํ๊ฒฝ ํน์ฑ์ Bedrock์ ํตํด์ผ ํ๋ค. ๊ฐ๋จํ ์ค ์์๋๋ฐ ์๊ฐ๋ณด๋ค ์ค๋ ๊ฑธ๋ ธ๋ค.
์ด ๊ธ์ ๊ทธ 5๊ฐ ์ปค๋ฐ ๋์ ๋ญ ๋ง๋ฌ๋์ง ๊ธฐ๋ก์ด๋ค.

1. ์ค๊ณ ๊ตฌ์กฐ — AI ์ ๊ณต์ฌ๋ฅผ ๊ฐ์๋ผ์ธ ์ ์๊ฒ
์ฒ์๋ถํฐ AI ์ ๊ณต์ฌ๋ฅผ ๋ฐ๊ฟ ์ ์๋ ๊ตฌ์กฐ๋ก ์ค๊ณํ๋ค. ํ๋ง๋ค ์ฐ๋ ํ๊ฒฝ์ด ๋ค๋ฅด๊ณ , Bedrock์ด ์ ๋๋ ์ํฉ์์ Groq์ผ๋ก ๋น ๋ฅด๊ฒ ํ ์คํธํด์ผ ํ ๋๋ ์์ผ๋๊น.
ai/base.py ← ์ธํฐํ์ด์ค ์ ์
ai/claude_client.py ← Anthropic ์ง์ ํธ์ถ
ai/groq_client.py ← Groq
ai/bedrock_client.py ← AWS Bedrock
AIDispatcher๊ฐ ์ค์ ๊ฐ์ ๋ฐ๋ผ ํด๋ผ์ด์ธํธ๋ฅผ ๊ณจ๋ผ์ฃผ๊ณ , ์๋น์ค ๋ ์ด์ด๋ ์ด๋ค AI๋ฅผ ์ฐ๋์ง ๋ชฐ๋ผ๋ ๋๋ค. ์ด ๊ตฌ์กฐ ๋๋ถ์ ๋์ค์ Groq → Bedrock ๊ต์ฒด๊ฐ ์ค์ ํ ์ค ๋ฐ๊พธ๋ ๊ฒ์ผ๋ก ๊ฐ๋ฅํ๋ค.
2. invoke_model์ด ๋ด๋ฟ์ ์ฒซ ๋ฒ์งธ ์๋ฌ
Bedrock ์ฒซ ์ฐ๋์ AnthropicBedrock SDK๋ก ์๋ํ๋ค.
๊ทผ๋ฐ ํ AWS ํ๊ฒฝ์์ MFA ์๊ฒฉ์ฆ๋ช ์ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ๋ค. ๋ฐ๋ก boto3๋ก ๊ต์ฒดํ๋ค.
response = boto3_client.invoke_model(
modelId="anthropic.claude-sonnet-4-6",
body=json.dumps(payload)
)
3. URL ์ด์ค ์ธ์ฝ๋ฉ
invoke_model์ด modelId๋ฅผ URL ๊ฒฝ๋ก์ ์ง์ ์ฝ์
ํ๋ ๋ฐฉ์์ ์ฐ๋๋ฐ,
์ด ๊ณผ์ ์์ botocore๊ฐ ํ ๋ฒ ๋ ์ธ์ฝ๋ฉ์ ํ๋ค.
anthropic.claude-sonnet-4-6
↓ invoke_model์ด URL ๊ฒฝ๋ก์ ์ฝ์
anthropic.claude-sonnet-4-6
↓ botocore๊ฐ ์ถ๊ฐ ์ธ์ฝ๋ฉ
anthropic%2Eclaude-sonnet-4-6 ← Bedrock์ด ์ธ์ ๋ถ๊ฐ
.์ด %2E๋ก ๋ฐ๋๋ฉด์ Bedrock์ด ๋ชจ๋ธ ID๋ฅผ ๋ชป ์ฝ๋ ๊ฒ์ด๋ค.
๋ชจ๋ธ ID ์์ฒด๋ ๋ง๋๋ฐ ์๋ฒ๊น์ง ๊ฐ๋ ๊ณผ์ ์์ ๋ง๊ฐ์ก๋ค.
4. ํด๊ฒฐ ๊ณผ์
์ฒ์์๋ URL ์ธ์ฝ๋ฉ์ ์ฐํํ๋ ค๊ณ botocore์ AWS Signature V4๋ฅผ ์ง์ ๊บผ๋ด์ httpx๋ก Bedrock์ ํธ์ถํ๋ ๋ฐฉ์์ ์๋ํ๋ค. ๋์์ ํ๋ค. ๊ทผ๋ฐ botocore ๋ด๋ถ๋ฅผ ๊ฑด๋๋ฆฌ๋ ์ฝ๋๋ ๋ฒ์ ์ด ๋ฐ๋๋ฉด ์ธ์ ํฐ์ง์ง ๋ชฐ๋ผ์ ํฌ๊ธฐํ๋ค.
converse API๋ก ๊ต์ฒดํ๋ ๊ฒ ์ ๋ต์ด์๋ค.
response = boto3_client.converse(
modelId="anthropic.claude-sonnet-4-6",
messages=[{"role": "user", "content": [{"text": prompt}]}],
inferenceConfig={"maxTokens": 2048}
)
invoke_model์ modelId๋ฅผ URL ๊ฒฝ๋ก์ ๋ฌธ์์ด๋ก ๋ฐ์ ๋ฃ์ง๋ง, converse๋ ๋ด๋ถ ์ฒ๋ฆฌ ๋ฐฉ์์ด ๋ฌ๋ผ์ ์ด์ค ์ธ์ฝ๋ฉ์ด ์๊ธฐ์ง ์๋๋ค. ์๋ฌ๊ฐ ์ฌ๋ผ์ก๋ค.
5. ์ฐ๊ฒฐ๋๋๋ JSON ํ์ฑ ์คํจ
Bedrock ์ฐ๊ฒฐ์ ๋๋๋ฐ ์ด๋ฒ์ ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ํ์ฑ์ด ์ ๋๋ค.
AI์๊ฒ JSON์ ๋ฑ์ผ๋ผ๊ณ ํ๋กฌํํธ๋ฅผ ์คฌ๋๋ฐ, ์๋ต์ ๋งํฌ๋ค์ด ์ฝ๋ ๋ธ๋ก์ด ๊ฐ์ธ์ ธ์ ์๋ค.
```json
{
"severity": "HIGH",
...
}
์ด๊ฑธ `json.loads()`์ ๊ทธ๋ฅ ๋ฃ์ผ๋ฉด ํฐ์ง๋ค.
ํ๋กฌํํธ ๋ฌธ์ ์ธ๊ฐ ์ถ์ด์ ๊ณ ์ณ๋ดค๋๋ฐ ์ผ๊ด์ฑ์ด ์์๋ค.
Claude, Groq, Bedrock์ด ๋งํฌ๋ค์ด์ ๋ถ์ด๋ ๊ธฐ์ค์ด ์ ๊ฐ๊ฐ์ด์๋ค. ์
๊ณต์ฌ๋ง๋ค ๋ค๋ฅด๊ณ , ๊ฐ์ ์ ๊ณต์ฌ์์๋ ์์ฒญ ๋ด์ฉ์ ๋ฐ๋ผ ๋ฌ๋ผ์ก๋ค.
6. depth ์นด์ดํฐ๋ก JSON ์ถ์ถ
์ ๊ท์์ผ๋ก JSON ๋ถ๋ถ๋ง ๋ฝ๋ ๊ฒ๋ ํ๊ณ๊ฐ ์์๋ค. ์ค์ฒฉ๋ JSON์์ ๋ซํ ๊ดํธ๊ฐ ์ด๋์ธ์ง ์ ํํ ๋ชป ์ฐพ๋๋ค.
๋ฐ๋ผ์, `{`์ `}`์ ๊ฐ์๋ฅผ ์ธ๋ depth ์นด์ดํฐ ๋ฐฉ์์ผ๋ก ๋ง๋ค์๋ค.
def extract_json(text: str) -> dict:
text = re.sub(r'```json\s*', '', text)
text = re.sub(r'```\s*', '', text)
start = text.find('{')
depth = 0
for i, ch in enumerate(text[start:], start):
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
return json.loads(text[start:i+1])
{๋ฅผ ๋ง๋๋ฉด depth +1, }๋ฅผ ๋ง๋๋ฉด depth -1. depth๊ฐ 0์ด ๋๋ ์๊ฐ์ด JSON ๋์ด๋ค. ๋งํฌ๋ค์ด์ด ์๋ค์ ๋ญ ๋ถ์ด๋ ์๊ด์์ด ๋์ํ๋ค.
์ด๊ฑธ extract_json ์ ํธ๋ก ๋นผ์ ๋ชจ๋ AI ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ด ์ฐ๊ฒ ํ๋ค.
7. Groq์ด Tool Use๋ฅผ ์ ์ฐ๋ ๋ฌธ์
Bedrock Tool Use ๊ตฌ์กฐ์ Groq Tool Use ๊ตฌ์กฐ๊ฐ ๋ฌ๋๋ค.
# Bedrock
{"toolUse": {"toolUseId": "...", "name": "analyze", "input": {...}}}
# Groq
{"tool_calls": [{"function": {"name": "analyze", "arguments": "..."}}]}
ํ์ฑ ์ฝ๋๋ฅผ ํด๋ผ์ด์ธํธ๋ณ๋ก ๋ฐ๋ก ๋ง๋ค์๋ค. ๊ทธ๋ฆฌ๊ณ Groq์ Tool Use๋ฅผ ์ ์ฐ๊ณ ๊ทธ๋ฅ ํ ์คํธ๋ก ์๋ตํด๋ฒ๋ฆฌ๋ ์ผ์ด์ค๊ฐ ์์๋ค. ์์คํ ํ๋กฌํํธ์ "๋ฐ๋์ ํจ์๋ฅผ ํธ์ถํ๋ผ"๋ ์ง์นจ์ ๋ช ์์ ์ผ๋ก ์ถ๊ฐํด์ผ ํ๋ค.
8. ๋ง๋ฌด๋ฆฌ
๊ฒฐ๊ตญ Claude, Groq, Bedrock ์ธ ์ ๊ณต์ฌ๊ฐ ๊ฐ์ ์ธํฐํ์ด์ค๋ก ๋์ํ๊ฒ ๋๋ค. ์ ๊ณต์ฌ๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด ์ค์ ํ ์ค๋ง ๊ณ ์น๋ฉด ๋๋ค. ์ด ์ ์ฐ์ฑ์ ๋์ค์ Haiku 4.5๋ก ๋ชจ๋ธ์ ๊ต์ฒดํ ๋ ๋น์ ๋ฐํ๋ค.
Bedrock์ boto3 ์ค์์๋ ์ด๋ค API๋ฅผ ์ฐ๋์ง์ ๋ฐ๋ผ ๋ด๋ถ ๋์์ด ๋ค๋ฅด๋ค๋ ๊ฑธ ์ด๋ฒ์ ์์๋ค. ๊ณต์ ๋ฌธ์์ ์ ๋์ค์ง ์๋ ์ข ๋ฅ์ ํจ์ ์ด๋ค.
์๋ฌ ๋ฉ์์ง๊ฐ "invalid model identifier"๋ผ๊ณ ํด๋,
๋ชจ๋ธ ID๊ฐ ์๋ URL ์ธ์ฝ๋ฉ ๋ฌธ์ ์ผ ์ ์๋ค.
๊ฐ์ฅ ์์ชฝ์ ์์ฌํ๊ธฐ ์ ์ ๊ฐ์ฅ ๋ฐ๊นฅ์ชฝ์ ๋จผ์ ํ์ธํ์.
End-Gamers | incident-autopsy



