์ฝ˜ํ…์ธ ๋กœ ์ด๋™

๋ผ์ด๋ธŒ ์—์ด์ „ํŠธ๋ฅผ ์œ„ํ•œ ๊ตฌ์„ฑ

ADK์—์„œ ์ง€์›Python v0.1.0Java v0.2.0

RunConfig๋Š” ๋ผ์ด๋ธŒ ์„ธ์…˜์˜ ๋™์ž‘์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ์˜ ์Œ์„ฑ ํ†ค, ์Œ์„ฑ ์ „์‚ฌ ๋ฐฉ์‹, ํ„ด ์ข…๋ฃŒ ํŒ๋‹จ ์‹œ์ , ์œ ์ง€ํ•  ๋Œ€ํ™” ํžˆ์Šคํ† ๋ฆฌ์˜ ์–‘, ์ ์šฉํ•  ํ•œ๋„ ๋“ฑ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ Runner.run_live()์— ์ „๋‹ฌํ•˜๋ฉด ํ•ด๋‹น ์„ธ์…˜์—๋งŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋™์ผํ•œ ์—์ด์ „ํŠธ๋ผ๋„ ๋‘ ๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ตฌ์„ฑ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RunConfig๋Š” ๋ผ์ด๋ธŒ ์ „์šฉ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„ ๊ตฌ์„ฑ์—์„œ๋Š” ์ „์ฒด ํด๋ž˜์Šค์™€ run_async()์— ์ ์šฉ๋˜๋Š” ํ•„๋“œ๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ run_live()์—์„œ ์ค‘์š”ํ•œ ํ•˜์œ„ ์ง‘ํ•ฉ๊ณผ ๋ผ์ด๋ธŒ ์„ธ์…˜์—๋งŒ ์กด์žฌํ•˜๋Š” ์Œ์„ฑ ๊ด€๋ จ ์„ค์ •์ž…๋‹ˆ๋‹ค.

RunConfig ๋งค๊ฐœ๋ณ€์ˆ˜ ๋น ๋ฅธ ์ฐธ์กฐ

๋‹ค์Œ ํ‘œ๋Š” ๋ผ์ด๋ธŒ ์—์ด์ „ํŠธ์™€ ๊ฐ€์žฅ ๊ด€๋ จ์„ฑ์ด ๋†’์€ RunConfig ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ๋น ๋ฅธ ์ฐธ์กฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž… ๋ชฉ์  ์ฐธ์กฐ
response_modalities list[str] ์ถœ๋ ฅ ํ˜•์‹. ๋ผ์ด๋ธŒ ์—์ด์ „ํŠธ๋Š” ๋ฐ˜๋“œ์‹œ AUDIO๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ โ€” ๋ผ์ด๋ธŒ ๋ชจ๋ธ์€ TEXT๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Œ ์„ธ๋ถ€์ •๋ณด
streaming_mode StreamingMode run_async() ๊ฒฝ๋กœ์—์„œ์˜ ์ฒญํฌ ๋˜๋Š” ๋‹จ์ผ ์ƒท ์ „๋‹ฌ; run_live()์—์„œ๋Š” ์ฝํžˆ์ง€ ์•Š์Œ ์„ธ๋ถ€์ •๋ณด
session_resumption SessionResumptionConfig ์ž๋™ ์žฌ์—ฐ๊ฒฐ ํ™œ์„ฑํ™” ์„ธ๋ถ€์ •๋ณด
context_window_compression ContextWindowCompressionConfig ๋ฌด์ œํ•œ ์„ธ์…˜ ์ง€์† ์‹œ๊ฐ„ ์ง€์› ์„ธ๋ถ€์ •๋ณด
history_config HistoryConfig ์ด์ „ ๋Œ€ํ™” ๊ธฐ๋ก์ด ๋ผ์ด๋ธŒ ์„œ๋ฒ„์— ์žฌ์ƒ๋˜๋Š” ๋ฐฉ์‹ ์ œ์–ด ์„ธ๋ถ€์ •๋ณด
max_llm_calls int ์„ธ์…˜๋‹น ์ด LLM ํ˜ธ์ถœ ์ˆ˜ ์ œํ•œ ์„ธ๋ถ€์ •๋ณด
save_live_blob bool ์˜ค๋””์˜ค/๋น„๋””์˜ค ์ŠคํŠธ๋ฆผ ์˜๊ตฌ ์ €์žฅ ์„ธ๋ถ€์ •๋ณด
custom_metadata dict[str, Any] ํ˜ธ์ถœ ์ด๋ฒคํŠธ์— ์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒจ๋ถ€ ์„ธ๋ถ€์ •๋ณด
speech_config SpeechConfig ์Œ์„ฑ ๋ฐ ์–ธ์–ด ๊ตฌ์„ฑ ์Œ์„ฑ ๋ฐ ์–ธ์–ด
input_audio_transcription AudioTranscriptionConfig ์‚ฌ์šฉ์ž ์Œ์„ฑ ์ „์‚ฌ ์˜ค๋””์˜ค ์ „์‚ฌ
output_audio_transcription AudioTranscriptionConfig ๋ชจ๋ธ ์Œ์„ฑ ์ „์‚ฌ ์˜ค๋””์˜ค ์ „์‚ฌ
realtime_input_config RealtimeInputConfig VAD ๊ตฌ์„ฑ ์Œ์„ฑ ํ™œ๋™ ๊ฐ์ง€
explicit_vad_signal bool ๋ชจ๋ธ๋กœ๋ถ€ํ„ฐ ์Œ์„ฑ ํ™œ๋™ ์ด๋ฒคํŠธ ๋ฐฉ์ถœ ์„ธ๋ถ€์ •๋ณด
proactivity ProactivityConfig ๋Šฅ๋™์  ์˜ค๋””์˜ค ํ™œ์„ฑํ™” (๋ชจ๋ธ๋ณ„ ์ง€์›) ๋Šฅ๋™์  ๋ฐ ๊ฐ์ •์  ๋Œ€ํ™”
enable_affective_dialog bool ๊ฐ์ • ์ ์‘ ํ™œ์„ฑํ™” (๋ชจ๋ธ๋ณ„ ์ง€์›) ๋Šฅ๋™์  ๋ฐ ๊ฐ์ •์  ๋Œ€ํ™”
translation_config TranslationConfig ์‹ค์‹œ๊ฐ„ ์Œ์„ฑ ๋Œ€ ์Œ์„ฑ ๋ฒˆ์—ญ (๋ฒˆ์—ญ ๋ชจ๋ธ ์ „์šฉ) ์„ธ๋ถ€์ •๋ณด
avatar_config AvatarConfig ์—์ด์ „ํŠธ๋ฅผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์•„๋ฐ”ํƒ€๋กœ ๋ Œ๋”๋ง ์„ธ๋ถ€์ •๋ณด

