ํจ์ ๋๊ตฌ¶
์ฌ์ ๋น๋๋ ADK ๋๊ตฌ๊ฐ ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ์ง ์๋ ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ง์ ํจ์ ๋๊ตฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ํจ์ ๋๊ตฌ๋ฅผ ๋น๋ํ๋ฉด ๋
์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๊ฑฐ๋ ๊ณ ์ ํ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ ๋ฑ ๋ง์ถคํ ๊ธฐ๋ฅ์ ๋ง๋ค ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ํจ์ ๋๊ตฌ myfinancetool์ ํน์ ์ฌ๋ฌด ์งํ๋ฅผ ๊ณ์ฐํ๋ ํจ์์ผ ์ ์์ต๋๋ค. ADK๋ ๋ํ ์ฅ๊ธฐ ์คํ ํจ์๋ฅผ ์ง์ํ๋ฏ๋ก ํด๋น ๊ณ์ฐ์ ์๊ฐ์ด ๊ฑธ๋ฆฌ๋ ๊ฒฝ์ฐ ์์ด์ ํธ๋ ๋ค๋ฅธ ์์
์ ๊ณ์ํ ์ ์์ต๋๋ค.
ADK๋ ๊ฐ๊ฐ ๋ค๋ฅธ ์์ค์ ๋ณต์ก์ฑ๊ณผ ์ ์ด์ ์ ํฉํ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํจ์ ๋๊ตฌ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
ํจ์ ๋๊ตฌ¶
Python ํจ์๋ฅผ ๋๊ตฌ๋ก ๋ณํํ๋ ๊ฒ์ ์ฌ์ฉ์ ์ง์ ๋
ผ๋ฆฌ๋ฅผ ์์ด์ ํธ์ ํตํฉํ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์
๋๋ค. ์์ด์ ํธ์ tools ๋ชฉ๋ก์ ํจ์๋ฅผ ํ ๋นํ๋ฉด ํ๋ ์์ํฌ๊ฐ ์๋์ผ๋ก FunctionTool๋ก ๋ํํฉ๋๋ค.
์๋ ๋ฐฉ์¶
ADK ํ๋ ์์ํฌ๋ ์ด๋ฆ, docstring, ๋งค๊ฐ๋ณ์, ์ ํ ํํธ ๋ฐ ๊ธฐ๋ณธ๊ฐ์ ํฌํจํ์ฌ Python ํจ์์ ์๋ช ์ ์๋์ผ๋ก ๊ฒ์ฌํ์ฌ ์คํค๋ง๋ฅผ ์์ฑํฉ๋๋ค. ์ด ์คํค๋ง๋ LLM์ด ๋๊ตฌ์ ๋ชฉ์ , ์ฌ์ฉ ์๊ธฐ ๋ฐ ํ์ํ ์ธ์๋ฅผ ์ดํดํ๋ ๋ฐ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค.
ํจ์ ์๋ช ์ ์¶
์ ์ ์๋ ํจ์ ์๋ช ์ LLM์ด ๋๊ตฌ๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ ๋ฐ ์ค์ํฉ๋๋ค.
๋งค๊ฐ๋ณ์¶
๋งค๊ฐ๋ณ์๋ ๋๊ตฌ๊ฐ ํธ์ถ๋ ๋ ์์ ํ ๊ฒ์ผ๋ก ์์๋๋ ๋ฐ์ดํฐ๋ฅผ ๊ฒฐ์ ํฉ๋๋ค. ๋๊ตฌ์ ํ์์ ๋ฐ๋ผ ํ์ ๋๋ ์ ํ์ฌํญ์ผ๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
ํ์ ๋งค๊ฐ๋ณ์¶
๋งค๊ฐ๋ณ์๋ ์ ํ ํํธ๊ฐ ์์ง๋ง ๊ธฐ๋ณธ๊ฐ์ด ์๋ ๊ฒฝ์ฐ ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. LLM์ ๋๊ตฌ๋ฅผ ํธ์ถํ ๋ ์ด ์ธ์์ ๋ํ ๊ฐ์ ์ ๊ณตํด์ผ ํฉ๋๋ค. ๋งค๊ฐ๋ณ์์ ์ค๋ช ์ ํจ์์ docstring์์ ๊ฐ์ ธ์ต๋๋ค.
์: ํ์ ๋งค๊ฐ๋ณ์
def get_weather(city: str, unit: str):
"""
์ง์ ๋ ๋จ์๋ก ๋์์ ๋ ์จ๋ฅผ ๊ฒ์ํฉ๋๋ค.
์ธ์:
city (str): ๋์ ์ด๋ฆ์
๋๋ค.
unit (str): '์ญ์จ' ๋๋ 'ํ์จ' ์ค ํ๋์ธ ์จ๋ ๋จ์์
๋๋ค.
"""
# ... ํจ์ ๋
ผ๋ฆฌ ...
return {"status": "success", "report": f"{city}์ ๋ ์จ๋ ๋ง์ต๋๋ค."}
์ด ์์์ city์ unit์ ๋ชจ๋ ํ์์
๋๋ค. LLM์ด ๋ ์ค ํ๋ ์์ด get_weather๋ฅผ ํธ์ถํ๋ ค๊ณ ํ๋ฉด ADK๋ LLM์ ์ค๋ฅ๋ฅผ ๋ฐํํ์ฌ ํธ์ถ์ ์์ ํ๋๋ก ์ ๋ํฉ๋๋ค.
Go์์๋ ๊ตฌ์กฐ์ฒด ํ๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ JSON ์คํค๋ง๋ฅผ ์ ์ดํฉ๋๋ค. ๋ ๊ฐ์ง ๊ธฐ๋ณธ ํ๊ทธ๋ json๊ณผ jsonschema์
๋๋ค.
๋งค๊ฐ๋ณ์๋ ๊ตฌ์กฐ์ฒด ํ๋์ json ํ๊ทธ์ omitempty ๋๋ omitzero ์ต์
์ด ์๋ ๊ฒฝ์ฐ ํ์๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
jsonschema ํ๊ทธ๋ ์ธ์์ ์ค๋ช
์ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ LLM์ด ์ธ์๊ฐ ๋ฌด์์ธ์ง ์ดํดํ๋ ๋ฐ ์ค์ํฉ๋๋ค.
์: ํ์ ๋งค๊ฐ๋ณ์
// GetWeatherParams๋ getWeather ๋๊ตฌ์ ์ธ์๋ฅผ ์ ์ํฉ๋๋ค.
type GetWeatherParams struct {
// ์ด ํ๋๋ ํ์์
๋๋ค("omitempty" ์์).
// jsonschema ํ๊ทธ๋ ์ค๋ช
์ ์ ๊ณตํฉ๋๋ค.
Location string `json:"location" jsonschema:"๋์ ๋ฐ ์ฃผ, ์: ์ํ๋์์ค์ฝ, CA"`
// ์ด ํ๋๋ ํ์์
๋๋ค.
Unit string `json:"unit" jsonschema:"'์ญ์จ' ๋๋ 'ํ์จ' ์ค ํ๋์ธ ์จ๋ ๋จ์"`
}
์ด ์์์ location๊ณผ unit์ ๋ชจ๋ ํ์์
๋๋ค.
์ ํ์ ๋งค๊ฐ๋ณ์¶
๋งค๊ฐ๋ณ์๋ ๊ธฐ๋ณธ๊ฐ์ ์ ๊ณตํ๋ ๊ฒฝ์ฐ ์ ํ ์ฌํญ์ผ๋ก ๊ฐ์ฃผ๋ฉ๋๋ค. ์ด๊ฒ์ด ์ ํ์ ์ธ์๋ฅผ ์ ์ํ๋ ํ์ค Python ๋ฐฉ์์
๋๋ค. typing.Optional[SomeType] ๋๋ | None ๊ตฌ๋ฌธ(Python 3.10 ์ด์)์ ์ฌ์ฉํ์ฌ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํ ์ฌํญ์ผ๋ก ํ์ํ ์๋ ์์ต๋๋ค.
๊ธฐ๋ณธ๊ฐ์ ์ค์ ๋ก ์ ํ ์ฌํญ์ธ ๊ฐ์๋ง ์ฌ์ฉํ์ธ์. ๋ชจ๋ธ์ด ์ฌ์ฉ์ ์์ฒญ์์ ์ถ๋ก ํด์ผ ํ๊ฑฐ๋ ์ฌ์ฉ์์๊ฒ ๋ฌผ์ด๋ด์ผ ํ๋ ์ ๋ณด์๋ ๊ธฐ๋ณธ๊ฐ์ ์ถ๊ฐํ์ง ๋ง์ธ์.
์: ์ ํ์ ๋งค๊ฐ๋ณ์
def search_flights(destination: str, departure_date: str, flexible_days: int = 0):
"""
ํญ๊ณตํธ์ ๊ฒ์ํฉ๋๋ค.
์ธ์:
destination (str): ๋ชฉ์ ์ง ๋์์
๋๋ค.
departure_date (str): ์ํ๋ ์ถ๋ฐ์ผ์
๋๋ค.
flexible_days (int, optional): ๊ฒ์์ ๋ํ ์ ์ฐํ ์ผ์์
๋๋ค. ๊ธฐ๋ณธ๊ฐ์ 0์
๋๋ค.
"""
# ... ํจ์ ๋
ผ๋ฆฌ ...
if flexible_days > 0:
return {"status": "success", "report": f"{destination}์ผ๋ก ๊ฐ๋ ์ ์ฐํ ํญ๊ณตํธ์ ์ฐพ์์ต๋๋ค."}
return {"status": "success", "report": f"{departure_date}์ {destination}์ผ๋ก ๊ฐ๋ ํญ๊ณตํธ์ ์ฐพ์์ต๋๋ค."}
์ฌ๊ธฐ์ flexible_days๋ ์ ํ ์ฌํญ์
๋๋ค. LLM์ ์ด๋ฅผ ์ ๊ณตํ๋๋ก ์ ํํ ์ ์์ง๋ง ํ์๋ ์๋๋๋ค.
๋งค๊ฐ๋ณ์๋ ๊ตฌ์กฐ์ฒด ํ๋์ json ํ๊ทธ์ omitempty ๋๋ omitzero ์ต์
์ด ์๋ ๊ฒฝ์ฐ ์ ํ ์ฌํญ์ผ๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์: ์ ํ์ ๋งค๊ฐ๋ณ์
// GetWeatherParams๋ getWeather ๋๊ตฌ์ ์ธ์๋ฅผ ์ ์ํฉ๋๋ค.
type GetWeatherParams struct {
// ์์น๋ ํ์์
๋๋ค.
Location string `json:"location" jsonschema:"๋์ ๋ฐ ์ฃผ, ์: ์ํ๋์์ค์ฝ, CA"`
// ๋จ์๋ ์ ํ ์ฌํญ์
๋๋ค.
Unit string `json:"unit,omitempty" jsonschema:"'์ญ์จ' ๋๋ 'ํ์จ' ์ค ํ๋์ธ ์จ๋ ๋จ์"`
// ์ผ์๋ ์ ํ ์ฌํญ์
๋๋ค.
Days int `json:"days,omitzero" jsonschema:"๋ฐํํ ์๋ณด ์ผ์(๊ธฐ๋ณธ๊ฐ์ 1)"`
}
์ฌ๊ธฐ์ unit๊ณผ days๋ ์ ํ ์ฌํญ์
๋๋ค. LLM์ ์ด๋ฅผ ์ ๊ณตํ๋๋ก ์ ํํ ์ ์์ง๋ง ํ์๋ ์๋๋๋ค.
typing.Optional์ ์ฌ์ฉํ ์ ํ์ ๋งค๊ฐ๋ณ์¶
typing.Optional[SomeType] ๋๋ | None ๊ตฌ๋ฌธ(Python 3.10 ์ด์)์ ์ฌ์ฉํ์ฌ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํ ์ฌํญ์ผ๋ก ํ์ํ ์๋ ์์ต๋๋ค. ์ด๋ ๋งค๊ฐ๋ณ์๊ฐ None์ผ ์ ์์์ ๋ํ๋
๋๋ค. None์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒฐํฉํ๋ฉด ํ์ค ์ ํ์ ๋งค๊ฐ๋ณ์์ฒ๋ผ ๋์ํฉ๋๋ค.
์: typing.Optional
from typing import Optional
def create_user_profile(username: str, bio: Optional[str] = None):
"""
์ ์ฌ์ฉ์ ํ๋กํ์ ๋ง๋ญ๋๋ค.
์ธ์:
username (str): ์ฌ์ฉ์์ ๊ณ ์ ํ ์ฌ์ฉ์ ์ด๋ฆ์
๋๋ค.
bio (str, optional): ์ฌ์ฉ์์ ๋ํ ์งง์ ์ฝ๋ ฅ์
๋๋ค. ๊ธฐ๋ณธ๊ฐ์ None์
๋๋ค.
"""
# ... ํจ์ ๋
ผ๋ฆฌ ...
if bio:
return {"status": "success", "message": f"{username}์ ๋ํ ํ๋กํ์ด ์ฝ๋ ฅ๊ณผ ํจ๊ป ์์ฑ๋์์ต๋๋ค."}
return {"status": "success", "message": f"{username}์ ๋ํ ํ๋กํ์ด ์์ฑ๋์์ต๋๋ค."}
๊ฐ๋ณ ๋งค๊ฐ๋ณ์(*args ๋ฐ **kwargs)¶
๋ค๋ฅธ ๋ชฉ์ ์ผ๋ก ํจ์ ์๋ช
์ *args(๊ฐ๋ณ ์์น ์ธ์) ๋ฐ **kwargs(๊ฐ๋ณ ํค์๋ ์ธ์)๋ฅผ ํฌํจํ ์ ์์ง๋ง LLM์ ๋ํ ๋๊ตฌ ์คํค๋ง๋ฅผ ์์ฑํ ๋ ADK ํ๋ ์์ํฌ์์ ๋ฌด์๋ฉ๋๋ค. LLM์ ์ด๋ฅผ ์ธ์ํ์ง ๋ชปํ๋ฉฐ ์ธ์๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. LLM์์ ์์ํ๋ ๋ชจ๋ ๋ฐ์ดํฐ์ ๋ํด ๋ช
์์ ์ผ๋ก ์ ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฐ์ฅ ์ข์ต๋๋ค.
JSON ์คํค๋ง ๋๊ตฌ ์ ์ธ ๊ตฌ์ฑ¶
Python์ฉ ADK๋ ๋ชจ๋ธ ํจ์ ์ ์ธ์ ์ํด Pydantic์ ์ฌ์ฉํ์ฌ ํจ์ ์๋ช
์์ JSON ์คํค๋ง๋ฅผ ์์ฑํฉ๋๋ค. ์ด ๊ธฐ๋ฅ(JSON_SCHEMA_FOR_FUNC_DECL)์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋์ด ์์ต๋๋ค.
์คํ์ ๊ธฐ๋ฅ
JSON ์คํค๋ง ๋๊ตฌ ์ ์ธ(JSON_SCHEMA_FOR_FUNC_DECL)์ Python์ฉ ADK์ ์คํ์ ๊ธฐ๋ฅ์
๋๋ค. ์ด ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋์ด ์์ง๋ง ๋์ ๋ฐ API๊ฐ ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฉฐ, ํ์ฑํ๋ ๋์ ADK๋ [EXPERIMENTAL] ๊ฒฝ๊ณ ๋ฅผ ์ถ๋ ฅํฉ๋๋ค.
์ต์ 1: ๋ช ๋ น์ค์ ํตํด ๊ตฌ์ฑ¶
์์ด์ ํธ ํ๋ก์ธ์ค๋ฅผ ์์ํ๊ธฐ ์ ์ ํฐ๋ฏธ๋์์ ์ ์ ํ ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํฉ๋๋ค.
JSON ์คํค๋ง ์์ฑ์ ๋นํ์ฑํํ๊ณ ์ด์ ์ Schema ๊ธฐ๋ฐ ์ ์ธ ๋์์ผ๋ก ๋์ฒด(fallback)ํ๋ ค๋ฉด ๋ค์์ ์ค์ ํฉ๋๋ค.
์ต์ 2: Python ์ฝ๋์์ ๊ตฌ์ฑ¶
๋๊ตฌ์ ์์ด์ ํธ๋ฅผ ๋ง๋ค๊ธฐ ์ ์ ADK์ override_feature_enabled ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
import asyncio
from google.adk.features import FeatureName, override_feature_enabled
from google.adk.agents import Agent
from google.adk.tools import FunctionTool
# ์ด ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฑํ๋์ด ์์ผ๋ฏ๋ก ์ด์ ๋์์ผ๋ก ๋์๊ฐ๋ ค๋ฉด False๋ฅผ ์ ๋ฌํฉ๋๋ค.
override_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, False)
def get_current_weather(location: str, unit: str = "celsius") -> str:
"""Get the current weather for a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit ('celsius' or 'fahrenheit')
"""
return f"Weather in {location}: 22ยฐ {unit.capitalize()}"
async def main():
weather_tool = FunctionTool(func=get_current_weather)
agent = Agent(
model="gemini-2.5-flash",
name="my_agent",
instruction="You are a helpful assistant.",
tools=[weather_tool]
)
# ์คํ ๋ก์ง
if __name__ == "__main__":
asyncio.run(main())
๋๋ ์์ด์ ํธ ๊ตฌ์ฑ ์์๋ฅผ ์ด๊ธฐํํ๊ธฐ ์ ์ ์คํฌ๋ฆฝํธ์์ ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ ์๋ ์์ต๋๋ค.
import os
import asyncio
os.environ["ADK_ENABLE_JSON_SCHEMA_FOR_FUNC_DECL"] = "1"
from google.adk.agents import Agent
from google.adk.tools import FunctionTool
def get_current_weather(location: str) -> str:
"""Get the current weather for a given location."""
return f"Weather in {location}: 20ยฐC"
async def main():
weather_tool = FunctionTool(func=get_current_weather)
agent = Agent(
model="gemini-2.5-flash",
name="my_agent",
instruction="You are a helpful assistant.",
tools=[weather_tool]
)
# ์คํ ๋ก์ง
if __name__ == "__main__":
asyncio.run(main())
๋ฐํ ์ ํ¶
ํจ์ ๋๊ตฌ์ ๊ธฐ๋ณธ ๋ฐํ ์ ํ์ Python์์๋ ์ฌ์ ์ด๊ณ Java์์๋ ๋งต์ ๋๋ค. ์ด๋ฅผ ํตํด ํค-๊ฐ ์์ผ๋ก ์๋ต์ ๊ตฌ์กฐํํ์ฌ LLM์ ์ปจํ ์คํธ์ ๋ช ํ์ฑ์ ์ ๊ณตํ ์ ์์ต๋๋ค. ํจ์๊ฐ ์ฌ์ ์ด์ธ์ ์ ํ์ ๋ฐํํ๋ ๊ฒฝ์ฐ ํ๋ ์์ํฌ๋ ์๋์ผ๋ก **"result"**๋ผ๋ ๋จ์ผ ํค๋ฅผ ๊ฐ์ง ์ฌ์ ์ผ๋ก ๋ํํฉ๋๋ค.
๋ฐํ ๊ฐ์ ๊ฐ๋ฅํ ํ ์ค๋ช ์ ์ผ๋ก ๋ง๋์ญ์์ค. ์๋ฅผ ๋ค์ด, ์ซ์ ์ค๋ฅ ์ฝ๋๋ฅผ ๋ฐํํ๋ ๋์ ์ฌ๋์ด ์ฝ์ ์ ์๋ ์ค๋ช ์ด ํฌํจ๋ "error_message" ํค๊ฐ ์๋ ์ฌ์ ์ ๋ฐํํฉ๋๋ค. ์ฝ๋๊ฐ ์๋ LLM์ด ๊ฒฐ๊ณผ๋ฅผ ์ดํดํด์ผ ํ๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์. ๋ชจ๋ฒ ์ฌ๋ก๋ก ๋ฐํ ์ฌ์ ์ "status" ํค๋ฅผ ํฌํจํ์ฌ ์ ์ฒด ๊ฒฐ๊ณผ(์: "success", "error", "pending")๋ฅผ ๋ํ๋ด์ด LLM์ ์์ ์ํ์ ๋ํ ๋ช ํํ ์ ํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.
Docstrings¶
ํจ์์ docstring์ ๋๊ตฌ์ ์ค๋ช ์ญํ ์ ํ๋ฉฐ LLM์ผ๋ก ์ ์ก๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ ์์ฑ๋๊ณ ํฌ๊ด์ ์ธ docstring์ LLM์ด ๋๊ตฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ๋ ๋ฐ ์ค์ํฉ๋๋ค. ํจ์์ ๋ชฉ์ , ๋งค๊ฐ๋ณ์์ ์๋ฏธ ๋ฐ ์์ ๋ฐํ ๊ฐ์ ๋ช ํํ๊ฒ ์ค๋ช ํ์ญ์์ค.
๋๊ตฌ ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ¶
์์ด์ ํธ๊ฐ ์ฌ๋ฌ ๋๊ตฌ๋ฅผ ์์๋๋ก ํธ์ถํ ๋ ํ ๋๊ตฌ์์ ๋ค๋ฅธ ๋๊ตฌ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํด์ผ ํ ์ ์์ต๋๋ค. ๊ถ์ฅ๋๋ ๋ฐฉ๋ฒ์ ์ธ์
์ํ์์ temp: ์ ๋์ฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค.
๋๊ตฌ๋ temp: ๋ณ์์ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์๊ณ ํ์ ๋๊ตฌ๋ ์ด๋ฅผ ์ฝ์ ์ ์์ต๋๋ค. ์ด ๋ฐ์ดํฐ๋ ํ์ฌ ํธ์ถ์๋ง ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ์ดํ์๋ ์ญ์ ๋ฉ๋๋ค.
๊ณต์ ํธ์ถ ์ปจํ ์คํธ
๋จ์ผ ์์ด์ ํธ ํด ๋ด์ ๋ชจ๋ ๋๊ตฌ ํธ์ถ์ ๋์ผํ InvocationContext๋ฅผ ๊ณต์ ํฉ๋๋ค. ์ฆ, ๋์ผํ ์์(temp:) ์ํ๋ฅผ ๊ณต์ ํ๋ฏ๋ก ๋๊ตฌ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค.
์¶
์
์ด ๋๊ตฌ๋ ์ฃผ์ด์ง ์ฃผ์ ์์ธ/๊ธฐํธ์ ์ฃผ๊ฐ๋ฅผ ๊ฐ์ ธ์ค๋ ํ์ด์ฌ ํจ์์ ๋๋ค.
์ฐธ๊ณ : ์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ ์ pip install yfinance ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค.
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
import yfinance as yf
APP_NAME = "stock_app"
USER_ID = "1234"
SESSION_ID = "session1234"
def get_stock_price(symbol: str):
"""
Retrieves the current stock price for a given symbol.
Args:
symbol (str): The stock symbol (e.g., "AAPL", "GOOG").
Returns:
float: The current stock price, or None if an error occurs.
"""
try:
stock = yf.Ticker(symbol)
historical_data = stock.history(period="1d")
if not historical_data.empty:
current_price = historical_data['Close'].iloc[-1]
return current_price
else:
return None
except Exception as e:
print(f"Error retrieving stock price for {symbol}: {e}")
return None
stock_price_agent = Agent(
model='gemini-2.0-flash',
name='stock_agent',
instruction= 'You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.',
description='This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.',
tools=[get_stock_price], # You can add Python functions directly to the tools list; they will be automatically wrapped as FunctionTools.
)
# Session and Runner
async def setup_session_and_runner():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=stock_price_agent, app_name=APP_NAME, session_service=session_service)
return session, runner
# Agent Interaction
async def call_agent_async(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
async for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async("stock price of GOOG")
์ด ๋๊ตฌ์ ๋ฐํ ๊ฐ์ ์ฌ์ ์ผ๋ก ๋ํ๋ฉ๋๋ค.
์ด ๋๊ตฌ๋ ์ฃผ๊ฐ์ ๋ชจ์ ๊ฐ์ ๊ฒ์ํฉ๋๋ค.
import (
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/model/gemini"
"google.golang.org/adk/v2/runner"
"google.golang.org/adk/v2/session"
"google.golang.org/adk/v2/tool"
"google.golang.org/adk/v2/tool/functiontool"
"google.golang.org/genai"
)
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
"log"
"strings"
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/model/gemini"
"google.golang.org/adk/v2/runner"
"google.golang.org/adk/v2/session"
"google.golang.org/adk/v2/tool"
"google.golang.org/adk/v2/tool/agenttool"
"google.golang.org/adk/v2/tool/functiontool"
"google.golang.org/genai"
)
// mockStockPrices provides a simple in-memory database of stock prices
// to simulate a real-world stock data API. This allows the example to
// demonstrate tool functionality without making external network calls.
var mockStockPrices = map[string]float64{
"GOOG": 300.6,
"AAPL": 123.4,
"MSFT": 234.5,
}
// getStockPriceArgs defines the schema for the arguments passed to the getStockPrice tool.
// Using a struct is the recommended approach in the Go ADK as it provides strong
// typing and clear validation for the expected inputs.
type getStockPriceArgs struct {
Symbol string `json:"symbol" jsonschema:"The stock ticker symbol, e.g., GOOG"`
}
// getStockPriceResults defines the output schema for the getStockPrice tool.
type getStockPriceResults struct {
Symbol string `json:"symbol"`
Price float64 `json:"price,omitempty"`
Error string `json:"error,omitempty"`
}
// getStockPrice is a tool that retrieves the stock price for a given ticker symbol
// from the mockStockPrices map. It demonstrates how a function can be used as a
// tool by an agent. If the symbol is found, it returns a struct containing the
// symbol and its price. Otherwise, it returns a struct with an error message.
func getStockPrice(ctx agent.Context, input getStockPriceArgs) (getStockPriceResults, error) {
symbolUpper := strings.ToUpper(input.Symbol)
if price, ok := mockStockPrices[symbolUpper]; ok {
fmt.Printf("Tool: Found price for %s: %f\n", input.Symbol, price)
return getStockPriceResults{Symbol: input.Symbol, Price: price}, nil
}
return getStockPriceResults{}, fmt.Errorf("no data found for symbol")
}
// createStockAgent initializes and configures an LlmAgent.
// This agent is equipped with the getStockPrice tool and is instructed
// on how to respond to user queries about stock prices. It uses the
// Gemini model to understand user intent and decide when to use its tools.
func createStockAgent(ctx context.Context) (agent.Agent, error) {
stockPriceTool, err := functiontool.New(
functiontool.Config{
Name: "get_stock_price",
Description: "Retrieves the current stock price for a given symbol.",
},
getStockPrice)
if err != nil {
return nil, err
}
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
log.Fatalf("Failed to create model: %v", err)
}
return llmagent.New(llmagent.Config{
Name: "stock_agent",
Model: model,
Instruction: "You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.",
Description: "This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.",
Tools: []tool.Tool{
stockPriceTool,
},
})
}
// userID and appName are constants used to identify the user and application
// throughout the session. These values are important for logging, tracking,
// and managing state across different agent interactions.
const (
userID = "example_user_id"
appName = "example_app"
)
// callAgent orchestrates the execution of the agent for a given prompt.
// It sets up the necessary services, creates a session, and uses a runner
// to manage the agent's lifecycle. It streams the agent's responses and
// prints them to the console, handling any potential errors during the run.
func callAgent(ctx context.Context, a agent.Agent, prompt string) {
sessionService := session.InMemoryService()
// Create a new session for the agent interactions.
session, err := sessionService.Create(ctx, &session.CreateRequest{
AppName: appName,
UserID: userID,
})
if err != nil {
log.Fatalf("Failed to create the session service: %v", err)
}
config := runner.Config{
AppName: appName,
Agent: a,
SessionService: sessionService,
}
// Create the runner to manage the agent execution.
r, err := runner.New(config)
if err != nil {
log.Fatalf("Failed to create the runner: %v", err)
}
sessionID := session.Session.ID()
userMsg := &genai.Content{
Parts: []*genai.Part{
genai.NewPartFromText(prompt),
},
Role: string(genai.RoleUser),
}
for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
StreamingMode: agent.StreamingModeNone,
}) {
if err != nil {
fmt.Printf("\nAGENT_ERROR: %v\n", err)
} else {
for _, p := range event.Content.Parts {
fmt.Print(p.Text)
}
}
}
}
// RunAgentSimulation serves as the entry point for this example.
// It creates the stock agent and then simulates a series of user interactions
// by sending different prompts to the agent. This function showcases how the
// agent responds to various queries, including both successful and unsuccessful
// attempts to retrieve stock prices.
func RunAgentSimulation() {
// Create the stock agent
agent, err := createStockAgent(context.Background())
if err != nil {
panic(err)
}
fmt.Println("Agent created:", agent.Name())
prompts := []string{
"stock price of GOOG",
"What's the price of MSFT?",
"Can you find the stock price for an unknown company XYZ?",
}
// Simulate running the agent with different prompts
for _, prompt := range prompts {
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), agent, prompt)
fmt.Println("\n---")
}
}
// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "SummarizerAgent",
Model: model,
Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
Description: "An agent that summarizes text.",
})
}
// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "MainAgent",
Model: model,
Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
"After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
Description: "The main agent that can delegate tasks.",
Tools: tools,
})
}
func RunAgentAsToolSimulation() {
ctx := context.Background()
// 1. Create the Tool Agent (Summarizer)
summarizerAgent, err := createSummarizerAgent(ctx)
if err != nil {
log.Fatalf("Failed to create summarizer agent: %v", err)
}
// 2. Wrap the Tool Agent in an AgentTool
summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
SkipSummarization: true,
})
// 3. Create the Main Agent and provide it with the AgentTool
mainAgent, err := createMainAgent(ctx, summarizeTool)
if err != nil {
log.Fatalf("Failed to create main agent: %v", err)
}
// 4. Run the main agent
prompt := `
Please summarize this text for me:
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
`
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), mainAgent, prompt)
fmt.Println("\n---")
}
func main() {
fmt.Println("Attempting to run the agent simulation...")
RunAgentSimulation()
fmt.Println("\nAttempting to run the agent-as-a-tool simulation...")
RunAgentAsToolSimulation()
}
์ด ๋๊ตฌ์ ๋ฐํ ๊ฐ์ getStockPriceResults ์ธ์คํด์ค๊ฐ ๋ฉ๋๋ค.
์ด ๋๊ตฌ๋ ์ฃผ๊ฐ์ ๋ชจ์ ๊ฐ์ ๊ฒ์ํฉ๋๋ค.
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.Map;
import yahoofinance.Stock;
import yahoofinance.YahooFinance;
import java.math.BigDecimal;
public class StockPriceAgent {
private static final String APP_NAME = "stock_agent";
private static final String USER_ID = "user1234";
// No longer using mock stock data - we fetch it live!
@Schema(description = "Retrieves the current stock price for a given symbol.")
public static Map<String, Object> getStockPrice(
@Schema(description = "The stock symbol (e.g., \"AAPL\", \"GOOG\")",
name = "symbol")
String symbol) {
try {
Stock stock = YahooFinance.get(symbol.toUpperCase());
if (stock != null && stock.getQuote().getPrice() != null) {
BigDecimal currentPrice = stock.getQuote().getPrice();
System.out.println("Tool: Found live price for " + symbol + ": " + currentPrice);
return Map.of("symbol", symbol, "price", currentPrice.doubleValue());
} else {
return Map.of("symbol", symbol, "error", "No data found for symbol");
}
} catch (Exception e) {
return Map.of("symbol", symbol, "error", e.getMessage());
}
}
public static void callAgent(String prompt) {
// Create the FunctionTool from the Java method
FunctionTool getStockPriceTool = FunctionTool.create(StockPriceAgent.class, "getStockPrice");
LlmAgent stockPriceAgent =
LlmAgent.builder()
.model("gemini-2.0-flash")
.name("stock_agent")
.instruction(
"You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.")
.description(
"This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.")
.tools(getStockPriceTool) // Add the Java FunctionTool
.build();
// Create an InMemoryRunner
InMemoryRunner runner = new InMemoryRunner(stockPriceAgent, APP_NAME);
// InMemoryRunner automatically creates a session service. Create a session using the service
Session session = runner.sessionService().createSession(APP_NAME, USER_ID).blockingGet();
Content userMessage = Content.fromParts(Part.fromText(prompt));
// Run the agent
Flowable<Event> eventStream = runner.runAsync(USER_ID, session.id(), userMessage);
// Stream event response
eventStream.blockingForEach(
event -> {
if (event.finalResponse()) {
System.out.println(event.stringifyContent());
}
});
}
public static void main(String[] args) {
callAgent("stock price of GOOG");
callAgent("What's the price of MSFT?");
callAgent("Can you find the stock price for an unknown company XYZ?");
}
}
์ด ๋๊ตฌ์ ๋ฐํ ๊ฐ์ Map
๋ชจ๋ฒ ์ฌ๋ก¶
ํจ์๋ฅผ ์ ์ํ๋ ๋ฐ ์๋นํ ์ ์ฐ์ฑ์ด ์์ง๋ง ๋จ์์ฑ์ด LLM์ ์ฌ์ฉ์ฑ์ ํฅ์์ํจ๋ค๋ ์ ์ ๊ธฐ์ตํ์ธ์. ๋ค์ ์ง์นจ์ ๊ณ ๋ คํ์ธ์.
- ๋งค๊ฐ๋ณ์๊ฐ ์ ์์๋ก ์ข์ต๋๋ค: ๋ณต์ก์ฑ์ ์ค์ด๊ธฐ ์ํด ๋งค๊ฐ๋ณ์ ์๋ฅผ ์ต์ํํ์ญ์์ค.
- ๋จ์ํ ๋ฐ์ดํฐ ์ ํ: ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ง์ ํด๋์ค๋ณด๋ค
str๋ฐint์ ๊ฐ์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ ์ ํ์ ์ ํธํ์ญ์์ค. - ์๋ฏธ ์๋ ์ด๋ฆ: ํจ์์ ์ด๋ฆ๊ณผ ๋งค๊ฐ๋ณ์ ์ด๋ฆ์ LLM์ด ๋๊ตฌ๋ฅผ ํด์ํ๊ณ ํ์ฉํ๋ ๋ฐฉ์์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ํจ์์ ๋ชฉ์ ๊ณผ ์
๋ ฅ์ ์๋ฏธ๋ฅผ ๋ช
ํํ๊ฒ ๋ฐ์ํ๋ ์ด๋ฆ์ ์ ํํ์ธ์.
do_stuff()๋๋beAgent()์ ๊ฐ์ ์ผ๋ฐ์ ์ธ ์ด๋ฆ์ ํผํ์ญ์์ค. - ๋ณ๋ ฌ ์คํ์ ์ํ ๋น๋: ์ฌ๋ฌ ๋๊ตฌ๊ฐ ์คํ๋ ๋ ๋น๋๊ธฐ ์์ ์ ์ํด ๋น๋ํ์ฌ ํจ์ ํธ์ถ ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค. ๋๊ตฌ์ ๋ํ ๋ณ๋ ฌ ์คํ์ ํ์ฑํํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์ ๋ณด๋ ๋ณ๋ ฌ ์คํ์ผ๋ก ๋๊ตฌ ์ฑ๋ฅ ํฅ์์ ์ฐธ๊ณ ํ์ธ์.
์ฅ๊ธฐ ์คํ ํจ์ ๋๊ตฌ¶
์ด ๋๊ตฌ๋ ์์ด์ ํธ ์ํฌํ๋ก ์์
์ธ๋ถ์์ ์ฒ๋ฆฌ๋๊ณ ์์ด์ ํธ ์คํ์ ์ฐจ๋จํ์ง ์๊ณ ์๋นํ ์ฒ๋ฆฌ ์๊ฐ์ด ํ์ํ ์์
์ ์์ํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐ ๋์์ด ๋๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ด ๋๊ตฌ๋ FunctionTool์ ํ์ ํด๋์ค์
๋๋ค.
LongRunningFunctionTool์ ์ฌ์ฉํ ๋ ํจ์๋ ์ฅ๊ธฐ ์คํ ์์
์ ์์ํ๊ณ ์ ํ์ ์ผ๋ก ์ฅ๊ธฐ ์คํ ์์
ID์ ๊ฐ์ ์ด๊ธฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค. ์ฅ๊ธฐ ์คํ ํจ์ ๋๊ตฌ๊ฐ ํธ์ถ๋๋ฉด ์์ด์ ํธ ์คํ๊ธฐ๋ ์์ด์ ํธ ์คํ์ ์ผ์ ์ค์งํ๊ณ ์์ด์ ํธ ํด๋ผ์ด์ธํธ๊ฐ ์ฅ๊ธฐ ์คํ ์์
์ด ์๋ฃ๋ ๋๊น์ง ๊ณ์ํ ์ง ๋๋ ๊ธฐ๋ค๋ฆด์ง ๊ฒฐ์ ํ๋๋ก ํฉ๋๋ค. ์์ด์ ํธ ํด๋ผ์ด์ธํธ๋ ์ฅ๊ธฐ ์คํ ์์
์ ์งํ ์ํฉ์ ์ฟผ๋ฆฌํ๊ณ ์ค๊ฐ ๋๋ ์ต์ข
์๋ต์ ๋ค์ ๋ณด๋ผ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ์์ด์ ํธ๋ ๋ค๋ฅธ ์์
์ ๊ณ์ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์์ด์ ํธ๊ฐ ์์
์ ์งํํ๊ธฐ ์ ์ ์ฌ๋์ ์น์ธ์ด ํ์ํ ์ธ๊ฐ ์ฐธ์ฌ ์๋๋ฆฌ์ค๊ฐ ์์ต๋๋ค.
๊ฒฝ๊ณ : ์คํ ์ฒ๋ฆฌ
์ฅ๊ธฐ ์คํ ํจ์ ๋๊ตฌ๋ ์์ด์ ํธ ์ํฌํ๋ก์ ์ผ๋ถ๋ก ์ฅ๊ธฐ ์คํ ์์ ์ ์์ํ๊ณ ๊ด๋ฆฌํ๋ ๋ฐ ๋์์ด ๋๋๋ก ์ค๊ณ๋์์ง๋ง ์ค์ ์ฅ๊ธฐ ์์ ์ ์ํํ์ง๋ ์์ต๋๋ค. ์๋ฃํ๋ ๋ฐ ์๋นํ ์๊ฐ์ด ํ์ํ ์์ ์ ๊ฒฝ์ฐ ์์ ์ ์ํํ ๋ณ๋์ ์๋ฒ๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค.
ํ: ๋ณ๋ ฌ ์คํ
๋น๋ํ๋ ๋๊ตฌ ์ ํ์ ๋ฐ๋ผ ๋น๋๊ธฐ ์์ ์ ์ํด ์ค๊ณํ๋ ๊ฒ์ด ์ฅ๊ธฐ ์คํ ๋๊ตฌ๋ฅผ ๋ง๋๋ ๊ฒ๋ณด๋ค ๋ ๋์ ์๋ฃจ์ ์ผ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ ๋ณ๋ ฌ ์คํ์ผ๋ก ๋๊ตฌ ์ฑ๋ฅ ํฅ์์ ์ฐธ๊ณ ํ์ธ์.
์๋ ๋ฐฉ์¶
Python์์๋ ํจ์๋ฅผ LongRunningFunctionTool๋ก ๋ํํฉ๋๋ค. Java์์๋ ๋ฉ์๋ ์ด๋ฆ์ LongRunningFunctionTool.create()์ ์ ๋ฌํฉ๋๋ค.
-
์์: LLM์ด ๋๊ตฌ๋ฅผ ํธ์ถํ๋ฉด ํจ์๊ฐ ์ฅ๊ธฐ ์คํ ์์ ์ ์์ํฉ๋๋ค.
-
์ด๊ธฐ ์ ๋ฐ์ดํธ: ํจ์๋ ์ ํ์ ์ผ๋ก ์ด๊ธฐ ๊ฒฐ๊ณผ(์: ์ฅ๊ธฐ ์คํ ์์ ID)๋ฅผ ๋ฐํํด์ผ ํฉ๋๋ค. ADK ํ๋ ์์ํฌ๋ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์
FunctionResponse๋ด์ ํจํค์งํ์ฌ LLM์ ๋ค์ ๋ณด๋ ๋๋ค. ์ด๋ฅผ ํตํด LLM์ ์ฌ์ฉ์์๊ฒ ์๋ฆด ์ ์์ต๋๋ค(์: ์ํ, ์๋ฃ์จ, ๋ฉ์์ง). ๊ทธ๋ฐ ๋ค์ ์์ด์ ํธ ์คํ์ด ์ข ๋ฃ/์ผ์ ์ค์ง๋ฉ๋๋ค. -
๊ณ์ ๋๋ ๋๊ธฐ: ๊ฐ ์์ด์ ํธ ์คํ์ด ์๋ฃ๋ ํ. ์์ด์ ํธ ํด๋ผ์ด์ธํธ๋ ์ฅ๊ธฐ ์คํ ์์ ์ ์งํ ์ํฉ์ ์ฟผ๋ฆฌํ๊ณ ์ค๊ฐ ์๋ต์ผ๋ก ์์ด์ ํธ ์คํ์ ๊ณ์ํ ์ง(์งํ ์ํฉ์ ์ ๋ฐ์ดํธํ๊ธฐ ์ํด) ๋๋ ์ต์ข ์๋ต์ด ๊ฒ์๋ ๋๊น์ง ๊ธฐ๋ค๋ฆด์ง ๊ฒฐ์ ํ ์ ์์ต๋๋ค. ์์ด์ ํธ ํด๋ผ์ด์ธํธ๋ ๋ค์ ์คํ์ ์ํด ์ค๊ฐ ๋๋ ์ต์ข ์๋ต์ ์์ด์ ํธ์ ๋ค์ ๋ณด๋ด์ผ ํฉ๋๋ค.
-
ํ๋ ์์ํฌ ์ฒ๋ฆฌ: ADK ํ๋ ์์ํฌ๋ ์คํ์ ๊ด๋ฆฌํฉ๋๋ค. ์์ด์ ํธ ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ ์ค๊ฐ ๋๋ ์ต์ข
FunctionResponse๋ฅผ LLM์ ๋ณด๋ด ์ฌ์ฉ์ ์นํ์ ์ธ ๋ฉ์์ง๋ฅผ ์์ฑํฉ๋๋ค.
๋๊ตฌ ๋ง๋ค๊ธฐ¶
๋๊ตฌ ํจ์๋ฅผ ์ ์ํ๊ณ LongRunningFunctionTool ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ํํฉ๋๋ค.
from typing import Any
from google.adk.tools import LongRunningFunctionTool
# 1. Define the long running function
def ask_for_approval(
purpose: str, amount: float
) -> dict[str, Any]:
"""Ask for approval for the reimbursement."""
# create a ticket for the approval
# Send a notification to the approver with the link of the ticket
return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}
def reimburse(purpose: str, amount: float) -> dict[str, Any]:
"""Reimburse the amount of money to the employee."""
# send the reimbrusement request to payment vendor
return {'status': 'ok'}
# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
import (
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/model/gemini"
"google.golang.org/adk/v2/tool"
"google.golang.org/adk/v2/tool/functiontool"
"google.golang.org/genai"
)
// CreateTicketArgs defines the arguments for our long-running tool.
type CreateTicketArgs struct {
Urgency string `json:"urgency" jsonschema:"The urgency level of the ticket."`
}
// CreateTicketResults defines the *initial* output of our long-running tool.
type CreateTicketResults struct {
Status string `json:"status"`
TicketId string `json:"ticket_id"`
}
// createTicketAsync simulates the *initiation* of a long-running ticket creation task.
func createTicketAsync(ctx agent.Context, args CreateTicketArgs) (CreateTicketResults, error) {
log.Printf("TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)\n", args.Urgency, ctx.FunctionCallID())
// "Generate" a ticket ID and return it in the initial response.
ticketID := "TICKET-ABC-123"
log.Printf("ACTION: Generated Ticket ID: %s for Call ID: %s\n", ticketID, ctx.FunctionCallID())
// In a real application, you would save the association between the
// FunctionCallID and the ticketID to handle the async response later.
return CreateTicketResults{
Status: "started",
TicketId: ticketID,
}, nil
}
func createTicketAgent(ctx context.Context) (agent.Agent, error) {
ticketTool, err := functiontool.New(
functiontool.Config{
Name: "create_ticket_long_running",
Description: "Creates a new support ticket with a specified urgency level.",
},
createTicketAsync,
)
if err != nil {
return nil, fmt.Errorf("failed to create long running tool: %w", err)
}
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
return nil, fmt.Errorf("failed to create model: %v", err)
}
return llmagent.New(llmagent.Config{
Name: "ticket_agent",
Model: model,
Instruction: "You are a helpful assistant for creating support tickets. Provide the status of the ticket at each interaction.",
Tools: []tool.Tool{ticketTool},
})
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LongRunningFunctionTool;
import java.util.HashMap;
import java.util.Map;
public class ExampleLongRunningFunction {
// ์ฅ๊ธฐ ์คํ ํจ์๋ฅผ ์ ์ํฉ๋๋ค.
// ์ํ์ ๋ํ ์น์ธ์ ์์ฒญํฉ๋๋ค.
public static Map<String, Object> askForApproval(String purpose, double amount) {
// ํฐ์ผ ์์ฑ ๋ฐ ์๋ฆผ ์ ์ก ์๋ฎฌ๋ ์ด์
System.out.println(
"๋ชฉ์ ์ ๋ํ ํฐ์ผ ์์ฑ ์๋ฎฌ๋ ์ด์
: " + purpose + ", ๊ธ์ก: " + amount);
// ํฐ์ผ ๋งํฌ์ ํจ๊ป ์น์ธ์์๊ฒ ์๋ฆผ ๋ณด๋ด๊ธฐ
Map<String, Object> result = new HashMap<>();
result.put("status", "pending");
result.put("approver", "Sean Zhou");
result.put("purpose", purpose);
result.put("amount", amount);
result.put("ticket-id", "approval-ticket-1");
return result;
}
public static void main(String[] args) throws NoSuchMethodException {
// ๋ฉ์๋๋ฅผ LongRunningFunctionTool.create์ ์ ๋ฌ
LongRunningFunctionTool approveTool =
LongRunningFunctionTool.create(ExampleLongRunningFunction.class, "askForApproval");
// ์์ด์ ํธ์ ๋๊ตฌ ํฌํจ
LlmAgent approverAgent =
LlmAgent.builder()
// ...
.tools(approveTool)
.build();
}
}
์ค๊ฐ/์ต์ข ๊ฒฐ๊ณผ ์ ๋ฐ์ดํธ¶
์์ด์ ํธ ํด๋ผ์ด์ธํธ๋ ์ฅ๊ธฐ ์คํ ํจ์ ํธ์ถ๊ณผ ํจ๊ป ์ด๋ฒคํธ๋ฅผ ์์ ํ๊ณ ํฐ์ผ ์ํ๋ฅผ ํ์ธํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ์์ด์ ํธ ํด๋ผ์ด์ธํธ๋ ์ค๊ฐ ๋๋ ์ต์ข
์๋ต์ ๋ค์ ๋ณด๋ด ์งํ ์ํฉ์ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค. ํ๋ ์์ํฌ๋ ์ด ๊ฐ(None์ธ ๊ฒฝ์ฐ์๋)์ LLM์ ๋ค์ ๋ณด๋ด๋ FunctionResponse์ ๋ด์ฉ์ ํจํค์งํฉ๋๋ค.
์ฐธ๊ณ : ์ฌ๊ฐ ๊ธฐ๋ฅ์ด ์๋ ์ฅ๊ธฐ ์คํ ํจ์ ์๋ต
ADK ์์ด์ ํธ ์ํฌํ๋ก๊ฐ
์ฌ๊ฐ ๊ธฐ๋ฅ์ผ๋ก ๊ตฌ์ฑ๋ ๊ฒฝ์ฐ ์ฅ๊ธฐ ์คํ
ํจ์ ์๋ต๊ณผ ํจ๊ป ํธ์ถ ID(invocation_id) ๋งค๊ฐ๋ณ์๋ ํฌํจํด์ผ ํฉ๋๋ค.
์ ๊ณตํ๋ ํธ์ถ ID๋ ์ฅ๊ธฐ ์คํ ํจ์ ์์ฒญ์ ์์ฑํ ๊ฒ๊ณผ ๋์ผํ
ํธ์ถ์ด์ด์ผ ํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์์คํ
์ด ์๋ต์ผ๋ก ์ ํธ์ถ์ ์์ํฉ๋๋ค.
์์ด์ ํธ๊ฐ ์ฌ๊ฐ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์๋ต์ ํฌํจ๋ ์ ์๋๋ก
์ฅ๊ธฐ ์คํ ํจ์ ์์ฒญ๊ณผ ํจ๊ป ํธ์ถ ID๋ฅผ ๋งค๊ฐ๋ณ์๋ก ํฌํจํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
์ฌ๊ฐ ๊ธฐ๋ฅ ์ฌ์ฉ์ ๋ํ ์์ธํ ๋ด์ฉ์
์ค์ง๋ ์์ด์ ํธ ์ฌ๊ฐ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
Kotlin์์๋ ๋ฌ๋๊ฐ ํจ์ ์๋ต ์์ฒด์ ํธ์ถ ID๋ก๋ถํฐ ํธ์ถ์ ํ์ธํ๋ฏ๋ก runAsync์ invocationId๋ฅผ ์ ๋ฌํ ํ์๊ฐ ์์ต๋๋ค. ์ธ์
์ ํจ์ ํธ์ถ๊ณผ ์ผ์นํ๋ ID๊ฐ ์๋ ์๋ต์ ๋์ ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค.
Java ADK์๋ง ์ ์ฉ
ํจ์ ๋๊ตฌ์ ํจ๊ป ToolContext๋ฅผ ์ ๋ฌํ ๋ ๋ค์ ์ค ํ๋๊ฐ ์ฐธ์ธ์ง ํ์ธํ์ธ์.
-
์คํค๋ง๊ฐ ํจ์ ์๋ช ์ ToolContext ๋งค๊ฐ๋ณ์์ ํจ๊ป ์ ๋ฌ๋ฉ๋๋ค. ์:
๋๋ -
๋ค์
-parametersํ๋๊ทธ๊ฐ mvn ์ปดํ์ผ๋ฌ ํ๋ฌ๊ทธ์ธ์ ์ค์ ๋ฉ๋๋ค.
# Agent Interaction
async def call_agent_async(query):
def get_long_running_function_call(event: Event) -> types.FunctionCall:
# Get the long running function call from the event
if not event.long_running_tool_ids or not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_call
and event.long_running_tool_ids
and part.function_call.id in event.long_running_tool_ids
):
return part.function_call
def get_function_response(event: Event, function_call_id: str) -> types.FunctionResponse:
# Get the function response for the function call with specified id.
if not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_response
and part.function_response.id == function_call_id
):
return part.function_response
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
print("\nRunning agent...")
events_async = runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=content
)
long_running_function_call, long_running_function_response, ticket_id = None, None, None
async for event in events_async:
# Use helper to check for the specific auth request event
if not long_running_function_call:
long_running_function_call = get_long_running_function_call(event)
else:
_potential_response = get_function_response(event, long_running_function_call.id)
if _potential_response: # Only update if we get a non-None response
long_running_function_response = _potential_response
ticket_id = long_running_function_response.response['ticket-id']
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
if long_running_function_response:
# query the status of the corresponding ticket via ticket_id
# send back an intermediate / final response
updated_response = long_running_function_response.model_copy(deep=True)
updated_response.response = {'status': 'approved'}
async for event in runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
):
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
๋ค์ ์๋ ๋ค์ค ํด ์ํฌํ๋ก๋ฅผ ๋ณด์ฌ์ค๋๋ค. ๋จผ์ ์ฌ์ฉ์๋ ์์ด์ ํธ์๊ฒ ํฐ์ผ์ ๋ง๋ค๋๋ก ์์ฒญํฉ๋๋ค. ์์ด์ ํธ๋ ์ฅ๊ธฐ ์คํ ๋๊ตฌ๋ฅผ ํธ์ถํ๊ณ ํด๋ผ์ด์ธํธ๋ FunctionCall ID๋ฅผ ์บก์ฒํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ํด๋ผ์ด์ธํธ๋ ํฐ์ผ ID์ ์ต์ข
์ํ๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ํ์ FunctionResponse ๋ฉ์์ง๋ฅผ ์์ด์ ํธ์ ๋ค์ ๋ณด๋ด ๋น๋๊ธฐ ์์
์๋ฃ๋ฅผ ์๋ฎฌ๋ ์ด์
ํฉ๋๋ค.
// runTurn executes a single turn with the agent and returns the captured function call ID.
func runTurn(ctx context.Context, r *runner.Runner, sessionID, turnLabel string, content *genai.Content) string {
var funcCallID atomic.Value // Safely store the found ID.
fmt.Printf("\n--- %s ---\n", turnLabel)
for event, err := range r.Run(ctx, userID, sessionID, content, agent.RunConfig{
StreamingMode: agent.StreamingModeNone,
}) {
if err != nil {
fmt.Printf("\nAGENT_ERROR: %v\n", err)
continue
}
// Print a summary of the event for clarity.
printEventSummary(event, turnLabel)
// Capture the function call ID from the event.
for _, part := range event.Content.Parts {
if fc := part.FunctionCall; fc != nil {
if fc.Name == "create_ticket_long_running" {
funcCallID.Store(fc.ID)
}
}
}
}
if id, ok := funcCallID.Load().(string); ok {
return id
}
return ""
}
func main() {
ctx := context.Background()
ticketAgent, err := createTicketAgent(ctx)
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}
// Setup the runner and session.
sessionService := session.InMemoryService()
session, err := sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID})
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
r, err := runner.New(runner.Config{AppName: appName, Agent: ticketAgent, SessionService: sessionService})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
// --- Turn 1: User requests to create a ticket. ---
initialUserMessage := genai.NewContentFromText("Create a high urgency ticket for me.", genai.RoleUser)
funcCallID := runTurn(ctx, r, session.Session.ID(), "Turn 1: User Request", initialUserMessage)
if funcCallID == "" {
log.Fatal("ERROR: Tool 'create_ticket_long_running' not called in Turn 1.")
}
fmt.Printf("ACTION: Captured FunctionCall ID: %s\n", funcCallID)
// --- Turn 2: App provides the final status of the ticket. ---
// In a real application, the ticketID would be retrieved from a database
// using the funcCallID. For this example, we'll use the same ID.
ticketID := "TICKET-ABC-123"
willContinue := false // Signal that this is the final response.
ticketStatusResponse := &genai.FunctionResponse{
Name: "create_ticket_long_running",
ID: funcCallID,
Response: map[string]any{
"status": "approved",
"ticket_id": ticketID,
},
WillContinue: &willContinue,
}
appResponseWithStatus := &genai.Content{
Role: string(genai.RoleUser),
Parts: []*genai.Part{{FunctionResponse: ticketStatusResponse}},
}
runTurn(ctx, r, session.Session.ID(), "Turn 2: App provides ticket status", appResponseWithStatus)
fmt.Println("Long running function completed successfully.")
}
// printEventSummary provides a readable log of agent and LLM interactions.
func printEventSummary(event *session.Event, turnLabel string) {
for _, part := range event.Content.Parts {
// Check for a text part.
if part.Text != "" {
fmt.Printf("[%s][%s_TEXT]: %s\n", turnLabel, event.Author, part.Text)
}
// Check for a function call part.
if fc := part.FunctionCall; fc != nil {
fmt.Printf("[%s][%s_CALL]: %s(%v) ID: %s\n", turnLabel, event.Author, fc.Name, fc.Args, fc.ID)
}
}
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.LongRunningFunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.FunctionResponse;
import com.google.genai.types.Part;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class LongRunningFunctionExample {
private static String USER_ID = "user123";
@Schema(
name = "create_ticket_long_running",
description = """
Creates a new support ticket with a specified urgency level.
Examples of urgency are 'high', 'medium', or 'low'.
The ticket creation is a long-running process, and its ID will be provided when ready.
""")
public static void createTicketAsync(
@Schema(
name = "urgency",
description =
"The urgency level for the new ticket, such as 'high', 'medium', or 'low'.")
String urgency,
@Schema(name = "toolContext") // Ensures ADK injection
ToolContext toolContext) {
System.out.printf(
"TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)%n",
urgency, toolContext.functionCallId().orElse("N/A"));
}
public static void main(String[] args) {
LlmAgent agent =
LlmAgent.builder()
.name("ticket_agent")
.description("Agent for creating tickets via a long-running task.")
.model("gemini-2.0-flash")
.tools(
ImmutableList.of(
LongRunningFunctionTool.create(
LongRunningFunctionExample.class, "createTicketAsync")))
.build();
Runner runner = new InMemoryRunner(agent);
Session session =
runner.sessionService().createSession(agent.name(), USER_ID, null, null).blockingGet();
// --- Turn 1: User requests ticket ---
System.out.println("\n--- Turn 1: User Request ---");
Content initialUserMessage =
Content.fromParts(Part.fromText("Create a high urgency ticket for me."));
AtomicReference<String> funcCallIdRef = new AtomicReference<>();
runner
.runAsync(USER_ID, session.id(), initialUserMessage)
.blockingForEach(
event -> {
printEventSummary(event, "T1");
if (funcCallIdRef.get() == null) { // Capture the first relevant function call ID
event.content().flatMap(Content::parts).orElse(ImmutableList.of()).stream()
.map(Part::functionCall)
.flatMap(Optional::stream)
.filter(fc -> "create_ticket_long_running".equals(fc.name().orElse("")))
.findFirst()
.flatMap(FunctionCall::id)
.ifPresent(funcCallIdRef::set);
}
});
if (funcCallIdRef.get() == null) {
System.out.println("ERROR: Tool 'create_ticket_long_running' not called in Turn 1.");
return;
}
System.out.println("ACTION: Captured FunctionCall ID: " + funcCallIdRef.get());
// --- Turn 2: App provides initial ticket_id (simulating async tool completion) ---
System.out.println("\n--- Turn 2: App provides ticket_id ---");
String ticketId = "TICKET-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
FunctionResponse ticketCreatedFuncResponse =
FunctionResponse.builder()
.name("create_ticket_long_running")
.id(funcCallIdRef.get())
.response(ImmutableMap.of("ticket_id", ticketId))
.build();
Content appResponseWithTicketId =
Content.builder()
.parts(
ImmutableList.of(
Part.builder().functionResponse(ticketCreatedFuncResponse).build()))
.role("user")
.build();
runner
.runAsync(USER_ID, session.id(), appResponseWithTicketId)
.blockingForEach(event -> printEventSummary(event, "T2"));
System.out.println("ACTION: Sent ticket_id " + ticketId + " to agent.");
// --- Turn 3: App provides ticket status update ---
System.out.println("\n--- Turn 3: App provides ticket status ---");
FunctionResponse ticketStatusFuncResponse =
FunctionResponse.builder()
.name("create_ticket_long_running")
.id(funcCallIdRef.get())
.response(ImmutableMap.of("status", "approved", "ticket_id", ticketId))
.build();
Content appResponseWithStatus =
Content.builder()
.parts(
ImmutableList.of(Part.builder().functionResponse(ticketStatusFuncResponse).build()))
.role("user")
.build();
runner
.runAsync(USER_ID, session.id(), appResponseWithStatus)
.blockingForEach(event -> printEventSummary(event, "T3_FINAL"));
System.out.println("Long running function completed successfully.");
}
private static void printEventSummary(Event event, String turnLabel) {
event
.content()
.ifPresent(
content -> {
String text =
content.parts().orElse(ImmutableList.of()).stream()
.map(part -> part.text().orElse(""))
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(" "));
if (!text.isEmpty()) {
System.out.printf("[%s][%s_TEXT]: %s%n", turnLabel, event.author(), text);
}
content.parts().orElse(ImmutableList.of()).stream()
.map(Part::functionCall)
.flatMap(Optional::stream)
.findFirst() // Assuming one function call per relevant event for simplicity
.ifPresent(
fc ->
System.out.printf(
"[%s][%s_CALL]: %s(%s) ID: %s%n",
turnLabel,
event.author(),
fc.name().orElse("N/A"),
fc.args().orElse(ImmutableMap.of()),
fc.id().orElse("N/A")));
});
}
}
private fun printText(event: Event) {
val text = event.content?.parts?.mapNotNull { it.text }?.joinToString("").orEmpty()
if (text.isNotEmpty()) println("[${event.author}]: $text")
}
suspend fun callReimbursementAgent(
runner: InMemoryRunner,
userId: String,
sessionId: String,
query: String,
) {
var pendingCallId: String? = null
var pendingResponse: FunctionResponse? = null
runner
.runAsync(
userId = userId,
sessionId = sessionId,
newMessage = Content(role = "user", parts = listOf(Part(text = query))),
).collect { event ->
val callId = pendingCallId
if (callId == null) {
// A long-running call is the one whose id the event lists in longRunningToolIds.
pendingCallId =
event
.functionCalls()
.firstOrNull { it.id != null && it.id in event.longRunningToolIds }
?.id
} else {
event
.functionResponses()
.firstOrNull { it.id == callId }
?.let { pendingResponse = it }
}
printText(event)
}
// The tool returned "pending" and the invocation paused. Resume it by sending the
// outcome back as a FunctionResponse carrying the same id.
val paused = pendingResponse ?: return
val updated = paused.copy(response = mapOf("status" to "approved"))
runner
.runAsync(
userId = userId,
sessionId = sessionId,
newMessage = Content(role = "user", parts = listOf(Part(functionResponse = updated))),
).collect(::printText)
}
Python ์ ์ฒด ์: ํ์ผ ์ฒ๋ฆฌ ์๋ฎฌ๋ ์ด์
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
from google.adk.agents import Agent
from google.adk.events import Event
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from typing import Any
from google.adk.tools import LongRunningFunctionTool
# 1. Define the long running function
def ask_for_approval(
purpose: str, amount: float
) -> dict[str, Any]:
"""Ask for approval for the reimbursement."""
# create a ticket for the approval
# Send a notification to the approver with the link of the ticket
return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}
def reimburse(purpose: str, amount: float) -> dict[str, Any]:
"""Reimburse the amount of money to the employee."""
# send the reimbrusement request to payment vendor
return {'status': 'ok'}
# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
# 3. Use the tool in an Agent
file_processor_agent = Agent(
# Use a model compatible with function calling
model="gemini-2.0-flash",
name='reimbursement_agent',
instruction="""
You are an agent whose job is to handle the reimbursement process for
the employees. If the amount is less than $100, you will automatically
approve the reimbursement.
If the amount is greater than $100, you will
ask for approval from the manager. If the manager approves, you will
call reimburse() to reimburse the amount to the employee. If the manager
rejects, you will inform the employee of the rejection.
""",
tools=[reimburse, long_running_tool]
)
APP_NAME = "human_in_the_loop"
USER_ID = "1234"
SESSION_ID = "session1234"
# Session and Runner
async def setup_session_and_runner():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=file_processor_agent, app_name=APP_NAME, session_service=session_service)
return session, runner
# Agent Interaction
async def call_agent_async(query):
def get_long_running_function_call(event: Event) -> types.FunctionCall:
# Get the long running function call from the event
if not event.long_running_tool_ids or not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_call
and event.long_running_tool_ids
and part.function_call.id in event.long_running_tool_ids
):
return part.function_call
def get_function_response(event: Event, function_call_id: str) -> types.FunctionResponse:
# Get the function response for the function call with specified id.
if not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_response
and part.function_response.id == function_call_id
):
return part.function_response
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
print("\nRunning agent...")
events_async = runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=content
)
long_running_function_call, long_running_function_response, ticket_id = None, None, None
async for event in events_async:
# Use helper to check for the specific auth request event
if not long_running_function_call:
long_running_function_call = get_long_running_function_call(event)
else:
_potential_response = get_function_response(event, long_running_function_call.id)
if _potential_response: # Only update if we get a non-None response
long_running_function_response = _potential_response
ticket_id = long_running_function_response.response['ticket-id']
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
if long_running_function_response:
# query the status of the corresponding ticket via ticket_id
# send back an intermediate / final response
updated_response = long_running_function_response.model_copy(deep=True)
updated_response.response = {'status': 'approved'}
async for event in runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
):
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
# reimbursement that doesn't require approval
# asyncio.run(call_agent_async("Please reimburse 50$ for meals"))
await call_agent_async("Please reimburse 50$ for meals") # For Notebooks, uncomment this line and comment the above line
# reimbursement that requires approval
# asyncio.run(call_agent_async("Please reimburse 200$ for meals"))
await call_agent_async("Please reimburse 200$ for meals") # For Notebooks, uncomment this line and comment the above line
์ด ์์ ์ฃผ์ ์ธก๋ฉด¶
LongRunningFunctionTool: ์ ๊ณต๋ ๋ฉ์๋/ํจ์๋ฅผ ๋ํํฉ๋๋ค. ํ๋ ์์ํฌ๋ ์์ฑ๋ ์ ๋ฐ์ดํธ์ ์ต์ข ๋ฐํ ๊ฐ์ ์์ฐจ์ ์ธ FunctionResponses๋ก ๋ณด๋ด๋ ๊ฒ์ ์ฒ๋ฆฌํฉ๋๋ค.- ์์ด์ ํธ ์ง์นจ: LLM์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ฌ์ฉ์ ์ ๋ฐ์ดํธ๋ฅผ ์ํด ๋ค์ด์ค๋ FunctionResponse ์คํธ๋ฆผ(์งํ๋ฅ ๋ ์๋ฃ)์ ์ดํดํ๋๋ก ์ง์ํฉ๋๋ค.
- ์ต์ข ๋ฐํ: ํจ์๋ ์ต์ข ๊ฒฐ๊ณผ ์ฌ์ ์ ๋ฐํํ๋ฉฐ, ์ด๋ ์๋ฃ๋ฅผ ๋ํ๋ด๊ธฐ ์ํด ๋ง์ง๋ง FunctionResponse์์ ์ ์ก๋ฉ๋๋ค.
- Kotlin์๋
LongRunningFunctionToolํด๋์ค๊ฐ ์์: ํจ์์@Tool(isLongRunning = true)์ ์ด๋ ธํ ์ด์ ํ๊ฑฐ๋BaseTool์๋ธํด๋์ค์isLongRunning = true๋ฅผ ์ ๋ฌํฉ๋๋ค. - Kotlin ํด ์: ์ ๋๊ตฌ๋
Unit๋์ ๊ฐ์ ๋ฐํํ๋ฏ๋ก, ์ฌ๊ฐํ ์ ์๋(non-resumable) ์ฑ์ ํด๋น ํ๋ ์ด์คํ๋๋ฅผ ๋ชจ๋ธ์ ๋ณด๋ด๊ณ ๋ ๋ฒ์งธ๋ก ํธ์ถํ์ฌ ์ค๊ฐ ์๋ต์ผ๋ก ํด 1์ ์ข ๋ฃํฉ๋๋ค. ์ฌ๊ฐ ๊ฐ๋ฅํ(resumable) ์ฑ์ ๋ ๋ฒ์งธ ๋ชจ๋ธ ํธ์ถ ์์ด ํจ์ ํธ์ถ์์ ์ผ์ ์ค์ง๋ฉ๋๋ค.Unit์ ๋ฐํํ๋ฉด ํ๋ ์ด์คํ๋ ์๋ต์ด ์์ ํ ์ต์ ๋์ด ์ด๋ ๋ชจ๋์์๋ ํจ์ ํธ์ถ์์ ํด์ด ์ข ๋ฃ๋ฉ๋๋ค.
๋๊ตฌ๋ก์์ ์์ด์ ํธ¶
์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ์์คํ ๋ด์ ๋ค๋ฅธ ์์ด์ ํธ์ ๊ธฐ๋ฅ์ ๋๊ตฌ๋ก ํธ์ถํ์ฌ ํ์ฉํ ์ ์์ต๋๋ค. ๋๊ตฌ๋ก์์ ์์ด์ ํธ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค๋ฅธ ์์ด์ ํธ๋ฅผ ํธ์ถํ์ฌ ํน์ ์์ ์ ์ํํ๋๋ก ํ์ฌ ํจ๊ณผ์ ์ผ๋ก ์ฑ ์์ ์์ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฐ๋ ์ ์ผ๋ก ๋ค๋ฅธ ์์ด์ ํธ๋ฅผ ํธ์ถํ๊ณ ์์ด์ ํธ์ ์๋ต์ ํจ์์ ๋ฐํ ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ Python ํจ์๋ฅผ ๋ง๋๋ ๊ฒ๊ณผ ์ ์ฌํฉ๋๋ค.
ํ์ ์์ด์ ํธ์์ ์ฃผ์ ์ฐจ์ด์ ¶
๋๊ตฌ๋ก์์ ์์ด์ ํธ์ ํ์ ์์ด์ ํธ๋ฅผ ๊ตฌ๋ณํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
- ๋๊ตฌ๋ก์์ ์์ด์ ํธ: ์์ด์ ํธ A๊ฐ ์์ด์ ํธ B๋ฅผ ๋๊ตฌ๋ก ํธ์ถํ๋ฉด(๋๊ตฌ๋ก์์ ์์ด์ ํธ ์ฌ์ฉ) ์์ด์ ํธ B์ ๋ต๋ณ์ ์์ด์ ํธ A๋ก ๋ค์ ์ ๋ฌ๋๋ฉฐ, ์์ด์ ํธ A๋ ๋ต๋ณ์ ์์ฝํ๊ณ ์ฌ์ฉ์์๊ฒ ์๋ต์ ์์ฑํฉ๋๋ค. ์์ด์ ํธ A๋ ์ ์ด๊ถ์ ์ ์งํ๊ณ ํฅํ ์ฌ์ฉ์ ์ ๋ ฅ์ ๊ณ์ ์ฒ๋ฆฌํฉ๋๋ค.
- ํ์ ์์ด์ ํธ: ์์ด์ ํธ A๊ฐ ์์ด์ ํธ B๋ฅผ ํ์ ์์ด์ ํธ๋ก ํธ์ถํ๋ฉด ์ฌ์ฉ์์๊ฒ ์๋ตํ๋ ์ฑ ์์ด ์์ ํ ์์ด์ ํธ B๋ก ์ด์ ๋ฉ๋๋ค. ์์ด์ ํธ A๋ ์ฌ์ค์ ๋ฃจํ์์ ๋ฒ์ด๋ฉ๋๋ค. ๋ชจ๋ ํ์ ์ฌ์ฉ์ ์ ๋ ฅ์ ์์ด์ ํธ B์์ ์๋ตํฉ๋๋ค.
AgentTool ์ฌ์ฉ¶
์์ด์ ํธ๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ๋ ค๋ฉด AgentTool ํด๋์ค๋ก ์์ด์ ํธ๋ฅผ ๋ํํฉ๋๋ค.
์ฌ์ฉ์ ์ง์ ¶
AgentTool ํด๋์ค๋ ๋์์ ์ฌ์ฉ์ ์ง์ ํ๊ธฐ ์ํ ๋ค์ ์์ฑ์ ์ ๊ณตํฉ๋๋ค.
- skip_summarization (Python/TypeScript) / skipSummarization (Kotlin/Java): (boolean) True๋ก ์ค์ ํ๋ฉด ํ๋ ์์ํฌ๋ ๋๊ตฌ ์์ด์ ํธ์ ์๋ต์ ๋ํ LLM ๊ธฐ๋ฐ ์์ฝ์ ๊ฑด๋๋๋๋ค. ์ด๋ ๋๊ตฌ์ ์๋ต์ด ์ด๋ฏธ ์ ํ์ํ๋์ด ์๊ณ ์ถ๊ฐ ์ฒ๋ฆฌ๊ฐ ํ์ํ์ง ์์ ๊ฒฝ์ฐ์ ์ ์ฉํ ์ ์์ต๋๋ค.
์
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.agent_tool import AgentTool
from google.genai import types
APP_NAME="summary_agent"
USER_ID="user1234"
SESSION_ID="1234"
summary_agent = Agent(
model="gemini-2.0-flash",
name="summary_agent",
instruction="""You are an expert summarizer. Please read the following text and provide a concise summary.""",
description="Agent to summarize text",
)
root_agent = Agent(
model='gemini-2.0-flash',
name='root_agent',
instruction="""You are a helpful assistant. When the user provides a text, use the 'summarize' tool to generate a summary. Always forward the user's message exactly as received to the 'summarize' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.""",
tools=[AgentTool(agent=summary_agent, skip_summarization=True)]
)
# Session and Runner
async def setup_session_and_runner():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=session_service)
return session, runner
# Agent Interaction
async def call_agent_async(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
async for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
long_text = """Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages."""
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async(long_text)
import (
"google.golang.org/adk/v2/agent"
"google.golang.org/adk/v2/agent/llmagent"
"google.golang.org/adk/v2/model/gemini"
"google.golang.org/adk/v2/tool"
"google.golang.org/adk/v2/tool/agenttool"
"google.golang.org/genai"
)
// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "SummarizerAgent",
Model: model,
Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
Description: "An agent that summarizes text.",
})
}
// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "MainAgent",
Model: model,
Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
"After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
Description: "The main agent that can delegate tasks.",
Tools: tools,
})
}
func RunAgentAsToolSimulation() {
ctx := context.Background()
// 1. Create the Tool Agent (Summarizer)
summarizerAgent, err := createSummarizerAgent(ctx)
if err != nil {
log.Fatalf("Failed to create summarizer agent: %v", err)
}
// 2. Wrap the Tool Agent in an AgentTool
summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
SkipSummarization: true,
})
// 3. Create the Main Agent and provide it with the AgentTool
mainAgent, err := createMainAgent(ctx, summarizeTool)
if err != nil {
log.Fatalf("Failed to create main agent: %v", err)
}
// 4. Run the main agent
prompt := `
Please summarize this text for me:
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
`
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), mainAgent, prompt)
fmt.Println("\n---")
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.AgentTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
public class AgentToolCustomization {
private static final String APP_NAME = "summary_agent";
private static final String USER_ID = "user1234";
public static void initAgentAndRun(String prompt) {
LlmAgent summaryAgent =
LlmAgent.builder()
.model("gemini-2.0-flash")
.name("summaryAgent")
.instruction(
"You are an expert summarizer. Please read the following text and provide a concise summary.")
.description("Agent to summarize text")
.build();
// Define root_agent
LlmAgent rootAgent =
LlmAgent.builder()
.model("gemini-2.0-flash")
.name("rootAgent")
.instruction(
"You are a helpful assistant. When the user provides a text, always use the 'summaryAgent' tool to generate a summary. Always forward the user's message exactly as received to the 'summaryAgent' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.")
.description("Assistant agent")
.tools(AgentTool.create(summaryAgent, true)) // Set skipSummarization to true
.build();
// Create an InMemoryRunner
InMemoryRunner runner = new InMemoryRunner(rootAgent, APP_NAME);
// InMemoryRunner automatically creates a session service. Create a session using the service
Session session = runner.sessionService().createSession(APP_NAME, USER_ID).blockingGet();
Content userMessage = Content.fromParts(Part.fromText(prompt));
// Run the agent
Flowable<Event> eventStream = runner.runAsync(USER_ID, session.id(), userMessage);
// Stream event response
eventStream.blockingForEach(
event -> {
if (event.finalResponse()) {
System.out.println(event.stringifyContent());
}
});
}
public static void main(String[] args) {
String longText =
"""
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.""";
initAgentAndRun(longText);
}
}
์๋ ๋ฐฉ์¶
root_agent๊ฐ ๊ธด ํ ์คํธ๋ฅผ ๋ฐ์ผ๋ฉด ์ง์นจ์ ๋ฐ๋ผ ๊ธด ํ ์คํธ์ ๋ํด 'summarize' ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋๋ก ์ง์ํฉ๋๋ค.- ํ๋ ์์ํฌ๋ 'summarize'๋ฅผ
summary_agent๋ฅผ ๋ํํ๋AgentTool๋ก ์ธ์ํฉ๋๋ค. - ๋ด๋ถ์ ์ผ๋ก
root_agent๋ ๊ธด ํ ์คํธ๋ฅผ ์ ๋ ฅ์ผ๋กsummary_agent๋ฅผ ํธ์ถํฉ๋๋ค. summary_agent๋ ์ง์นจ์ ๋ฐ๋ผ ํ ์คํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ ์์ฝ์ ์์ฑํฉ๋๋ค.summary_agent์ ์๋ต์root_agent๋ก ๋ค์ ์ ๋ฌ๋ฉ๋๋ค.- ๊ทธ๋ฐ ๋ค์
root_agent๋ ์์ฝ์ ๊ฐ์ ธ์ ์ฌ์ฉ์์๊ฒ ์ต์ข ์๋ต์ ๊ณต์ํํ ์ ์์ต๋๋ค(์: "ํ ์คํธ ์์ฝ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค. ...")
Kotlin ํจ์ ๋๊ตฌ ์์ ๋ณด๊ฐ¶
์์ด ์๋ฌธ์ ์ถ๊ฐ๋ Kotlin ํจ์ ๋๊ตฌ ์์ ๋ฅผ ์๋์ ํจ๊ป ์ ๊ณตํฉ๋๋ค.
ํ์ ๋งค๊ฐ๋ณ์¶
class WeatherService {
/**
* Retrieves the weather for a city in the specified unit.
*/
@Tool
fun getWeather(
@Param("The city and state, e.g., San Francisco, CA") location: String,
@Param("The temperature unit, either 'Celsius' or 'Fahrenheit'") unit: String,
): String {
// ... function logic ...
return "Weather for $location is sunny in $unit."
}
}
์ ํ์ ๋งค๊ฐ๋ณ์¶
class FlightService {
/**
* Searches for flights.
*/
@Tool
fun searchFlights(
@Param("The destination city.") destination: String,
@Param("The desired departure date.") departureDate: String,
@Param("Number of flexible days for the search. Defaults to 0.") flexibleDays: Int? = 0,
): String {
// ... function logic ...
val days = flexibleDays ?: 0
if (days > 0) {
return "Found flexible flights to $destination."
}
return "Found flights to $destination on $departureDate."
}
}
์ ์ฒด ํจ์ ๋๊ตฌ ์์ ¶
data class StockPrice(val symbol: String, val price: Double)
class StockService {
/**
* Retrieves the stock price for a given symbol.
*/
@Tool
fun getStockPrice(
@Param("The stock symbol, e.g. GOOG") symbol: String,
): StockPrice {
// In a real app, you would call a stock price API here.
return StockPrice(symbol = symbol, price = 123.45)
}
}
fun main() =
runBlocking {
val stockService = StockService()
val agent =
LlmAgent(
name = "stock_agent",
model = Gemini(name = "gemini-flash-latest"),
instruction = Instruction("You are a helpful stock assistant."),
// .generatedTools() is used to get the tools from the annotated class.
tools = stockService.generatedTools(),
)
// ... use the agent ...
}
์ฅ๊ธฐ ์คํ ๋๊ตฌ¶
data class ReimbursementApproval(
val status: String,
val approver: String,
val purpose: String,
val amount: Double,
val ticketId: String,
)
class ReimbursementService {
/**
* Asks for approval for the reimbursement.
*/
@Tool(isLongRunning = true)
fun askForApproval(
@Param("The purpose of the reimbursement.") purpose: String,
@Param("The amount to be reimbursed.") amount: Double,
): ReimbursementApproval {
// Simulate creating a ticket and sending a notification.
// This tool returns the initial result and then the agent pauses.
return ReimbursementApproval(
status = "pending",
approver = "Sean Zhou",
purpose = purpose,
amount = amount,
ticketId = "approval-ticket-1",
)
}
}
fun main() {
val service = ReimbursementService()
val agent =
LlmAgent(
name = "approver_agent",
model = Gemini(name = "gemini-flash-latest"),
instruction = Instruction("You are a helpful reimbursement assistant."),
tools = service.generatedTools(),
)
}
AgentTool¶
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.tools.AgentTool
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Part
import kotlinx.coroutines.runBlocking
fun main() =
runBlocking {
val appName = "summary_agent"
val userId = "user1234"
// Define a specialized agent to be used as a tool
val summaryAgent =
LlmAgent(
name = "summary_agent",
model = Gemini(name = "gemini-flash-latest"),
description = "Agent to summarize text",
instruction =
Instruction(
"You are an expert summarizer. Please read the following text and provide a concise summary.",
),
)
// Wrap the agent in an AgentTool with skipSummarization = true
val summaryTool =
AgentTool(
agent = summaryAgent,
skipSummarization = true,
)
// Define the root agent that uses the summary tool
val rootAgent =
LlmAgent(
name = "root_agent",
model = Gemini(name = "gemini-flash-latest"),
instruction =
Instruction(
"You are a helpful assistant. When the user provides a text, use the 'summary_agent' tool to generate a summary. Always forward the user's message exactly as received to the 'summary_agent' tool. Present the response from the tool to the user.",
),
tools = listOf(summaryTool),
)
// Create an InMemoryRunner
val runner = InMemoryRunner(agent = rootAgent, appName = appName)
val sessionId = "session_001"
val longText =
"""
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
""".trimIndent()
val userMessage = Content(parts = listOf(Part(text = longText)))
// Run the agent and collect events
runner.runAsync(userId = userId, sessionId = sessionId, newMessage = userMessage).collect {
event ->
if (event.isFinalResponse) {
val finalResponse = event.content?.parts?.firstOrNull()?.text
println("Agent Response: $finalResponse")
}
}
}
TypeScript ํจ์ ๋๊ตฌ ์์ ๋ณด๊ฐ¶
์์ด ์๋ฌธ์ ํฌํจ๋ TypeScript ํจ์ ๋๊ตฌ ์์ ๋ฅผ ์๋์ ํจ๊ป ์ ๊ณตํฉ๋๋ค.
๊ธฐ๋ณธ ํจ์ ๋๊ตฌ¶
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Content, Part, createUserContent} from '@google/genai';
import { stringifyContent, FunctionTool, InMemoryRunner, LlmAgent } from '@google/adk';
import {z} from 'zod';
// Define the function to get the stock price
async function getStockPrice({ticker}: {ticker: string}): Promise<Record<string, unknown>> {
console.log(`Getting stock price for ${ticker}`);
// In a real-world scenario, you would fetch the stock price from an API
const price = (Math.random() * 1000).toFixed(2);
return {price: `$${price}`};
}
async function main() {
// Define the schema for the tool's parameters using Zod
const getStockPriceSchema = z.object({
ticker: z.string().describe('The stock ticker symbol to look up.'),
});
// Create a FunctionTool from the function and schema
const stockPriceTool = new FunctionTool({
name: 'getStockPrice',
description: 'Gets the current price of a stock.',
parameters: getStockPriceSchema,
execute: getStockPrice,
});
// Define the agent that will use the tool
const stockAgent = new LlmAgent({
name: 'stock_agent',
model: 'gemini-2.5-flash',
instruction: 'You can get the stock price of a company.',
tools: [stockPriceTool],
});
// Create a runner for the agent
const runner = new InMemoryRunner({agent: stockAgent});
// Create a new session
const session = await runner.sessionService.createSession({
appName: runner.appName,
userId: 'test-user',
});
const userContent: Content = createUserContent('What is the stock price of GOOG?');
// Run the agent and get the response
const response = [];
for await (const event of runner.runAsync({
userId: session.userId,
sessionId: session.id,
newMessage: userContent,
})) {
response.push(event);
}
// Print the final response from the agent
const finalResponse = response[response.length - 1];
if (finalResponse?.content?.parts?.length) {
console.log(stringifyContent(finalResponse));
}
}
main();
์ฅ๊ธฐ ์คํ ํจ์ ์ ์¶
// 1. Define the long-running function
function askForApproval(args: {purpose: string; amount: number}) {
/**
* Ask for approval for the reimbursement.
*/
// create a ticket for the approval
// Send a notification to the approver with the link of the ticket
return {
"status": "pending",
"approver": "Sean Zhou",
"purpose": args.purpose,
"amount": args.amount,
"ticket-id": "approval-ticket-1",
};
}
// 2. Instantiate the LongRunningFunctionTool class with the long-running function
const longRunningTool = new LongRunningFunctionTool({
name: "ask_for_approval",
description: "Ask for approval for the reimbursement.",
parameters: z.object({
purpose: z.string().describe("The purpose of the reimbursement."),
amount: z.number().describe("The amount to reimburse."),
}),
execute: askForApproval,
});
์ฅ๊ธฐ ์คํ ํจ์ ๋๊ตฌ ์ ์ฒด ์์ ¶
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LlmAgent, Runner, FunctionTool, LongRunningFunctionTool, InMemorySessionService, Event, stringifyContent } from '@google/adk';
import {z} from "zod";
import {Content, FunctionCall, FunctionResponse, createUserContent} from "@google/genai";
// 1. Define the long-running function
function askForApproval(args: {purpose: string; amount: number}) {
/**
* Ask for approval for the reimbursement.
*/
// create a ticket for the approval
// Send a notification to the approver with the link of the ticket
return {
"status": "pending",
"approver": "Sean Zhou",
"purpose": args.purpose,
"amount": args.amount,
"ticket-id": "approval-ticket-1",
};
}
// 2. Instantiate the LongRunningFunctionTool class with the long-running function
const longRunningTool = new LongRunningFunctionTool({
name: "ask_for_approval",
description: "Ask for approval for the reimbursement.",
parameters: z.object({
purpose: z.string().describe("The purpose of the reimbursement."),
amount: z.number().describe("The amount to reimburse."),
}),
execute: askForApproval,
});
function reimburse(args: {purpose: string; amount: number}) {
/**
* Reimburse the amount of money to the employee.
*/
// send the reimbursement request to payment vendor
return {status: "ok"};
}
const reimburseTool = new FunctionTool({
name: "reimburse",
description: "Reimburse the amount of money to the employee.",
parameters: z.object({
purpose: z.string().describe("The purpose of the reimbursement."),
amount: z.number().describe("The amount to reimburse."),
}),
execute: reimburse,
});
// 3. Use the tool in an Agent
const reimbursementAgent = new LlmAgent({
model: "gemini-2.5-flash",
name: "reimbursement_agent",
instruction: `
You are an agent whose job is to handle the reimbursement process for
the employees. If the amount is less than $100, you will automatically
approve the reimbursement.
If the amount is greater than $100, you will
ask for approval from the manager. If the manager approves, you will
call reimburse() to reimburse the amount to the employee. If the manager
rejects, you will inform the employee of the rejection.
`,
tools: [reimburseTool, longRunningTool],
});
const APP_NAME = "human_in_the_loop";
const USER_ID = "1234";
const SESSION_ID = "session1234";
// Session and Runner
async function setupSessionAndRunner() {
const sessionService = new InMemorySessionService();
const session = await sessionService.createSession({
appName: APP_NAME,
userId: USER_ID,
sessionId: SESSION_ID,
});
const runner = new Runner({
agent: reimbursementAgent,
appName: APP_NAME,
sessionService: sessionService,
});
return {session, runner};
}
function getLongRunningFunctionCall(event: Event): FunctionCall | undefined {
// Get the long-running function call from the event
if (
!event.longRunningToolIds ||
!event.content ||
!event.content.parts?.length
) {
return;
}
for (const part of event.content.parts) {
if (
part &&
part.functionCall &&
event.longRunningToolIds &&
part.functionCall.id &&
event.longRunningToolIds.includes(part.functionCall.id)
) {
return part.functionCall;
}
}
}
function getFunctionResponse(
event: Event,
functionCallId: string
): FunctionResponse | undefined {
// Get the function response for the function call with specified id.
if (!event.content || !event.content.parts?.length) {
return;
}
for (const part of event.content.parts) {
if (
part &&
part.functionResponse &&
part.functionResponse.id === functionCallId
) {
return part.functionResponse;
}
}
}
// Agent Interaction
async function callAgentAsync(query: string) {
let longRunningFunctionCall: FunctionCall | undefined;
let longRunningFunctionResponse: FunctionResponse | undefined;
let ticketId: string | undefined;
const content: Content = createUserContent(query);
const {session, runner} = await setupSessionAndRunner();
console.log("\nRunning agent...");
const events = runner.runAsync({
sessionId: session.id,
userId: USER_ID,
newMessage: content,
});
for await (const event of events) {
// Use helper to check for the specific auth request event
if (!longRunningFunctionCall) {
longRunningFunctionCall = getLongRunningFunctionCall(event);
} else {
const _potentialResponse = getFunctionResponse(
event,
longRunningFunctionCall.id!
);
if (_potentialResponse) {
// Only update if we get a non-None response
longRunningFunctionResponse = _potentialResponse;
ticketId = (
longRunningFunctionResponse.response as {[key: string]: any}
)[`ticket-id`];
}
}
const text = stringifyContent(event);
if (text) {
console.log(`[${event.author}]: ${text}`);
}
}
if (longRunningFunctionResponse) {
// query the status of the corresponding ticket via ticket_id
// send back an intermediate / final response
const updatedResponse = JSON.parse(
JSON.stringify(longRunningFunctionResponse)
);
updatedResponse.response = {status: "approved"};
for await (const event of runner.runAsync({
sessionId: session.id,
userId: USER_ID,
newMessage: createUserContent(JSON.stringify({functionResponse: updatedResponse})),
})) {
const text = stringifyContent(event);
if (text) {
console.log(`[${event.author}]: ${text}`);
}
}
}
}
async function main() {
// reimbursement that doesn't require approval
await callAgentAsync("Please reimburse 50$ for meals");
// reimbursement that requires approval
await callAgentAsync("Please reimburse 200$ for meals");
}
main();
๋๊ตฌ๋ก์์ ์์ด์ ํธ¶
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AgentTool, InMemoryRunner, LlmAgent } from '@google/adk';
import {Part, createUserContent} from '@google/genai';
/**
* This example demonstrates how to use an agent as a tool.
*/
async function main() {
// Define the summarization agent that will be used as a tool
const summaryAgent = new LlmAgent({
name: 'summary_agent',
model: 'gemini-2.5-flash',
description: 'Agent to summarize text',
instruction:
'You are an expert summarizer. Please read the following text and provide a concise summary.',
});
// Define the main agent that uses the summarization agent as a tool.
// skipSummarization is set to true, so the main_agent will directly output
// the result from the summary_agent without further processing.
const mainAgent = new LlmAgent({
name: 'main_agent',
model: 'gemini-2.5-flash',
instruction:
"You are a helpful assistant. When the user provides a text, use the 'summary_agent' tool to generate a summary. Always forward the user's message exactly as received to the 'summary_agent' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.",
tools: [new AgentTool({agent: summaryAgent, skipSummarization: true})],
});
const appName = 'agent-as-a-tool-app';
const runner = new InMemoryRunner({agent: mainAgent, appName});
const longText = `Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.`;
// Create the session before running the agent
await runner.sessionService.createSession({
appName,
userId: 'user1',
sessionId: 'session1',
});
// Run the agent with the long text to summarize
const events = runner.runAsync({
userId: 'user1',
sessionId: 'session1',
newMessage: createUserContent(longText),
});
// Print the final response from the agent
console.log('Agent Response:');
for await (const event of events) {
if (event.content?.parts?.length) {
const responsePart = event.content.parts.find((p: Part) => p.functionResponse);
if (responsePart && responsePart.functionResponse) {
console.log(responsePart.functionResponse.response);
}
}
}
}
main();
๊ทธ๋ผ์ด๋ฉ ๋ฉํ๋ฐ์ดํฐ ์ ํ¶
propagate_grounding_metadata (boolean, ๊ธฐ๋ณธ๊ฐ: False)
True๋ก ์ค์ ํ๋ฉด ํ์ ์์ด์ ํธ๊ฐ ์์ฑํ Google Search ์ถ์ฒ(citation)์ ๊ฐ์ ๊ทธ๋ผ์ด๋ฉ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์์ ์์ด์ ํธ์ ์ธ์
์ํ๋ก ์๋ ์ ๋ฌํฉ๋๋ค. ์ด ์ฌ์ฉ์ ์ง์ ์ ํตํด ์ ๋ฌธ ๊ฒ์ ์์ด์ ํธ๋ฅผ ๋๊ตฌ๋ก ์ฌ์ฉํ ๋ ์ถ์ฒ ์ ๋ณด๊ฐ ๋ณด์กด๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
from google.adk.agents import Agent
from google.adk.tools import AgentTool
search_specialist_agent = Agent(
# ์ ๋๋ ์ดํฐ๋ธ ๋ชจ๋ธ ์ง์
model="gemini-flash-latest",
name="search_specialist_agent",
instruction=(
"You are a search expert. Find and "
"compile citations on requested topics."
),
# ์ฌ๊ธฐ์ ๊ฒ์ ๋๊ตฌ ์ถ๊ฐ
)
search_agent_tool = AgentTool(
agent=search_specialist_agent,
# ์ถ์ฒ ์ ๋ณด๋ฅผ ๋ฃจํธ ์์ด์ ํธ๊น์ง ์จ์ ํ ์ ์ง
propagate_grounding_metadata=True
)
root_agent = Agent(
model="gemini-flash-latest",
name="root_agent",
description=(
"A central coordinator that delegates "
"to specialist agents."
),
tools=[search_agent_tool]
)
ํ๋ฌ๊ทธ์ธ ์์ ์ ์ด¶
์์ด์ ํธ๋ฅผ AgentTool๋ก ๊ฐ์ ๋ include_plugins ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์์ ๋ฌ๋๋ก๋ถํฐ ํ๋ฌ๊ทธ์ธ์ ์์๋ฐ์์ง ์ฌ๋ถ๋ฅผ ์ ์ดํ ์ ์์ต๋๋ค.
include_plugins=True(๊ธฐ๋ณธ๊ฐ): ํ์ ์์ด์ ํธ๋ ์์ ์์ด์ ํธ์ ๋ชจ๋ ํ๋ฌ๊ทธ์ธ์ ์์๋ฐ์ ์ถ์ ์คํฌ(trace spans) ๋ฐ ์ด๋ฒคํธ ์คํธ๋ฆฌ๋ฐ์ ์ ์งํฉ๋๋ค.include_plugins=False: ํ์ ์์ด์ ํธ๋ ์์ ์์ด์ ํธ์ ํ๋ฌ๊ทธ์ธ ํ๊ฒฝ์ ์์๋ฐ์ง ์๊ณ ๊ฒฉ๋ฆฌ๋ ํ๊ฒฝ์์ ์คํ๋ฉ๋๋ค. ์์ด์ ํธ์ ์คํ์ ์๋ฆฝ์ (self-contained)์ผ๋ก ์ ์งํ๊ณ ์์ ์์ด์ ํธ์ ํ๋ฌ๊ทธ์ธ ํ๊ฒฝ์ ์ํฅ์ ๋ฐ์ง ์๋๋ก ํ๋ ค๋ฉด ์ด ์ค์ ์ ์ฌ์ฉํ์ธ์.
from google.adk.tools import agent_tool
# MyImageAgent๋ฅผ ์ํ ํ๋ ์ด์คํ๋ ์ ์
class MyImageAgent:
def __init__(
self, name="My Agent", description="A simple image agent."
):
self.name = name
# description ์์ฑ ์ถ๊ฐ
self.description = description
# ์์ 1: ์์ ํ๋ฌ๊ทธ์ธ์ผ๋ก๋ถํฐ MyImageAgent ๊ฒฉ๋ฆฌ
my_isolated_tool = agent_tool.AgentTool(
agent=MyImageAgent(), # MyImageAgent ์ธ์คํด์คํ
include_plugins=False
)
# ์์ 2: ํ๋ฌ๊ทธ์ธ ์์
my_observable_tool = agent_tool.AgentTool(
agent=MyImageAgent(), # MyImageAgent ์ธ์คํด์คํ
include_plugins=True
)