에이전트

**에이전트**는 루프에서 실행되는 모델로, 작업이 완료될 때까지 호출할 다음 도구를 선택합니다. 이 장의 나머지 부분은 그것이 무엇을 의미하는지, 어디서 진정한 가치를 보이는지, 그리고 어디서 조용히 비용을 치르는지에 대해 설명합니다.
에이전트 루프
도구 사용에서 모델은 하나의 함수를 호출했고 당신이 결과를 다시 보냈습니다. 하지만 일부 작업은 여러 단계를 거치며, 미리 단계를 알 수 없습니다. "가장 저렴한 항공편을 찾아서 예약하기"는 검색, 비교, 일정 확인, 예약을 의미할 수 있으며, 모델이 진행하면서 그 순서를 파악해야 합니다.
따라서 단일 도구 호출을 처리하는 대신 전체를 루프에 넣습니다. 이 반복 사이클을 **에이전트 루프**라고 하며, 작업이 완료될 때까지 반복되는 한 번의 턴입니다:
- 대화와 사용 가능한 도구를 모델에 보냅니다.
- 모델은 도구를 호출하거나 최종 답변을 제공합니다.
- 도구를 호출했다면 실행하고, 결과를 대화에 추가한 후 1단계로 돌아갑니다.
- 최종 답변을 제공했다면 중단합니다.
매 턴마다 모델은 이전 도구 결과를 포함하여 지금까지 일어난 모든 것을 보고 다음 이동을 결정합니다. 이것이 도구 사용과의 전부 차이입니다: 모델이 완료되었다고 생각할 때까지 루프를 계속합니다.
진정한 변화는 누가 조종하는가입니다. 지금까지의 모든 장에서 당신의 코드가 제어 흐름을 결정했습니다: 모델을 호출할 때, 도구를 실행할 때, 다음에 무엇이 올지를 당신이 선택했습니다. 에이전트는 그 결정을 모델에 맡깁니다. 루프는 계속 "다음에 무엇이 일어나야 하나?"라고 묻고 모델의 예측이 매번 답입니다. 당신은 제어를 유연성으로 바꾸며, 이 거래가 도달 범위와 위험의 원천입니다.
내부적으로는 새로운 일이 일어나지 않습니다. 이것은 성장하는 컨텍스트에 대한 동일한 예측이며, 도구 사용 왕복을 반복한 것입니다. 모델은 계획을 세우고 따르지 않습니다. 매 턴마다 현재 볼 수 있는 것에서 단 하나의 최선의 다음 단계를 예측한 후 루프가 다시 실행됩니다.
그것이 작동할 때는 목표를 향한 추론처럼 보입니다. 그렇지 않을 때는 에이전트가 자신에게 루프를 돌거나 막힌 끝으로 헤맬 수 있습니다. 왜냐하면 시스템의 어느 부분도 전체 계획을 유지하지 않기 때문입니다.
최소 에이전트
여기 루프의 코드입니다. 이것은 도구 사용의 도구 정의 및 구현을 재사용하고 모델이 도구를 요청하는 것을 멈출 때까지 실행하는 루프로 감쌉니다.
import json
def run_agent(user_text, tools, tool_impls, max_steps=6):
messages = [{"role": "user", "content": user_text}]
step = 0
while step < max_steps:
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
message = response.choices[0].message
messages.append(message)
calls = message.tool_calls
if not calls:
return message.content # 도구를 원하지 않음: 이것이 최종 답변입니다
# 요청된 모든 도구를 실행하고 결과를 다시 피드합니다
for call in calls:
args = json.loads(call.function.arguments)
result = tool_impls[call.function.name](args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
step += 1
return "Stopped: the agent reached its step limit." # 안전망위에서 아래로 읽으십시오: 모델을 호출하고, 메시지를 대화에 밀어넣고, 도구를 요청했다면 모두 실행하고 결과를 추가한 후 다시 루프를 돕니다. 모델이 도구 호출 대신 텍스트를 반환하면 그것이 최종 답변이고 반환합니다.
max_steps 값은 **단계 제한**을 설정하며, 루프가 도망가지 않도록 하는 안전망이며, 다음 섹션이 다루는 것입니다. 여기 모양은 한 공급자의 SDK와 일치합니다; 필드 이름은 공급자마다 다르지만 루프는 모든 곳에서 동일합니다.
중단과 가드레일
모델이 중단할 때를 결정하도록 하는 루프는 중단하지 못할 수 있습니다. 도구를 영원히 호출하거나 자신을 반복하거나 막힌 끝을 쫓을 수 있습니다. 모든 단계가 토큰으로 청구되는 모델 호출이므로, 도망가는 에이전트는 빠르게 돈과 시간을 소진합니다.
그래서 가드레일, 루프 주위에 두는 한계는 최종 폴리시 단계가 아닌 설계의 부분입니다:
- 단계 제한.
max_steps가 하는 방식으로 루프의 개수를 한계하십시오. 도달되면 중단하고 반환합니다. - 도구 입력 검증. 모델이 인수를 선택하므로, 진정한 결과를 가진 것을 실행하기 전에 확인하십시오.
- 도구가 할 수 있는 것을 제한하십시오. 에이전트에 필요한 최소 전력을 제공하십시오. 지우는 것보다 읽는 도구가 더 안전합니다.
- 비용을 감시하십시오. 작업당 여러 모델 호출이 빠르게 곱해지므로 토큰을 추적하고 예산을 설정하십시오.
이것들을 묶는 스레드: 모델이 경로를 즉흥하므로, 당신이 그것이 하지 않을 한계를 배치합니다. 에이전트가 적어도 한 번은 잘못된 일을 할 것이라고 가정하고, 그것을 생존할 수 있게 만드십시오.
워크플로우가 에이전트를 이길 때
에이전트는 흥미로우므로 필요하지 않을 때 하나를 잡도록 유혹합니다. 일반 규칙: 이미 단계를 알고 있으면 에이전트를 사용하지 마십시오. 당신이 제어하는 경로 대신 모델이 찾기를 바라는 일반 코드, **워크플로우**로 기록하십시오.
워크플로우는 더 신뢰할 수 있고, 더 저렴하고, 디버깅이 훨씬 더 쉬우며, 경로를 제어하기 때문입니다. 에이전트를 열린 작업을 위해 절약하십시오. 단계가 발견된 것에 의존합니다.
많은 에이전트처럼 보이는 기능은 실제로 구조화된 출력 호출이나 RAG 조회이며 손으로 연결됩니다. 에이전트를 잡으십시오. 단계를 미리 기록할 수 없을 때만.
실제로
위의 run_agent 함수는 전부입니다. 질문, 도구 집합, 그들의 구현을 제공하고, 완료될 때까지 또는 단계 제한에 도달할 때까지 루프합니다. 작은 작업은 루프가 그 자리를 얻는 곳을 보여줍니다:
answer = run_agent(
"What's the weather in Oslo and should I bring an umbrella?",
tools, # 도구 사용 장의 get_weather를 포함합니다
tool_impls, # {"get_weather": ...}
)
# 에이전트가 get_weather를 호출하고 "흐림"을 읽은 후 하나의 최종 턴에서 답변합니다:
# "It's cloudy in Oslo at 18 degrees, so an umbrella is a sensible idea."한 도구, 한 루프, 한 답변. 동일한 구조는 형태를 변경하지 않고 여러 도구와 여러 단계로 확장합니다. 일단 이렇게 능력 있는 것을 구축할 수 있으면, 다음 질문은 어떻게 실제로 작동하는지 알 수 있으며, 이것이 evals입니다.
run_agent 루프는 전체 패턴입니다: 질문, 도구, 그들의 구현을 제공하고, 모델이 도구 요청을 멈추거나 한계에 도달할 때까지 루프합니다. 한 도구와 한 루프는 형태를 변경하지 않고 많은 것으로 확장합니다. 다음 질문은 실제로 작동하는지 아는 방법이며, 그것이 evals입니다. 
