의존성
의존성 (Dependencies)
에이전트가 일을 하려면 시스템 프롬프트·도구·출력 검증기가 실행될 때 쓸 데이터와 서비스가 필요해요. Pydantic AI는 그걸 의존성 주입(dependency injection) 시스템으로 전달하는데, Python의 관행을 그대로 따르면서 타입 세이프하고, 이해하기 쉽고, 테스트와 프로덕션 배포가 쉬워요.
출처: 공식문서
의존성 정의하기
의존성은 어떤 Python 타입이든 될 수 있어요. 단순한 경우엔 객체 하나(예: HTTP 연결)를 넘겨도 되지만, 여러 객체를 담아야 한다면 dataclass가 편리한 컨테이너예요.
의존성을 요구하는 에이전트를 정의하는 예시를 볼게요. (이 예시에선 의존성을 실제로 쓰진 않지만, 선언 방식은 정확히 같아요.)
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent
@dataclass
class MyDeps: # (1)
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps, # (2)
)
async def main():
async with httpx.AsyncClient() as client:
deps = MyDeps('foobar', client)
result = await agent.run(
'Tell me a joke.',
deps=deps, # (3)
)
print(result.output)
#> Did you hear about the toothpaste scandal? They called it Colgate.
포인트를 짚으면, 의존성을 담는 dataclass를 정의하고(1), 그 타입을 Agent 생성자의 deps_type 인자에 넘겨요(2) — 여기서 중요한 건 타입을 넘긴다는 점이에요. 인스턴스가 아니라요. 그래서 이 파라미터는 런타임에 실제로 안 쓰이지만, 에이전트에 대한 완전한 타입 체크를 얻을 수 있어요. 실행할 때는 dataclass 인스턴스를 deps 파라미터로 넘겨요(3).
의존성 접근하기
의존성은 RunContext 타입을 통해 접근해요. 이 타입은 시스템 프롬프트 함수 등에서 첫 번째 파라미터로 쓰여요.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps,
)
@agent.system_prompt # (1)
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str: # (2)
response = await ctx.deps.http_client.get( # (3)
'https://example.com',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'}, # (4)
)
response.raise_for_status()
return f'Prompt: {response.text}'
RunContext는 의존성 타입으로 파라미터화되고, 타입이 틀리면 정적 타입 체커가 에러를 내요. .deps 속성을 통해 HTTP 클라이언트(3)와 API 키(4)에 접근해요.
.deps 외에도 RunContext는 실행 중인 에이전트에 .agent 속성으로 접근하게 해줘요. 도구·훅·capability가 name이나 output_type 같은 에이전트 속성을 읽을 때 유용해요. .realtime 속성은 모델 타입 체크 없이 실시간 세션인지 구분해주고, .realtime_session은 연결된 후 도구·훅에 라이브 RealtimeSession을 노출해요.
비동기 vs 동기 의존성
시스템 프롬프트 함수, 함수 도구, 출력 검증기는 모두 에이전트 실행의 async 컨텍스트 안에서 돌아요. 이 함수들이 async def가 아니라 동기 함수(def)로 정의돼 있으면, Pydantic AI는 run_in_executor로 쓰레드 풀에서 호출해요. 동기 의존성도 동작하지만, 의존성이 I/O를 수행한다면 async 함수를 선호해요.
참고할 점 하나 — 동기/비동기 의존성의 선택은 run/run_sync의 선택과 완전히 독립적이에요. run_sync는 그냥 run을 감싼 것뿐이고, 에이전트는 항상 async 컨텍스트에서 실행돼요.
동기 의존성 예시는 httpx.Client(동기)를 쓰고 시스템 프롬프트 함수도 평범한 함수가 되는 걸 제외하면 같아요.
전체 예제
시스템 프롬프트 외에도 의존성은 도구와 출력 검증기에서도 쓸 수 있어요.
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, ModelRetry, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
agent = Agent(
'openai:gpt-5.2',
deps_type=MyDeps,
)
@agent.system_prompt
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str:
response = await ctx.deps.http_client.get('https://example.com')
response.raise_for_status()
return f'Prompt: {response.text}'
@agent.tool # (1)
async def get_joke_material(ctx: RunContext[MyDeps], subject: str) -> str:
response = await ctx.deps.http_client.get(
'https://example.com#jokes',
params={'subject': subject},
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
)
response.raise_for_status()
return response.text
@agent.output_validator # (2)
async def validate_output(ctx: RunContext[MyDeps], output: str) -> str:
response = await ctx.deps.http_client.post(
'https://example.com#validate',
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
params={'query': output},
)
if response.status_code == 400:
raise ModelRetry(f'invalid response: {response.text}')
response.raise_for_status()
return output
여기서 도구에는 tool 데코레이터를, 출력 검증기에는 output_validator 데코레이터를 쓰고 RunContext를 첫 인자로 받아요. 검증기가 400을 받으면 ModelRetry를 던져 모델에게 다시 시도해 달라고 요청해요.
의존성 덮어쓰기 (Overriding)
에이전트를 테스트할 때 의존성을 커스터마이즈할 수 있으면 유용해요. 단위 테스트에서 에이전트를 직접 호출할 수도 있지만, 에이전트를 호출하는 애플리케이션 코드를 그대로 두고 의존성만 덮어쓸 수도 있어요. 이건 에이전트의 override 메서드로 해요.
# joke_app.py
from dataclasses import dataclass
import httpx
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
http_client: httpx.AsyncClient
async def system_prompt_factory(self) -> str: # (1)
response = await self.http_client.get('https://example.com')
response.raise_for_status()
return f'Prompt: {response.text}'
joke_agent = Agent('openai:gpt-5.2', deps_type=MyDeps)
@joke_agent.system_prompt
async def get_system_prompt(ctx: RunContext[MyDeps]) -> str:
return await ctx.deps.system_prompt_factory() # (2)
async def application_code(prompt: str) -> str: # (3)
...
...
# now deep within application code we call our agent
async with httpx.AsyncClient() as client:
app_deps = MyDeps('foobar', client)
result = await joke_agent.run(prompt, deps=app_deps) # (4)
return result.output
# test_joke_app.py
from joke_app import MyDeps, application_code, joke_agent
class TestMyDeps(MyDeps): # (1)
async def system_prompt_factory(self) -> str:
return 'test prompt'
async def test_application_code():
test_deps = TestMyDeps('test_key', None) # (2)
with joke_agent.override(deps=test_deps): # (3)
joke = await application_code('Tell me a joke.') # (4)
assert joke.startswith('Did you hear about the toothpaste scandal?')
테스트에서 MyDeps의 서브클래스를 만들어 시스템 프롬프트 팩토리를 오버라이드하고(1), 테스트 의존성 인스턴스를 만들고(http_client는 안 쓰이니 None)(2), with joke_agent.override(deps=test_deps): 블록 동안 에이전트의 의존성을 덮어써요(3). 그러면 애플리케이션 코드를 안전하게 호출할 수 있고, 에이전트는 덮어쓴 의존성을 사용해요(4).