๋ผ์ด๋ธ ์์ด์ ํธ๋ฅผ ์ํ ๊ฐ๋๋ ์ผ¶
๊ฐ๋๋ ์ผ์ ํ๋ก๋์ ํ๊ฒฝ์์ ์ค์๊ฐ ์์ฑ ์์ด์ ํธ๊ฐ ์ด๋ป๊ฒ ๋์ํ ์ง๋ฅผ ํต์ ํ์ฌ ๋ํ๊ฐ ์ฃผ์ ์์ ๋ฒ์ด๋์ง ์๊ณ (on-topic), ์ ์ฑ ์ ์ค์ํ๋ฉฐ(on-policy), ์์ (safe)ํ๊ฒ ์ ์ง๋๋๋ก ํฉ๋๋ค. ์ค์๊ฐ ์ฐ๊ฒฐ์์๋ ์ค๋์ค๊ฐ ์ง์์ ์ผ๋ก ์คํธ๋ฆฌ๋ฐ๋๊ณ ๋ชจ๋ธ์ด ์ค์๊ฐ์ผ๋ก ๋ฐํ๋ฅผ ์์ํฉ๋๋ค. ADK๋ ๋ํ ์ง์ฐ ์๊ฐ(latency)์ ๋ฏธ์น๋ ์ํฅ์ ์ต์ํํ๋ฉด์ ๋ํ๋ฅผ ์์ ํ๊ฒ ๋ณดํธํ๊ธฐ ์ํด ํจ๊ป ์๋ํ๋ ๋ค์ค ๊ณ์ธต ๋ณดํธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
์ผ๋ฐ์ ์ธ ์ฝ๋ฐฑ ๋ฉ์ปค๋์ฆ ๋ฐ ํ๋ฌ๊ทธ์ธ ๋ฑ๋ก ๋ฐฉ๋ฒ์ ์ฝ๋ฐฑ ์ ํ ๋ฐ ํ๋ฌ๊ทธ์ธ์ ์ฐธ์กฐํ์ธ์.
๊ฐ๋๋ ์ผ ๊ณ์ธต¶
| ๊ณ์ธต | ์ญํ | ๋ณดํธ ์ด์ | ์ง์ฐ ์๊ฐ ์ํฅ |
|---|---|---|---|
| ์์คํ ์ง์นจ (System instructions) | ํค, ๋ํ ๊ท์น ๋ฐ ๊ฒฝ๊ณ๋ฅผ ํ์ฑ | ํ๋ ๊ฐ์ด๋ | ์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐ ์ง์ฐ ์๊ฐ ์์ |
| ์์ ์ค์ (Safety settings) | ์ฝํ ์ธ ์์ ์ ๋ํ ํ๋ซํผ ์๊ณ๊ฐ์ ์ ์ฉ | ํ๋ซํผ ์์ ํํฐ | ์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐ ์ง์ฐ ์๊ฐ ์์ |
| ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ (Input validation) | ํ๋กฌํํธ ์ธ์ ์ ๋ฐ ๊ธ์ง๋ ์ฃผ์ ๊ฐ์ง | ์ฌ์ฉ์ ์ ๋ ฅ ๊ฒ์ฆ | ์ต์ํ (ํด๋น ์คํ) |
| ์๋ต ์ ํจ์ฑ ๊ฒ์ฌ (Response validation) | ๋น์ฆ๋์ค ์ ์ฑ ์ ๋ฐ๋ผ ์์ด์ ํธ ์๋ต์ ์ ๋ณ | ์์ด์ ํธ ์๋ต ๊ฒ์ฆ | ๊ฒ์ฌ ๋ฐฉ์์ ๋ฐ๋ผ ๋ค๋ฆ |
| ๋๊ตฌ ๊ฐ๋๋ ์ผ (Tool guardrails) | ๋๊ตฌ ์คํ์ ๊ฐ๋ก์ฑ๊ณ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฒ์ฆ | ๋๊ตฌ ์คํ ์์ ์ฑ | ์ต์ํ (๋๊ตฌ ํธ์ถ๋น ์คํ) |
์ด๋ฌํ ๊ณ์ธต๋ค์ ํจ๊ป ์ฐ๊ณ๋์ด ์ฌ์ธต ๋ฐฉ์ด(defense in depth)๋ฅผ ์ ๊ณตํฉ๋๋ค. ์์คํ ์ง์นจ์ ์์ฐ์ค๋ฌ์ด ๋ํ ํ๋ฆ์ ํ์ฑํ๊ณ , ํ๋ซํผ ์์ ํํฐ๋ ๊ธฐ๋ณธ์ ์ธ ์ ํด์ฑ ๊ฒฝ๊ณ๋ฅผ ์ ์ฉํ๋ฉฐ, ์ ํ๋ฆฌ์ผ์ด์ ๊ฒ์ฆ๊ธฐ๋ ์ ๋ ฅ, ์๋ต ๋ฐ ๋๊ตฌ ํธ์ถ์ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฒ์ฆํฉ๋๋ค. ๋ช ํํ ์ง์นจ๊ณผ ๋ ๋ฆฝ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ฒฐํฉํ๋ฉด ํ ๊ณ์ธต์ ์ฐํํ๋ ์์ธ์ ์ธ ์ผ์ด์ค(edge case)๊ฐ ๋ฐ์ํ๋๋ผ๋ ๋ค๋ฅธ ๊ณ์ธต์์ ์ด๋ฅผ ์ฐจ๋จํ ์ ์์ต๋๋ค.
์ง์นจ ๋ฐ ์์ ์ค์ ¶
์ง์นจ๊ณผ ์์ ์ค์ ์ ์ธ์ ์ ์ฒด์ ๊ธฐ์ค ๋์์ ์ค์ ํฉ๋๋ค. ๋ ์ค์ ๋ชจ๋ ์ธ์ ์ ์์ฑํ ๋ ์์ด์ ํธ์ ๊ตฌ์ฑํฉ๋๋ค.
from google.adk.agents import Agent
from google.genai import types
root_agent = Agent(
model='gemini-live-2.5-flash-native-audio',
name='support_agent',
instruction=(
'You help customers with billing questions. '
'Never quote a price; offer to transfer to sales instead. '
'Never discuss a competitor.'
),
generate_content_config=types.GenerateContentConfig(
safety_settings=[
types.SafetySetting(
category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold=types.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
),
]
),
)
๋ชจ๋ธ์ ๋ํ ์ปจํ ์คํธ๊ฐ ๋์ ๋จ์ ๋ฐ๋ผ ๋ชจ๋ ํด์ ๊ฑธ์ณ ์์คํ ์ง์นจ์ ํ๊ฐํฉ๋๋ค. ์ค์๊ฐ ์์ด์ ํธ๋ฅผ ์ํ ์ง์นจ์ ์ค๊ณํ ๋๋ ํ๋ฅด์๋, ๋ํ ๊ท์น, ๋ํ ๊ฐ๋๋ ์ผ์ ์์๋๋ก ์ ์ํ์ธ์. ์๊ฒฉํ ์ ์ฝ ์กฐ๊ฑด์ ๊ฒฝ์ฐ ๋์์ ์ ์ํ๊ฑฐ๋ ๋ฐ์ ์๋ฅผ ์๋ด์์๊ฒ ํธ์ ํ(transfer)ํ๋ ๋ฑ ๊ฒฝ๊ณ ์กฐ๊ฑด์ ๋ถ๋ชํ์ ๋ ์ํํด์ผ ํ ๋ช ์์ ์ธ ์ฒ๋ฆฌ ๊ท์น์ ์ ๊ณตํ์ธ์.
์์ ์ค์ ์ ์ค์๊ฐ ์ธ์ ์ ์์ ๋ฐ ์ฝํ ์ธ ํํฐ๋ฅผ ์ ์ฉํฉ๋๋ค. ๋ชจ๋ธ์ด ๊ตฌ์ฑ๋ ์๊ณ๊ฐ์ ์ด๊ณผํ๋ ์ฝํ ์ธ ๋ฅผ ์์ฑํ๋ฉด ํ๋ซํผ์์ ์์ฑ์ ์ค๋จํฉ๋๋ค.
์ค์๊ฐ ์ฐ๊ฒฐ์ ์์ ์์ ์ ์ด๋ฌํ ์ค์ ์ ์ค์ ํ๋ฏ๋ก ์ธ์ ์ง์ ์๊ฐ ๋์ ๊ณ ์ ๋ ์ํ๋ก ์ ์ง๋ฉ๋๋ค. ์ธ์ ์ํ๋ฅผ ๋ณด๊ฐ(interpolate)ํ๋ ์ง์นจ ๋ฌธ์์ด์ ์ธ์ ์ด ์์๋ ๋ ํ ๋ฒ ๋ ๋๋ง๋๋ฉฐ, ์ฌ๊ฐ๋ ์ฐ๊ฒฐ์ ํด๋น ๋ ๋๋ง๋ ์ง์นจ์ ์ฌ์ฌ์ฉํฉ๋๋ค.
๋ํ ์ ํจ์ฑ ๊ฒ์ฌ¶
๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์ฌ์ฉ์์ ๋ฐํ์ ์์ด์ ํธ์ ์๋ต์ ๋ ๋ฆฝ์ ์ผ๋ก ํ๊ฐํฉ๋๋ค. ๊ด๋ฆฌํ ์ํฐํ๋ผ์ด์ฆ ์ ์ฑ ์ ๊ฒฝ์ฐ, Model Armor ํ๋ฌ๊ทธ์ธ์ด Google Cloud ํ ํ๋ฆฟ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋กฌํํธ ์ธ์ ์ , ๋ฏผ๊ฐํ ๋ฐ์ดํฐ ๋ฐ ์ ์ฑ ์๋ฐ ์ฌํญ์ ์ ๋ณํฉ๋๋ค. ADK ๋ชจ๋ธ ์ฝ๋ฐฑ์ ์ฌ์ฉํ์ฌ ์ปค์คํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ ์๋ ์์ต๋๋ค.
์ฌ์ฉ์ ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ¶
before_model_callback ํ
์ ์ฌ์ฉ์ ์
๋ ฅ์ ๊ฐ๋ก์ฑ๋๋ค. ์ด ์ฝ๋ฐฑ์ ์
๋ ฅ๋ ํ
์คํธ๊ฐ ๋ชจ๋ธ์ ๋๋ฌํ๊ธฐ ์ ์ ํ๊ฐํ๋ฏ๋ก, ์ฐจ๋จ ์ ์ฐ๊ฒฐ์ ์ ์งํ๋ฉด์๋ ๋ฉ์์ง๊ฐ ์ปจํ
์คํธ์ ํฌํจ๋์ง ์๋๋ก ๋ฐฉ์งํฉ๋๋ค. ์์ฑ ์ค๋์ค์ ๊ฒฝ์ฐ ์ฝ๋ฐฑ์ ๋ชจ๋ธ์ด ์ด๋ฏธ ์ค๋์ค๋ฅผ ์์ ํ ํ์ ์ ์ฌ๋ ๋ฐํ ์ ์ฒด๋ฅผ ๊ฒ์ฌํฉ๋๋ค. ์ฌ๊ธฐ์ ์ฐจ๋จ์ด ๋ฐ์ํ๋ฉด ๋ผ์ด๋ธ ์ธ์
์ ๋ค์ ์์ํ์ฌ ์ด๋ฏธ ์ ์ก ์ค์ธ ์๋ต์ ์ญ์ ํฉ๋๋ค.
๊ฒ์ฌ์์ ์ ์ฑ
์๋ฐ์ด ํ์ธ๋๋ฉด LlmResponse๋ฅผ ๋ฐํํ์ฌ ์ฌ์ฉ์์ ํด์ ์์ ํ ๋์ฒด ์๋ต์ผ๋ก ๊ต์ฒดํฉ๋๋ค.
from typing import Optional
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.genai import types
def block_input(
callback_context: CallbackContext,
llm_request: LlmRequest,
) -> Optional[LlmResponse]:
"""๊ฒฝ์์ฌ๋ฅผ ์ธ๊ธํ๋ ์ฌ์ฉ์ ํด์ ์ฐจ๋จํฉ๋๋ค."""
text = ''.join(
part.text or ''
for content in llm_request.contents
for part in content.parts or []
)
if 'competitor' not in text.lower():
return None
return LlmResponse(
content=types.Content(
role='model',
parts=[types.Part(text="I can't discuss that.")],
)
)
์์ด์ ํธ ์๋ต ์ ํจ์ฑ ๊ฒ์ฌ¶
after_model_callback ํ
์ ๋ชจ๋ธ ์๋ต์ด ์ฌ์ฉ์์๊ฒ ์ ๋ฌ๋๊ธฐ ์ ์ด๋ ์ ๋ฌ๋๋ ๋์ค์ ์ด๋ฅผ ํ์ธํฉ๋๋ค. ์ ํจ์ฑ ๊ฒ์ฌ๋ ์์ฑ ํด ์ค์ ๋์ ๋๋ ์ ์ฌ ํ
์คํธ๋ฅผ ๊ฒ์ฌํ์ฌ ์๋ฐ ์ฌํญ์ ์กฐ๊ธฐ์ ํฌ์ฐฉํ๊ฑฐ๋, ์ ์ฒด ํด์ ์ ์ฌ ํ
์คํธ๋ฅผ ํ๊ฐํ ์ ์์ต๋๋ค.
def block_output(
callback_context: CallbackContext,
llm_response: LlmResponse,
) -> Optional[LlmResponse]:
"""๋ต๋ณ์ ๊ฐ๊ฒฉ์ด ์ธ๊ธ๋๋ฉด ์๋ต์ ๋์ฒดํฉ๋๋ค."""
transcription = llm_response.output_transcription
text = transcription.text if transcription else ''
if '$' not in text:
return None
return LlmResponse(
content=types.Content(
role='model',
parts=[types.Part(text='Let me connect you with sales.')],
)
)
์ถ๋ ฅ ์ฝ๋ฐฑ์์ LlmResponse๋ฅผ ๋ฐํํ๋ฉด ๋ชจ๋ธ ์์ฑ์ ์ฆ์ ์ค๋จํ๊ณ ๋์ฒด ๋ฉ์์ง๋ฅผ ์ฌ์ฉ์์๊ฒ ์ ๋ฌํฉ๋๋ค. ๋ชจ๋ธ์ด ์ด๋ฏธ ์
๋ ฅ ์ฒ๋ฆฌ๋ฅผ ์์ํ ์์ฑ ํด์ ๊ฒฝ์ฐ, ๊ฑฐ๋ถ๋ ์ฝํ
์ธ ๊ฐ ๋ํ ์ปจํ
์คํธ์ ๋จ์ ์์ง ์๋๋ก ADK๊ฐ ํ์ฌ ํด์ ์ง์๋๋ค.
๋๊ตฌ ๊ฐ๋๋ ์ผ¶
๋๊ตฌ ์ฝ๋ฐฑ์ ์ธ๋ถ ์์คํ
์ ๋ณดํธํ๊ณ ์์
์ ๊ฒ์ฆํฉ๋๋ค. before_tool_callback ํ
์ ๋๊ตฌ๊ฐ ์คํ๋๊ธฐ ์ ์ ์ธ์๋ฅผ ๊ฒ์ฌํ์ฌ ์น์ธ๋์ง ์์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฑฐ๋ถํ๊ฑฐ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ ์ฉํ ์ ์๋๋ก ํฉ๋๋ค. after_tool_callback ํ
์ ๊ฒฐ๊ณผ๊ฐ ๋ชจ๋ธ๋ก ๋ฐํ๋๊ธฐ ์ ์ ๋ฏผ๊ฐํ ๊ฒฐ๊ณผ๋ฅผ ์ญ์ (redact)ํฉ๋๋ค.
from typing import Any, Optional
from google.adk.tools import BaseTool
from google.adk.tools import ToolContext
def validate_refund(
tool: BaseTool,
args: dict[str, Any],
tool_context: ToolContext,
) -> Optional[dict[str, Any]]:
"""์๊ณ๊ฐ์ ์ด๊ณผํ๋ ์น์ธ๋์ง ์์ ํ๋ถ์ ๋ฐฉ์งํฉ๋๋ค."""
if tool.name == 'issue_refund' and args.get('amount', 0) > 100:
return {'error': 'Refund exceeds automatic approval limit.'}
return None
๋๊ตฌ ์ฝ๋ฐฑ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ฉด ๋๊ตฌ ์คํ์ ์ฐํํ๊ณ ๋ฐํ๊ฐ์ ๋ชจ๋ธ์ ์ง์ ์ ๊ณตํฉ๋๋ค. ์์ธํ ๋ด์ฉ์ ๋๊ตฌ ์คํ ์ฝ๋ฐฑ์ ์ฐธ์กฐํ์ธ์.
์ค์๊ฐ ์ค๋์ค ์คํธ๋ฆผ์ ์ธํฐ๋ํฐ๋ธํ ์ฌ๋์ ํ์ธ์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ์ํด ์ผ์ ์ค์ง๋ ์ ์์ต๋๋ค. ์ค์๊ฐ ์์ด์ ํธ๊ฐ ๋์ ๊ถํ์ด ํ์ํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ฝ๋ฐฑ ๋ด์์ ์ธ์๋ฅผ ์๋์ผ๋ก ๊ฒ์ฆํ๊ฑฐ๋ ์์ฒญ์ ์ฌ๋์๊ฒ ์ธ๊ณ(handoff)ํ๋๋ก ๋ผ์ฐํ ํ์ธ์.
์๊ตฌ์ฌํญ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก¶
- ์ ์ฌ(Transcription) ํ์ฑ ์ํ ์ ์ง: ํ
์คํธ ๊ธฐ๋ฐ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์์ฑ ์ ์ฌ์ ์์กดํฉ๋๋ค.
RunConfig.input_audio_transcription๊ณผRunConfig.output_audio_transcription์ ๋ชจ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋์ด ์์ต๋๋ค. ๋ ์ค ํ๋๋ฅผNone์ผ๋ก ์ค์ ํ๋ฉด ํด๋น ๊ฒ๋ณ ๊ณ์ธต์ด ๋นํ์ฑํ๋ฉ๋๋ค. - ๊ฒ์ฆ๊ธฐ ๊ฒฝ๋ํ ์ ์ง: ๋ผ์ด๋ธ ์์ ๋ฃจํ์ ์ฝ๋ฐฑ์ ์ค๋์ค ์ฒ๋ฆฌ์ ์ธ๋ผ์ธ์ผ๋ก ์คํ๋ฉ๋๋ค. ๋น ๋ฅธ ๋ก์ปฌ ๊ฒ์ฌ๋ฅผ ํตํด ๋ํ์ ์๋ต์ฑ์ ์ ์งํ์ธ์. ๋ ๋ฌด๊ฑฐ์ด ๊ฒ์ฌ์ ๊ฒฝ์ฐ ์ ์ฒด ํด ์ ์ฌ๋ฅผ ํ๊ฐํ๊ฑฐ๋ ์ฅ์๊ฐ ์คํ๋๋ ๋ถ์์ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋ก๋ํ์ธ์.
- ์์ด์ ํธ ์ ๋ฐ์ ๊ฑธ์น ์ ์ฑ
์ ์ฉ:
App์ Model Armor์ ๊ฐ์ ํ๋ฌ๊ทธ์ธ์ ๋ฑ๋กํ๋ฉด ์ฝ๋ฐฑ ์ฝ๋๋ฅผ ์ค๋ณตํ์ง ์๊ณ ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ ์์ด์ ํธ์ ์ผ๊ด๋ ๋ณด์ ๊ท์น์ ์ ์ฉํ ์ ์์ต๋๋ค.