๊ตฌ์„ฑ ์˜ต์…˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ Python API ์ฐธ์กฐ์˜ RunConfig๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

Import ๊ฒฝ๋กœ:

์œ„ ํ‘œ์—์„œ ์ฐธ์กฐ๋œ ๋ชจ๋“  ๊ตฌ์„ฑ ํƒ€์ž… ํด๋ž˜์Šค๋Š” google.genai.types์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค:

from google.genai import types
from google.adk.agents.run_config import RunConfig, StreamingMode

# types ๋ชจ๋“ˆ์„ ํ†ตํ•ด ๊ตฌ์„ฑ ํƒ€์ž…์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค
run_config = RunConfig(
    session_resumption=types.SessionResumptionConfig(),
    context_window_compression=types.ContextWindowCompressionConfig(...),
    speech_config=types.SpeechConfig(...),
    # ๋“ฑ๋“ฑ
)

RunConfig ํด๋ž˜์Šค ์ž์ฒด์™€ StreamingMode enum์€ google.adk.agents.run_config์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์‘๋‹ต ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ (Response modes)

response_modalities ์„ค์ •์€ ์ถœ๋ ฅ ํ˜•์‹์„ ์ œ์–ดํ•˜๋ฉฐ, ์„ธ์…˜๋‹น ์ •ํ™•ํžˆ ํ•˜๋‚˜์˜ ํ˜•์‹์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ ์—์ด์ „ํŠธ์˜ ๊ฒฝ์šฐ ๊ฐ’์€ ํ•ญ์ƒ ["AUDIO"]์ž…๋‹ˆ๋‹ค. ADK๊ฐ€ ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ๋ผ์ด๋ธŒ ๋ชจ๋ธ์€ ๋‹ค๋ฅธ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ฐ’์„ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ADK๊ฐ€ ์ž๋™์œผ๋กœ ์ฑ„์›Œ์ฃผ๋ฏ€๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๋ผ์ด๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์ด ํ•„๋“œ๋ฅผ ์ง์ ‘ ๋‹ค๋ฃฐ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

response_modalities=["TEXT"] ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์•ˆ๋‚ด

์ด์ „ ADK ์ƒ˜ํ”Œ๊ณผ ์ ˆ๋ฐ˜ ๊ณ„๋‹จ์‹(half-cascade) ๋ชจ๋ธ์€ ํ…์ŠคํŠธ ์ „์šฉ ๋ผ์ด๋ธŒ ์„ธ์…˜์„ ํ—ˆ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ๋” ์ด์ƒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ["TEXT"]๋กœ run_live()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์˜ค๋””์˜ค๋งŒ ์ƒ์„ฑํ•˜๋Š” ์ตœ์‹  ๋ผ์ด๋ธŒ ๋ชจ๋ธ์—์„œ๋Š” ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ ์—์ด์ „ํŠธ์—์„œ ํ…์ŠคํŠธ๋ฅผ ์–ป์œผ๋ ค๋ฉด event.output_transcription์„ ์ฝ์œผ์„ธ์š”. ADK์—์„œ๋Š” ์ „์‚ฌ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ response_modalities ๋ผ์ธ์„ ์‚ญ์ œํ•˜๊ธฐ๋งŒ ํ•ด๋„ ๋Œ€๋ถ€๋ถ„ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค.

ํ‘œ์ค€ Gemini ๋ชจ๋ธ์—์„œ ์‹คํ–‰๋˜๋Š” run_async() ๊ฒฝ๋กœ์—์„œ๋Š” ["TEXT"]๊ฐ€ ์—ฌ์ „ํžˆ ์˜ฌ๋ฐ”๋ฅธ ์„ค์ •์ž…๋‹ˆ๋‹ค. ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ ๋˜๋Š” SSE๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์‘๋‹ต ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ๋Š” ๋ชจ๋ธ์˜ ์ถœ๋ ฅ์—๋งŒ ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. ๋ชจ๋ธ์ด ํ•ด๋‹น ์ž…๋ ฅ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ๋ฅผ ์ง€์›ํ•œ๋‹ค๋ฉด ์ด์— ๊ด€๊ณ„์—†์ด ํ•ญ์ƒ ํ…์ŠคํŠธ, ์Œ์„ฑ ๋˜๋Š” ๋น„๋””์˜ค ์ž…๋ ฅ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ ๋˜๋Š” SSE

