Python: Added custom args and thread object to ai_function kwargs (#2769)

* Added an example of using kwargs in ai_function

* Added thread object to ai_function kwargs

* Updated docs

* Small fix

* Added thread parameter filtering
This commit is contained in:
Dmytro Struk
2025-12-11 17:53:04 -08:00
committed by GitHub
Unverified
parent eb1117fff4
commit d7434d59ce
7 changed files with 216 additions and 6 deletions
@@ -21,9 +21,11 @@ from agent_framework import (
ChatResponse,
Context,
ContextProvider,
FunctionCallContent,
HostedCodeInterpreterTool,
Role,
TextContent,
ai_function,
)
from agent_framework._mcp import MCPTool
from agent_framework.exceptions import AgentExecutionException
@@ -595,3 +597,38 @@ async def test_chat_agent_with_local_mcp_tools(chat_client: ChatClientProtocol)
# Test async context manager with MCP tools
async with agent:
pass
async def test_agent_tool_receives_thread_in_kwargs(chat_client_base: Any) -> None:
"""Verify tool execution receives 'thread' inside **kwargs when function is called by client."""
captured: dict[str, Any] = {}
@ai_function(name="echo_thread_info")
def echo_thread_info(text: str, **kwargs: Any) -> str: # type: ignore[reportUnknownParameterType]
thread = kwargs.get("thread")
captured["has_thread"] = thread is not None
captured["has_message_store"] = thread.message_store is not None if isinstance(thread, AgentThread) else False
return f"echo: {text}"
# Make the base client emit a function call for our tool
chat_client_base.run_responses = [
ChatResponse(
messages=ChatMessage(
role="assistant",
contents=[FunctionCallContent(call_id="1", name="echo_thread_info", arguments='{"text": "hello"}')],
)
),
ChatResponse(messages=ChatMessage(role="assistant", text="done")),
]
agent = ChatAgent(
chat_client=chat_client_base, tools=[echo_thread_info], chat_message_store_factory=ChatMessageStore
)
thread = agent.get_new_thread()
result = await agent.run("hello", thread=thread)
assert result.text == "done"
assert captured.get("has_thread") is True
assert captured.get("has_message_store") is True
@@ -1334,3 +1334,37 @@ async def test_streaming_two_functions_mixed_approval():
assert updates[2].role == Role.ASSISTANT
assert len(updates[2].contents) == 2
assert all(isinstance(c, FunctionApprovalRequestContent) for c in updates[2].contents)
async def test_ai_function_with_kwargs_injection():
"""Test that ai_function correctly handles kwargs injection and hides them from schema."""
@ai_function
def tool_with_kwargs(x: int, **kwargs: Any) -> str:
"""A tool that accepts kwargs."""
user_id = kwargs.get("user_id", "unknown")
return f"x={x}, user={user_id}"
# Verify schema does not include kwargs
assert tool_with_kwargs.parameters() == {
"properties": {"x": {"title": "X", "type": "integer"}},
"required": ["x"],
"title": "tool_with_kwargs_input",
"type": "object",
}
# Verify direct invocation works
assert tool_with_kwargs(1, user_id="user1") == "x=1, user=user1"
# Verify invoke works with injected args
result = await tool_with_kwargs.invoke(
arguments=tool_with_kwargs.input_model(x=5),
user_id="user2",
)
assert result == "x=5, user=user2"
# Verify invoke works without injected args (uses default)
result_default = await tool_with_kwargs.invoke(
arguments=tool_with_kwargs.input_model(x=10),
)
assert result_default == "x=10, user=unknown"