Bank Support

Bank Support

은행 고객 지원 에이전트를 Pydantic AI로 만드는, 작지만 완전한 예시예요. 고객 이름을 시스템 프롬프트에 동적으로 넣고, 카드 잔액 조회 같은 작업을 도구로 처리하며, 응답을 구조화된 형태로 강제하는 흐름을 모두 보여줍니다.

출처: 공식문서

예시가 보여주는 것

예시 실행하기

의존성을 설치하고 환경 변수를 설정했다면, 실행해요.

python -m pydantic_ai_examples.bank_support
uv run -m pydantic_ai_examples.bank_support

(혹은 PYDANTIC_AI_MODEL=gemini-3-flash-preview ...)

예시 코드

bank_support.py

import sqlite3
from dataclasses import dataclass

from pydantic import BaseModel

from pydantic_ai import Agent, RunContext


@dataclass
class DatabaseConn:
    """A wrapper over the SQLite connection."""

    sqlite_conn: sqlite3.Connection

    async def customer_name(self, *, id: int) -> str | None:
        res = cur.execute('SELECT name FROM customers WHERE id=?', (id,))
        row = res.fetchone()
        if row:
            return row[0]
        return None

    async def customer_balance(self, *, id: int) -> float:
        res = cur.execute('SELECT balance FROM customers WHERE id=?', (id,))
        row = res.fetchone()
        if row:
            return row[0]
        else:
            raise ValueError('Customer not found')


@dataclass
class SupportDependencies:
    customer_id: int
    db: DatabaseConn


class SupportOutput(BaseModel):
    support_advice: str
    """Advice returned to the customer"""
    block_card: bool
    """Whether to block their card or not"""
    risk: int
    """Risk level of query"""


support_agent = Agent(
    'openai:gpt-5.2',
    deps_type=SupportDependencies,
    output_type=SupportOutput,
    instructions=(
        'You are a support agent in our bank, give the '
        'customer support and judge the risk level of their query. '
        "Reply using the customer's name."
    ),
)


@support_agent.instructions
async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
    customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
    return f"The customer's name is {customer_name!r}"


@support_agent.tool
async def customer_balance(ctx: RunContext[SupportDependencies]) -> str:
    """Returns the customer's current account balance."""
    balance = await ctx.deps.db.customer_balance(
        id=ctx.deps.customer_id,
    )
    return f'${balance:.2f}'


if __name__ == '__main__':
    with sqlite3.connect(':memory:') as con:
        cur = con.cursor()
        cur.execute('CREATE TABLE customers(id, name, balance)')
        cur.execute("""
            INSERT INTO customers VALUES
                (123, 'John', 123.45)
        """)
        con.commit()

        deps = SupportDependencies(customer_id=123, db=DatabaseConn(sqlite_conn=con))
        result = support_agent.run_sync('What is my balance?', deps=deps)
        print(result.output)
        """
        support_advice='Hello John, your current account balance, including pending transactions, is $123.45.' block_card=False risk=1
        """

        result = support_agent.run_sync('I just lost my card!', deps=deps)
        print(result.output)
        """
        support_advice="I'm sorry to hear that, John. We are temporarily blocking your card to prevent unauthorized transactions." block_card=True risk=8
        """

여기서 구조를 하나씩 뜯어볼게요.

의존성 주입SupportDependencies(고객 id + DB 연결 래퍼)를 deps_type으로 선언해요. run_sync(...)을 호출할 때 deps=deps로 실제 값을 넘기죠. 도구 함수는 ctx.deps.db로 DB 접근해요.

다이나믹 시스템 프롬프트@support_agent.instructions 데코레이터가 붙은 함수가 ctx.deps.db.customer_name(...)을 호출해 고객 이름을 가져와 프롬프트에 추가해요. 그래서 "John에게 답변해" 같은 지시가 실제 이름으로 동적으로 완성돼요.

구조화된 출력output_type=SupportOutputsupport_advice, block_card, risk 세 필드를 정의해요. 모델은 반드시 이 Pydantic 모델 형태로 응답해야 해요. 두 예시 실행 결과를 보면, "잔액 조회" 질문은 block_card=False risk=1로, "카드 분실" 질문은 block_card=True risk=8로 다르게 판단하는 걸 알 수 있어요.

도구@support_agent.tool이 붙은 customer_balance가 잔액 조회를 처리해요. LLM이 질문에 따라 이 도구를 부르고, 그 결과를 프롬프트에 반영해 구조화된 답을 만들어내요.

참고로 DatabaseConn의 메서드 안에 cur이라는 변수가 보이는데, 이는 예시 코드의 단순화를 위해 생략된 부분이에요. 실제 실행 시에는 연결의 커서를 명시적으로 얻어 사용해야 해요.

더 알아보기 (Learn more)