ADK๋Š” ๋‘ ๊ฐ€์ง€ ์„œ๋กœ ๋‹ค๋ฅธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด Gemini์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ˜ธ์ถœํ•˜๋Š” Runner ๋ฉ”์„œ๋“œ์— ๋”ฐ๋ผ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ๊ฒฐ์ •๋ฉ๋‹ˆ๋‹ค:

  • runner.run_live(): ADK๋Š” Live API(live.connect()๋ฅผ ํ†ตํ•œ ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ ์—”๋“œํฌ์ธํŠธ)์— ๋Œ€ํ•œ WebSocket์„ ์—ฝ๋‹ˆ๋‹ค. ์ด ๊ฐ€์ด๋“œ์˜ ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์—์„œ ๋‹ค๋ฃจ๋Š” ๋‚ด์šฉ์ด๋ฉฐ ์‹ค์‹œ๊ฐ„ ์˜ค๋””์˜ค ๋ฐ ๋น„๋””์˜ค์— ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.
  • runner.run_async(): ADK๋Š” ํ‘œ์ค€ Gemini API(generate_content_async()๋ฅผ ํ†ตํ•œ ๋‹จํ•ญ/์ŠคํŠธ๋ฆฌ๋ฐ ์—”๋“œํฌ์ธํŠธ)์— HTTP๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฒญํฌ ๋‹จ์œ„๋กœ ์‘๋‹ต์„ ๋‹ค์‹œ ์ŠคํŠธ๋ฆฌ๋ฐํ•˜๋ ค๋ฉด RunConfig.streaming_mode = StreamingMode.SSE๋กœ ์„ค์ •ํ•˜์„ธ์š”.

๋‘ ๋ชจ๋ธ ์„ธํŠธ๋Š” ๊ฑฐ์˜ ๊ฒน์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. gemini-flash-latest์™€ ๊ฐ™์€ ํ‘œ์ค€ Gemini ๋ชจ๋ธ์€ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ง€์› ๋ชจ๋ธ์˜ ๋ชจ๋ธ๋“ค์€ run_live()๋กœ ๊ตฌ๋™ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์œผ๋ฏ€๋กœ ๋ชจ๋ธ ์„ ํƒ์€ ๊ณง Runner ๋ฉ”์„œ๋“œ ์„ ํƒ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

Python: StreamingMode.BIDI๋Š” ADK๋ฅผ Live API๋กœ ์ „ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

Python์—์„œ RunConfig.streaming_mode๋Š” run_async() ์ฝ”๋“œ ๊ฒฝ๋กœ์—์„œ๋งŒ ์ฝํžˆ๋ฉฐ, ๋‹จ์ผ ์™„๋ฃŒ ์‘๋‹ต(StreamingMode.NONE, ๊ธฐ๋ณธ๊ฐ’)๊ณผ ์ฒญํฌ ๋‹จ์œ„ ์ „๋‹ฌ(StreamingMode.SSE) ์‚ฌ์ด๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. run_live() ๊ฒฝ๋กœ๋Š” ์ด๋ฅผ ์ „ํ˜€ ์ฝ์ง€ ์•Š์œผ๋ฏ€๋กœ streaming_mode=StreamingMode.BIDI๋ฅผ ์„ค์ •ํ•ด๋„ ์•„๋ฌด๋Ÿฐ ํšจ๊ณผ๊ฐ€ ์—†์œผ๋ฉฐ ์กฐ์šฉํžˆ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค. ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด run_live()๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ADK ์ž์ฒด์˜ Python StreamingMode ๋…์ŠคํŠธ๋ง์—์„œ๋„ BIDI๋Š” "ํ‘œ์ค€ ์‹คํ–‰ ๊ฒฝ๋กœ์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ", ์‹ค์ œ ์–‘๋ฐฉํ–ฅ ๋™์ž‘์€ "streaming_mode์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ์ฝ”๋“œ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค"๊ณ  ๋ช…์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Java๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ADK Java์˜ ํ๋ฆ„์€ StreamingMode.BIDI๋ฅผ ์‹ค์ œ๋กœ ์ฝ์œผ๋ฉฐ, Java ํ€ต์Šคํƒ€ํŠธ๋Š” runLive()์— ์ „๋‹ฌํ•˜๋Š” RunConfig์— ์ด๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์–ธ์–ด ๊ฐ„์— ์„ค์ •์„ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์ง€ ๋ง๊ณ  ๊ฐ ์–ธ์–ด์˜ ํ€ต์Šคํƒ€ํŠธ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”.

# Live API: streaming_mode๊ฐ€ ํ•„์š” ์—†์œผ๋ฉฐ, run_live() ํ˜ธ์ถœ ์ž์ฒด๊ฐ€ ์ด๋ฅผ ์„ ํƒํ•จ
run_config = RunConfig(response_modalities=["AUDIO"])
async for event in runner.run_live(..., run_config=run_config):
    ...

์ด ์„ ํƒ์€ ADK๊ฐ€ Gemini์™€ ํ†ต์‹ ํ•˜๋Š” ๋ฐฉ์‹์—๋งŒ ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ–ฅํ•œ ์•„ํ‚คํ…์ฒ˜๋Š” ๋…๋ฆฝ์ ์ž…๋‹ˆ๋‹ค. ์–ด๋А ๊ฒฝ๋กœ์—์„œ๋“  WebSocket ์„œ๋ฒ„, REST API ๋˜๋Š” SSE ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

run_async() ๋ฐ SSE ๊ฒฝ๋กœ(streaming_mode ๊ฐ’, ํ”„๋กœ๊ทธ๋ ˆ์‹œ๋ธŒ SSE ์ŠคํŠธ๋ฆฌ๋ฐ, ์–ธ์–ด๋ณ„ ๊ตฌ์„ฑ)์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ๋Ÿฐํƒ€์ž„ ๊ตฌ์„ฑ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๊ธฐํƒ€ ์ œ์–ด ์˜ต์…˜

