메모
점진적 도구 노출 API(FunctionInvocationContext.add_tools / remove_tools)는 현재 Python 전용입니다.
이 페이지에서는 워크플로를 요구하지 않고 모델이 호출할 수 있는 도구와 단일 에이전트 실행 내의 순서를 제어하기 위한 세 가지 보완 기술을 다룹니다.
- 점진적 도구 노출 - 도구 또는 함수 미들웨어 내부에서 런타임 시 도구를 추가하거나 제거하므로 모델은 사용할 준비가 된 도구만 볼 수 있습니다.
- 미들웨어 게이팅 - 함수 미들웨어를 사용하여 호출 인수의 유효성을 검사하고 기본 함수를 실행하지 않고 수정 피드백을 반환합니다.
-
강제 첫 번째 호출 - 모델이 다른 도구 앞에 특정 도구를 호출하도록 요구하는 데 사용합니다
tool_choice.
메모
"always call get_record before update_record"와 같은 쌍으로 정렬하는 제약 조건에는 워크플로가 필요하지 않습니다. 이 페이지의 기술은 단일 실행 내에서 해당 패턴을 처리합니다. 워크플로는 실행 또는 병렬 분기에서 진정한 다단계 오케스트레이션을 위한 것입니다.
점진적 도구 노출
점진적 도구 노출을 사용하면 작은 도구 집합으로 실행을 시작하고 동일한 실행 내에서 이전 도구 결과에 대한 응답으로 도구를 추가하거나 제거할 수 있습니다. 모델은 함수 호출 루프의 다음 반복 에서 업데이트된 집합만 확인합니다. 변경 내용이 적용되기 전에 진행 중인 일괄 처리에서 이미 요청된 도구 호출이 계속 실행됩니다.
API는 실험적이며 다음을 기반으로 합니다.FunctionInvocationContext
| 회원 | 설명 |
|---|---|
ctx.tools |
현재 실행에 대한 변경 가능한 list 라이브 도구입니다.
None 함수 호출 루프 외부에서 함수가 호출될 때 |
ctx.add_tools(tools) |
하나 이상의 도구를 추가합니다. 호출 가능한 항목은 FunctionTool로 래핑됩니다. 동일한 개체를 다시 추가해도 아무 동작도 하지 않으며, 이름이 중복된 다른 개체를 추가하면 ValueError 예외가 발생합니다. 전부 아니면 전무: 배치 내 도구 중 하나라도 오류를 발생시키면 어느 것도 추가되지 않습니다. |
ctx.remove_tools(tools) |
이름, 도구 객체 또는 호출 가능한 객체를 기준으로 제거합니다. 목록에 없는 이름은 자동으로 무시됩니다. |
두 도우미는 프로세스에서 처음 호출될 때 ExperimentalWarning를 내보낸다(기능 ID PROGRESSIVE_TOOLS). 함수 호출 루프 외부에서 두 헬퍼 중 하나를 호출하면 RuntimeError 예외가 발생합니다.
Important
도구 목록은 모든 새 agent.run() 호출에서 원래 집합으로 다시 설정되므로 모든 게이트는 각 턴에 대해 자동으로 다시 무장합니다.
메모
점진적 도구 노출은 표준 함수 호출 루프에만 적용됩니다. 모델에서 개별 도구 스키마가 아닌 단일 코드 실행 화면이 표시되는 CodeAct 공급자(agent-framework-monty, agent-framework-hyperlight)에서는 사용할 수 없습니다. CodeAct 샌드박스 내부에서 add_tools 또는 remove_tools을 호출하면 RuntimeError 예외가 발생합니다. CodeAct 에이전트에 대한 도구 집합을 변경하려면 실행 간에 공급자 고유의 add_tools / remove_tool / clear_tools 메서드를 사용합니다.
로더 도구 패턴
작은 "로더" 도구 집합을 미리 등록하고 모델이 요청 시 추가 도구를 끌어올 수 있도록 합니다. 이렇게 하면 초기 스키마가 작게 유지되어 도구 선택 정확도가 향상되고 비용이 절감됩니다.
import asyncio
import warnings
from typing import Annotated
from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.openai import OpenAIChatClient
from pydantic import Field
warnings.filterwarnings("ignore", category=UserWarning) # suppress ExperimentalWarning for brevity
@tool(approval_mode="never_require")
def factorial(n: Annotated[int, Field(description="A non-negative integer.")]) -> str:
"""Compute the factorial of n."""
if n < 0:
return "Error: n must be a non-negative integer."
result = 1
for value in range(2, n + 1):
result *= value
return f"{n}! = {result}"
@tool(approval_mode="never_require")
def fibonacci(n: Annotated[int, Field(description="The 0-based index in the Fibonacci sequence.")]) -> str:
"""Compute the n-th Fibonacci number."""
if n < 0:
return "Error: n must be a non-negative integer."
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return f"fib({n}) = {a}"
# The ctx parameter is injected by the framework and is NOT visible to the model.
@tool(approval_mode="never_require")
def load_math_tools(ctx: FunctionInvocationContext) -> str:
"""Load additional math tools (factorial, fibonacci) so they can be used."""
ctx.add_tools([factorial, fibonacci])
return "Loaded math tools: factorial, fibonacci. You can now call them."
async def main() -> None:
agent = Agent(
client=OpenAIChatClient(),
name="MathAgent",
instructions=(
"You are a math assistant. "
"If you need math capabilities that are not yet available, call load_math_tools first."
),
tools=[load_math_tools], # agent starts with only the loader
)
print(await agent.run("What is 5 factorial?"))
asyncio.run(main())
전체 실행 가능한 샘플은 python/samples/02-agents/tools/dynamic_tool_exposure.py에 있습니다.
게이트 패턴
처음에 읽기 도구만 등록합니다. 읽기 도구는 성공적으로 인출한 후에 쓰기 도구를 추가하므로 읽기 도구가 실행되기 전에 모델이 쓰기 도구를 호출할 수 없습니다.
from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.openai import OpenAIChatClient
_last_fetched_id: str | None = None
@tool(approval_mode="never_require")
def get_record(record_id: str, ctx: FunctionInvocationContext) -> str:
"""Fetch a record. Unlocks update_record for the same record."""
global _last_fetched_id
_last_fetched_id = record_id
ctx.add_tools(update_record) # gate: expose the write tool now
return f"Record {record_id}: title='Example record', status='open'"
@tool(approval_mode="never_require")
def update_record(record_id: str, status: str) -> str:
"""Update the status of a record."""
return f"Updated record {record_id} to status '{status}'."
agent = Agent(
client=OpenAIChatClient(),
name="RecordAgent",
instructions="You help manage records. Fetch a record before updating it.",
tools=[get_record], # update_record is hidden until get_record runs
)
ctx.tools가 매 실행 시작 시 [get_record]로 재설정되므로, 게이트는 각 대화 턴마다 자동으로 다시 활성화됩니다.
미들웨어 게이팅
함수 미들웨어는 보류 중인 도구 호출의 인수를 검사하고 호출context.result하지 않고 설정 call_next() 하여 기본 함수가 실행되기 전에 이를 거부할 수 있습니다. 할당된 context.result 문자열이 함수 결과로 모델에 반환되어 수정 피드백을 제공합니다.
이는 스키마 정의 시간에 사용할 수 없는 정보가 필요한 인수 수준 검사에 유용합니다. 예를 들어 업데이트가 실행 초기에 가져온 것과 동일한 항목을 대상으로 하는지 확인합니다.
from collections.abc import Awaitable, Callable
from agent_framework import FunctionInvocationContext
_last_fetched_id: str | None = None
async def enforce_read_before_write(
context: FunctionInvocationContext,
call_next: Callable[[], Awaitable[None]],
) -> None:
"""Reject update_record calls that target a different record than the one fetched."""
if context.function.name == "update_record":
requested_id = context.arguments.get("record_id") if hasattr(context.arguments, "get") else None
if requested_id != _last_fetched_id:
# Set result without calling call_next — the function never executes.
context.result = (
f"Error: you must fetch record '{requested_id}' before updating it. "
f"Last fetched record was '{_last_fetched_id}'."
)
return
await call_next()
에이전트에 미들웨어를 추가합니다.
agent = Agent(
client=OpenAIChatClient(),
name="RecordAgent",
instructions="Fetch a record before updating it.",
tools=[get_record, update_record],
middleware=[enforce_read_before_write],
)
함수 미들웨어에 대한 자세한 내용은 미들웨어 및 결과 재정의 정의를 참조하세요.
tool_choice로 도구 호출 강제하기
모델이 특정 도구를 첫 동작으로 호출하도록 하려면 mode "required" 및 required_function_name와 함께 tool_choice를 전달하세요. 프레임워크는 첫 번째 반복 후 자동으로 다시 설정 tool_choiceNone 되므로 후속 반복에서 모델이 무료로 제공됩니다.
result = await agent.run(
"Update record REC-42 to status 'in-progress'.",
tool_choice={"mode": "required", "required_function_name": "get_record"},
)
tool_choice 필드는 ToolMode dict 또는 축약 문자열 "auto", "required", "none"를 허용합니다:
from agent_framework import ToolMode
tool_choice: ToolMode = {"mode": "required", "required_function_name": "get_record"}
의미 체계 및 주의 사항
| 작동 방식 | 세부 정보 |
|---|---|
| 다음 반복 효과 |
add_tools
/
remove_tools 변경 내용은 다음 루프 반복에서 모델에 표시됩니다. 현재 일괄 처리에서 이미 디스패치된 도구 호출은 관계없이 완료됩니다. |
| 기내 일괄 처리 | 모델이 하나의 일괄 처리로 여러 도구를 요청하는 경우 업데이트된 도구 목록이 다시 전송되기 전에 모두 실행됩니다. |
| 중복 이름 | 완전히 동일한 객체를 다시 추가해도 아무 동작도 하지 않습니다. 이름이 기존 도구와 일치하는 다른 개체를 추가하면 ValueError 오류가 발생합니다. 전체 일괄 처리는 추가하기 전에 유효성을 검사하므로 목록 중간에 중복되면 라이브 목록이 변경되지 않습니다. |
| 외부 루프 오류 |
ctx.tools is None일 때 add_tools 또는 remove_tools을 호출하면 RuntimeError가 발생합니다. 이 문제는 함수가 에이전트 루프를 통하지 않고 직접(예: 통해 FunctionTool.invoke) 호출될 때 발생합니다. |
| 실험적 상태 | 두 도우미는 각 프로세스에서 처음 호출될 때 ExperimentalWarning를 출력한다. 원하는 경우 warnings.filterwarnings("ignore", category=UserWarning)로 숨길 수 있습니다. |
| 실행별 범위 | 라이브 도구 목록은 각 agent.run() 호출이 시작될 때 normalize_tools에서 생성된 새 복사본입니다. 호출자의 원래 tools 컨테이너는 변경되지 않습니다. |
| CodeAct 제외 |
agent-framework-monty 또는 agent-framework-hyperlight CodeAct 공급자에는 사용할 수 없습니다. |