ADK๋Š” ์„ธ์…˜ ๋™์ž‘์„ ์ œ์–ดํ•˜๊ณ , ๋น„์šฉ์„ ๊ด€๋ฆฌํ•˜๋ฉฐ, ๋””๋ฒ„๊น… ๋ฐ ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๋ชฉ์ ์œผ๋กœ ์˜ค๋””์˜ค ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€ RunConfig ์˜ต์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

run_config = RunConfig(
    # ํ˜ธ์ถœ๋‹น ์ด LLM ํ˜ธ์ถœ ์ œํ•œ
    max_llm_calls=500,  # ๊ธฐ๋ณธ๊ฐ’: 500 (๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€)
                        # 0 ๋˜๋Š” ์Œ์ˆ˜ = ๋ฌด์ œํ•œ (์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉ)

    # ๋””๋ฒ„๊น…/์ปดํ”Œ๋ผ์ด์–ธ์Šค๋ฅผ ์œ„ํ•œ ์˜ค๋””์˜ค/๋น„๋””์˜ค ์•„ํ‹ฐํŒฉํŠธ ์ €์žฅ
    save_live_blob=True,  # ๊ธฐ๋ณธ๊ฐ’: False

    # ์ด๋ฒคํŠธ์— ์ปค์Šคํ…€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ฒจ๋ถ€
    custom_metadata={"user_tier": "premium", "session_type": "support"},  # ๊ธฐ๋ณธ๊ฐ’: None
)

max_llm_calls

max_llm_calls๋Š” ํ˜ธ์ถœ ์ปจํ…์ŠคํŠธ๋‹น LLM ํ˜ธ์ถœ ์ˆ˜๋ฅผ ์ œํ•œํ•˜๋ฉฐ, ๋Ÿฐํƒ€์ž„ ๊ตฌ์„ฑ์— ์ „์ฒด ๋‚ด์šฉ์ด ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์„ค์ •์€ run_live()์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” run_async() ๊ฒฝ๋กœ๋งŒ ๋ณดํ˜ธํ•˜๋ฏ€๋กœ ๋ผ์ด๋ธŒ ์„ธ์…˜์€ ์ด๋ฅผ ํ†ตํ•œ ์ž๋™ ๋น„์šฉ ์ƒํ•œ์„ ์–ป์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์„ธ์…˜ ์ง€์† ์‹œ๊ฐ„์„ ์ œํ•œํ•˜๊ณ , ํ„ด ์ˆ˜๋ฅผ ์„ธ๋ฉฐ, ๋ชจ๋ธ ์ด๋ฒคํŠธ์˜ usage_metadata(๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , ๋ฃจํ”„ ์•ž์— ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค๋ฅผ ๋‘์–ด ์ž์ฒด์ ์œผ๋กœ ์˜ˆ์‚ฐ์„ ๊ด€๋ฆฌํ•˜์„ธ์š”.

save_live_blob

save_live_blob=True๋Š” ์„ธ์…˜์˜ ์˜ค๋””์˜ค๋ฅผ ์„ธ์…˜ ์„œ๋น„์Šค์—๋Š” ์ฐธ์กฐ๋กœ, ์•„ํ‹ฐํŒฉํŠธ ์„œ๋น„์Šค์—๋Š” ํŒŒ์ผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฆ„๊ณผ ๋‹ฌ๋ฆฌ ํ˜„์žฌ๋Š” ์˜ค๋””์˜ค๋งŒ ์œ ์ง€๋˜๋ฉฐ ๋น„๋””์˜ค๋Š” ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์Œ์„ฑ ๋™์ž‘์„ ๋””๋ฒ„๊น…ํ•˜๊ฑฐ๋‚˜ ๊ทœ์ œ ๋Œ€์ƒ ํ™˜๊ฒฝ์˜ ๊ฐ์‚ฌ ์ถ”์ ์„ ์œ„ํ•ด ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ๋กœ ๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. 16 kHz PCM ์ž…๋ ฅ์€ ๋‘ ์„œ๋น„์Šค์— ๊ธฐ๋ก๋  ๋•Œ ์„ธ์…˜๋‹น ๋ถ„๋‹น ์•ฝ 1.92 MB๋ฅผ ์ฐจ์ง€ํ•˜๋ฉฐ, ์Œ์„ฑ ์›Œํฌ๋กœ๋“œ์—์„œ๋Š” ์šฉ๋Ÿ‰์ด ๋น ๋ฅด๊ฒŒ ๋ˆ„์ ๋ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ชจ๋“  ์„ธ์…˜ ๋Œ€์‹  ์ผ๋ถ€ ์„ธ์…˜๋งŒ ์ƒ˜ํ”Œ๋งํ•˜๊ณ  ์•„ํ‹ฐํŒฉํŠธ ์„œ๋น„์Šค์— ๋ณด์กด ์ •์ฑ…(retention policy)์„ ์„ค์ •ํ•˜์„ธ์š”. ADK๋Š” ์ด๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋ฃŒ์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

save_live_audio๋Š” ์ง€์› ์ค‘๋‹จ(deprecated)๋˜์—ˆ์Šต๋‹ˆ๋‹ค

ADK๋Š” save_live_audio=True๋ฅผ save_live_blob=True๋กœ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๊ณ  ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•˜์ง€๋งŒ, ์ด ํ˜ธํ™˜์„ฑ ๊ณ„์ธต์€ ํ–ฅํ›„ ๋ฆด๋ฆฌ์Šค์—์„œ ์ œ๊ฑฐ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. save_live_blob์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜์„ธ์š”.

history_config

์ด๋ฏธ ๋Œ€ํ™” ๊ธฐ๋ก์ด ์žˆ๋Š” ์„ธ์…˜์— ๋Œ€ํ•ด ADK๊ฐ€ ์ƒˆ๋กœ์šด Live API ์—ฐ๊ฒฐ์„ ์—ด ๋•Œ ํ•ด๋‹น ๊ธฐ๋ก์„ ์„œ๋ฒ„์— ์žฌ์ƒ(replay)ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๊ธฐ๋ก์—๋Š” ๋ชจ๋ธ ์ž์ฒด์˜ ๊ณผ๊ฑฐ ํ„ด์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์„œ๋ฒ„์— ๋‹ค์‹œ ์‘๋‹ตํ•˜์ง€ ์•Š๋„๋ก ์•Œ๋ ค์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ADK๊ฐ€ ์ด๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ „์†กํ•  ๊ธฐ๋ก์ด ์žˆ๊ณ  ์„ธ์…˜ ์žฌ๊ฐœ ํ•ธ๋“ค์ด ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์—ฐ๊ฒฐํ•˜๊ธฐ ์ „์— live_connect_config.history_config.initial_history_in_client_content = True๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

from google.genai import types

# ADK๊ฐ€ ์ด๋ฅผ ์ž๋™์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€ ๋™์ž‘์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์žฌ์ •์˜ํ•˜์„ธ์š”.
run_config = RunConfig(
    history_config=types.HistoryConfig(
        initial_history_in_client_content=True,
    ),
)

์‹ค์ œ ์˜๋ฏธ:

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์•„๋ฌด๊ฒƒ๋„ ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ADK๋Š” ๊ฐ’์„ ์„ค์ •ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ๋งŒ ์ฑ„์›Œ ๋„ฃ์œผ๋ฏ€๋กœ RunConfig์— ๋ช…์‹œ๋œ history_config๊ฐ€ ํ•ญ์ƒ ์šฐ์„  ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์žฌ์—ฐ๊ฒฐ ์‹œ์—๋Š” ๊ธฐ๋ก ์ „์†ก์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ADK๊ฐ€ ์„ธ์…˜ ์žฌ๊ฐœ ํ•ธ๋“ค๋กœ ์žฌ์—ฐ๊ฒฐํ•  ๋•Œ ์„œ๋ฒ„๋Š” ์ด๋ฏธ ํ•ด๋‹น ์„ธ์…˜์˜ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ADK๋Š” ๊ธฐ๋ก์„ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉฐ history_config๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ž˜๋ชป๋˜์—ˆ์„ ๋•Œ์˜ ์ฆ์ƒ: ๊ธฐ๋ก์„ ์‹œ๋”ฉํ•˜๋Š” ๋™์•ˆ initial_history_in_client_content=False๋กœ ์„ค์ •ํ•˜๋ฉด ๋ชจ๋ธ์ด ์žฌ์ƒ๋œ ํ„ด์— ์‘๋‹ตํ•˜์—ฌ ์—ฐ๊ฒฐ ์‹œ์ž‘ ์‹œ ์ค‘๋ณต๋œ ๋‹ต๋ณ€์ด ์Ÿ์•„์ ธ ๋‚˜์˜ต๋‹ˆ๋‹ค.

custom_metadata

custom_metadata๋Š” ํ˜ธ์ถœ ๋‚ด์˜ ๋ชจ๋“  Event์— ์ž„์˜์˜ JSON ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ์ฒจ๋ถ€ํ•˜๋ฉฐ, ๋Ÿฐํƒ€์ž„ ๊ตฌ์„ฑ์— ์„ค๋ช…๋œ ๋Œ€๋กœ ๋ผ์ด๋ธŒ ์„ธ์…˜์—์„œ๋„ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

run_config = RunConfig(
    response_modalities=["AUDIO"],
    custom_metadata={"user_tier": "premium", "session_type": "support"},
)

๋ผ์ด๋ธŒ ๊ณ ์œ ์˜ ์ฐจ์ด์ ์€ ์Šค์ฝ”ํ”„์ž…๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์˜ run_live() ํ˜ธ์ถœ์ด ํ•˜๋‚˜์˜ ํ˜ธ์ถœ(invocation)์ด๋ฏ€๋กœ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ๋‹จ์ผ ํ„ด์ด ์•„๋‹ˆ๋ผ ์ „์ฒด ์ŠคํŠธ๋ฆฌ๋ฐ ์„ธ์…˜์˜ ๋ชจ๋“  ์ด๋ฒคํŠธ์— ๊ฐ์ธ๋ฉ๋‹ˆ๋‹ค. event.custom_metadata๋ฅผ ํ†ตํ•ด ์ด๋ฅผ ๋‹ค์‹œ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

custom_metadata์— ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์ง€ ๋งˆ์„ธ์š”

์ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ชจ๋“  ์ด๋ฒคํŠธ๋Š” ์„ธ์…˜ ์„œ๋น„์Šค์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๊ฐœ์ธ ์‹๋ณ„ ์ •๋ณด(PII), ์ž๊ฒฉ ์ฆ๋ช… ๋ฐ ๊ธฐํƒ€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์ œ์™ธํ•˜๊ณ  ๋Œ€์•ˆ์ด ์—†๋Š” ๊ฒฝ์šฐ ์•”ํ˜ธํ™”ํ•˜์„ธ์š”.

๊ธฐํƒ€ ๋ผ์ด๋ธŒ ๊ด€๋ จ ํ•„๋“œ

RunConfig์—๋Š” run_live() ๊ฒฝ๋กœ์—์„œ๋งŒ ์œ ํšจํ•œ ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ํ•„๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ADK๋Š” ์ด๋ฅผ ๋ผ์ด๋ธŒ ์—ฐ๊ฒฐ๋กœ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•˜๋ฏ€๋กœ ์ •ํ™•ํ•œ ๋™์ž‘์€ ADK๊ฐ€ ์•„๋‹Œ Live API์— ์˜ํ•ด ์ •์˜๋ฉ๋‹ˆ๋‹ค:

ํ•„๋“œ ํƒ€์ž… ์„ค๋ช…
explicit_vad_signal bool ๋ชจ๋ธ์ด ๋ช…์‹œ์ ์ธ ์Œ์„ฑ ํ™œ๋™ ์‹ ํ˜ธ๋ฅผ ๋ฐฉ์ถœํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. ADK๋Š” ์ฝ˜ํ…์ธ ๋กœ๋ถ€ํ„ฐ ํ„ด ๊ฒฝ๊ณ„๋ฅผ ์œ ์ถ”ํ•˜๋Š” ๋Œ€์‹  event.voice_activity๋กœ ์ด๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค
translation_config types.TranslationConfig ์‹ค์‹œ๊ฐ„ ์Œ์„ฑ ๋Œ€ ์Œ์„ฑ ๋ฒˆ์—ญ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. target_language_code(BCP-47) ๋ฐ echo_target_language๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค. gemini-3.5-live-translate-preview์™€ ๊ฐ™์€ ๋ฒˆ์—ญ ๋ชจ๋ธ์—์„œ๋งŒ ์ง€์›๋˜๋ฉฐ, ์ง€์› ๋ชจ๋ธ์˜ ์ผ๋ฐ˜ ๋ชจ๋ธ์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
avatar_config types.AvatarConfig ์—์ด์ „ํŠธ๋ฅผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์•„๋ฐ”ํƒ€๋กœ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. avatar_name(๋นŒํŠธ์ธ ์•„๋ฐ”ํƒ€) ๋˜๋Š” customized_avatar, ๊ทธ๋ฆฌ๊ณ  audio_bitrate_bps / video_bitrate_bps๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค
from google.genai import types

run_config = RunConfig(
    response_modalities=["AUDIO"],
    explicit_vad_signal=True,
)

๋ผ์ด๋ธŒ ์ „์šฉ์€ ์•„๋‹ˆ์ง€๋งŒ ๋ผ์ด๋ธŒ ์„ธ์…˜์—์„œ ๋งค์šฐ ์œ ์šฉํ•œ ํ•„๋“œ๊ฐ€ ํ•˜๋‚˜ ๋” ์žˆ์Šต๋‹ˆ๋‹ค:

  • model_input_context (list[types.Content]): ํ˜„์žฌ ํ˜ธ์ถœ์— ๋Œ€ํ•ด์„œ๋งŒ LLM ์š”์ฒญ์— ์ฃผ์ž…๋˜๋Š” ์ผ์‹œ์ ์ธ ์ปจํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. Runner๋Š” ์ด๋ฅผ ์„ธ์…˜์— ์ €์žฅํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋Œ€ํ™” ๊ธฐ๋ก์„ ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š๊ณ  ํ„ด๋ณ„ ๊ทธ๋ผ์šด๋”ฉ ์ •๋ณด(์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ๊ธˆ ์—ฐ ๋ฌธ์„œ, ๋ณด๊ณ  ์žˆ๋Š” ํŽ˜์ด์ง€ ๋“ฑ)๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ฉ์„ฑ ํ•จ์ˆ˜ ํ˜ธ์ถœ (support_cfc)

ํ•ฉ์„ฑ ํ•จ์ˆ˜ ํ˜ธ์ถœ(Compositional Function Calling, CFC)์€ ๋ผ์ด๋ธŒ ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋ผ run_async() / SSE ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ผ์ด๋ธŒ ๋ชจ๋ธ ์ค‘ CFC ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๋Š” ๋ชจ๋ธ์ด ์—†์œผ๋ฏ€๋กœ ์ด๋ก ์ƒ์œผ๋กœ๋งŒ ์ ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. support_cfc๋Š” SSE ๊ฒฝ๋กœ์— ๋‚จ๊ฒจ๋‘๊ณ  ๋ผ์ด๋ธŒ ์„ธ์…˜์—์„œ๋Š” ํ‘œ์ค€ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ์‚ฌ์šฉํ•˜์„ธ์š”(๋„๊ตฌ ์ฐธ๊ณ ). ๋งค๊ฐœ๋ณ€์ˆ˜ ์ž์ฒด์— ๋Œ€ํ•ด์„œ๋Š” ๋Ÿฐํƒ€์ž„ ๊ตฌ์„ฑ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์˜ค๋””์˜ค ์ „์‚ฌ (Audio transcription)

Live API๋Š” ๋Œ€ํ™” ์–‘์ธก์„ ๋ชจ๋‘ ์ „์‚ฌํ•ด์ฃผ๋ฏ€๋กœ ๋ณ„๋„์˜ ์Œ์„ฑ ํ…์ŠคํŠธ ๋ณ€ํ™˜(STT) ์„œ๋น„์Šค ์—†์ด๋„ ์บก์…˜์„ ํ‘œ์‹œํ•˜๊ณ , ๋Œ€ํ™”๋ฅผ ๊ธฐ๋กํ•˜๋ฉฐ, ์ ‘๊ทผ์„ฑ์„ ์ง€์›ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ADK์—์„œ๋Š” ์ž…๋ ฅ(์‚ฌ์šฉ์ž ์Œ์„ฑ)๊ณผ ์ถœ๋ ฅ(๋ชจ๋ธ ์Œ์„ฑ) ๋ชจ๋‘ ์ „์‚ฌ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ผœ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠน์ • ๋ฐฉํ–ฅ์„ ๋„๋ ค๋ฉด ํ•ด๋‹น ํ•„๋“œ๋ฅผ None์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”.

from google.genai import types
from google.adk.agents.run_config import RunConfig

# ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค AudioTranscriptionConfig()๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
run_config = RunConfig(response_modalities=["AUDIO"])

# ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ „์‚ฌ๋ฅผ ๋„๊ณ  ๋ชจ๋ธ ์ถœ๋ ฅ ์ „์‚ฌ๋Š” ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
run_config = RunConfig(
    response_modalities=["AUDIO"],
    input_audio_transcription=None,
)

์ „์‚ฌ๋Š” event.content์™€ ๋ณ„๋„๋กœ event.input_transcription ๋ฐ event.output_transcription์— types.Transcription ๊ฐ์ฒด๋กœ ๋„์ฐฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์กฐ๊ฐ(fragment) ๋‹จ์œ„๋กœ ์ŠคํŠธ๋ฆฌ๋ฐ๋ฉ๋‹ˆ๋‹ค. .text๋Š” ์ตœ์‹  ์กฐ๊ฐ์„ ๋‹ด๊ณ  ์žˆ๊ณ  .finished๋Š” ํ•ด๋‹น ํ„ด์˜ ๋งˆ์ง€๋ง‰ ์กฐ๊ฐ์ž„์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์กฐ๊ฐ๋“ค์„ ์ด์–ด ๋ถ™์—ฌ ์ „์ฒด ์ „์‚ฌ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

async for event in runner.run_live(...):
    if event.input_transcription and event.input_transcription.text:
        update_caption(
            event.input_transcription.text,
            is_user=True,
            is_final=event.input_transcription.finished,
        )
    if event.output_transcription and event.output_transcription.text:
        update_caption(
            event.output_transcription.text,
            is_user=False,
            is_final=event.output_transcription.finished,
        )

์ด๋ฒคํŠธ ๊ตฌ์กฐ์— ๋Œ€ํ•ด์„œ๋Š” ์ „์‚ฌ ์ด๋ฒคํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์„ธ์…˜์€ ํ•ญ์ƒ ์ „์‚ฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค

๋ฃจํŠธ ์—์ด์ „ํŠธ์— sub_agents๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ run_live()๋Š” ์‚ฌ์šฉ์ž๊ฐ€ None์œผ๋กœ ์„ค์ •ํ•˜๋”๋ผ๋„ ์ž…๋ ฅ ๋ฐ ์ถœ๋ ฅ ์ „์‚ฌ๋ฅผ ๋ชจ๋‘ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค. ์—์ด์ „ํŠธ ์ธ๊ณ„ ์‹œ ๋‹ค์Œ ์—์ด์ „ํŠธ์— ๋Œ€ํ™” ์ปจํ…์ŠคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ํ…์ŠคํŠธ ์ „์‚ฌ๊ฐ€ ํ•„์š”ํ•˜๋ฏ€๋กœ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(runners.py).

์Œ์„ฑ ๋ฐ ์–ธ์–ด (Voice and language)

๋ชจ๋ธ์˜ ์Œ์„ฑ๊ณผ ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜๋ ค๋ฉด speech_config๋ฅผ ์„ค์ •ํ•˜์„ธ์š”. ๋‘ ๊ณณ์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ์—์ด์ „ํŠธ ์ˆ˜์ค€: speech_config๊ฐ€ ํฌํ•จ๋œ Gemini ์ธ์Šคํ„ด์Šค๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋ฉ€ํ‹ฐ ์—์ด์ „ํŠธ ์›Œํฌํ”Œ๋กœ์—์„œ ๊ฐ ์—์ด์ „ํŠธ์— ๊ณ ์œ ํ•œ ์Œ์„ฑ์„ ๋ถ€์—ฌํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์„ธ์…˜ ์ˆ˜์ค€: RunConfig.speech_config๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์„ธ์…˜ ์ „์ฒด์— ๊ฑธ์ณ ๋‹จ์ผ ์Œ์„ฑ์„ ์‚ฌ์šฉํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‘˜ ๋‹ค ์„ค์ •๋œ ๊ฒฝ์šฐ ์—์ด์ „ํŠธ ์ˆ˜์ค€์˜ ์Œ์„ฑ์ด ์šฐ์„  ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ Live API๊ฐ€ ๊ธฐ๋ณธ ์Œ์„ฑ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

from google.genai import types
from google.adk.agents import Agent
from google.adk.models.google_llm import Gemini
from google.adk.agents.run_config import RunConfig

# ์—์ด์ „ํŠธ ์ˆ˜์ค€ ์Œ์„ฑ (RunConfig๋ณด๋‹ค ์šฐ์„  ์ ์šฉ๋จ).
agent = Agent(
    model=Gemini(
        model="gemini-live-2.5-flash-native-audio",
        speech_config=types.SpeechConfig(
            voice_config=types.VoiceConfig(
                prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Puck")
            ),
            language_code="en-US",
        ),
    ),
    instruction="You are a helpful assistant.",
)

# ์„ธ์…˜ ์ˆ˜์ค€ ๊ธฐ๋ณธ ์Œ์„ฑ (์ž์ฒด ์Œ์„ฑ์ด ์—†๋Š” ์—์ด์ „ํŠธ๊ฐ€ ์‚ฌ์šฉ).
run_config = RunConfig(
    response_modalities=["AUDIO"],
    speech_config=types.SpeechConfig(
        voice_config=types.VoiceConfig(
            prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Kore")
        ),
    ),
)

voice_name์€ ์‚ฌ์ „ ์ œ์ž‘๋œ ์Œ์„ฑ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ ๋ชจ๋ธ์€ 8๊ฐ€์ง€ ์Œ์„ฑ(Puck, Charon, Kore, Fenrir, Aoede, Leda, Orus, Zephyr)๊ณผ ํ™•์žฅ๋œ Text-to-Speech ์Œ์„ฑ ๋ชฉ๋ก์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ชฉ๋ก๊ณผ ๋ฐฑ์—”๋“œ๋ณ„ ๊ฐ€์šฉ์„ฑ์€ Gemini Live API ์Œ์„ฑ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ์ง€์›๋˜์ง€ ์•Š๋Š” ์Œ์„ฑ์€ ์—ฐ๊ฒฐ ์‹œ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

language_code(์˜ˆ: en-US, ko-KR, ja-JP)๋Š” ์–ธ์–ด์™€ ์–ต์–‘์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ ๋ชจ๋ธ์€ ๋Œ€ํ™” ๋‚ด์šฉ์—์„œ ์–ธ์–ด๋ฅผ ์ž์ฒด์ ์œผ๋กœ ์œ ์ถ”ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„ ์ด ์„ค์ •์„ ๋ฌด์‹œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์Œ์„ฑ ํ™œ๋™ ๊ฐ์ง€ (VAD)

VAD๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ง์„ ์‹œ์ž‘ํ•˜๊ณ  ๋ฉˆ์ถ”๋Š” ์‹œ์ ์„ ๊ฐ์ง€ํ•˜์—ฌ ๋ชจ๋ธ์ด ๋ผ์–ด๋“ค๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํฌํ•จํ•ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ„ด์„ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ผ์ด๋ธŒ ๋ชจ๋ธ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ผœ์ ธ ์žˆ์œผ๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ณ„๋„ ๊ตฌ์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Push-to-talk, ํด๋ผ์ด์–ธํŠธ ์ธก VAD ๋˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐœ์–ธ ์™„๋ฃŒ๋ฅผ ์ง์ ‘ ์‹ ํ˜ธํ•˜๋Š” UX ๋“ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ„ด ๊ฒฝ๊ณ„๋ฅผ ์ž์ฒด์ ์œผ๋กœ ๊ฒฐ์ •ํ•˜๋Š” ๊ฒฝ์šฐ ์ž๋™ VAD๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜์„ธ์š”. ๋น„ํ™œ์„ฑํ™”ํ•œ ๊ฒฝ์šฐ send_activity_start() / send_activity_end()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜๋™์œผ๋กœ ActivityStart/ActivityEnd ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด์•ผ ํ•˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๋Š” ์ž์ฒด ํ„ด ์‹ ํ˜ธ๋ฅผ ์„œ๋ฒ„์˜ ํ•ด๋‹น ํ˜ธ์ถœ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

from google.genai import types
from google.adk.agents.run_config import RunConfig

run_config = RunConfig(
    response_modalities=["AUDIO"],
    realtime_input_config=types.RealtimeInputConfig(
        automatic_activity_detection=types.AutomaticActivityDetection(disabled=True)
    ),
)

์ž์ฒด VAD๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ด๋‹น ์‹ ํ˜ธ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๊ณ , ์„œ๋ฒ„๋Š” send_activity_start() / send_activity_end()๋กœ ์ด๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๋Šฅ๋™์  ๋ฐ ๊ฐ์ •์  ๋Œ€ํ™” (Proactivity and affective dialog)

์ผ๋ถ€ ๋ผ์ด๋ธŒ ๋ชจ๋ธ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๊บผ์ ธ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋Œ€ํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  • ๋Šฅ๋™์  ์˜ค๋””์˜ค (proactivity): ๋ชจ๋ธ์ด ์‘๋‹ตํ•  ์‹œ์ ์„ ์Šค์Šค๋กœ ๊ฒฐ์ •ํ•˜๊ณ , ์š”์ฒญํ•˜์ง€ ์•Š์•„๋„ ์ œ์•ˆ์„ ํ•˜๊ฑฐ๋‚˜, ๊ด€๋ จ ์—†๋Š” ์ž…๋ ฅ์„ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐ์ •์  ๋Œ€ํ™” (enable_affective_dialog): ๋ชจ๋ธ์ด ์‚ฌ์šฉ์ž์˜ ์–ด์กฐ์—์„œ ๊ฐ์ •์„ ๊ฐ์ง€ํ•˜๊ณ  ๊ทธ์— ๋งž๊ฒŒ ์‘๋‹ต์„ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
from google.genai import types
from google.adk.agents.run_config import RunConfig

run_config = RunConfig(
    response_modalities=["AUDIO"],
    proactivity=types.ProactivityConfig(proactive_audio=True),
    enable_affective_dialog=True,
)

๋‘ ๋™์ž‘ ๋ชจ๋‘ ํ™•๋ฅ ์ ์ด๋ฉฐ ์‘๋‹ต์˜ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ์„ ๋‚ฎ์ถ”๋ฏ€๋กœ ๊ฒฉ์‹ ์žˆ๋Š” ์ปจํ…์ŠคํŠธ, ๋†’์€ ์ •ํ™•๋„๊ฐ€ ์š”๊ตฌ๋˜๋Š” ํ™˜๊ฒฝ ๋ฐ ๋””๋ฒ„๊น… ์ค‘์—๋Š” ๊บผ๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‘ ์„ค์ • ๋ชจ๋‘ ๋ชจ๋ธ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. Gemini 2.5 Flash Live๋Š” ์ด๋ฅผ ์ง€์›ํ•˜์ง€๋งŒ Gemini 3.1 Flash Live๋Š” ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์ด๋ฅผ ์„ค์ •ํ•œ ์ฑ„๋กœ ๋‘๋Š” ๊ฒƒ์ด 3.1๋กœ ์ „ํ™˜ํ•  ๋•Œ ๊ฐ€์žฅ ํ”ํ•œ ์‹คํŒจ ์›์ธ์ž…๋‹ˆ๋‹ค. ๋ชจ๋ธ๋ณ„ ๊ธฐ๋Šฅ ์ง€์›์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.