Skip to content

Commit 1efc7ad

Browse files
authored
Python: Pass through arguments when creating agents from specs (#12771)
### Motivation and Context Currently, attempting to pass through KernelArguments through agent creation via declarative spec isn't working properly. The arguments are used to normalize fields, but are then not passed through fully to where the agent is created. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description Pass arguments through, and merge them properly with the normalized fields. This allows for underlying args to be used by the agent, like in the case of structured outputs. - Closes #12770 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent b4b3a74 commit 1efc7ad

File tree

5 files changed

+67
-7
lines changed

5 files changed

+67
-7
lines changed

python/samples/getting_started_with_agents/chat_completion/step11_chat_completion_agent_declarative.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,35 @@
33
import asyncio
44
from typing import Annotated
55

6+
from pydantic import BaseModel
7+
68
from semantic_kernel import Kernel
79
from semantic_kernel.agents import AgentRegistry, ChatHistoryAgentThread
810
from semantic_kernel.agents.chat_completion.chat_completion_agent import ChatCompletionAgent
9-
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
10-
from semantic_kernel.functions import kernel_function
11+
from semantic_kernel.connectors.ai.open_ai import (
12+
AzureChatCompletion,
13+
AzureChatPromptExecutionSettings,
14+
)
15+
from semantic_kernel.functions import KernelArguments, kernel_function
1116

1217
"""
1318
The following sample demonstrates how to create a chat completion agent using a
1419
declarative approach. The Chat Completion Agent is created from a YAML spec,
1520
with a specific service and plugins. The agent is then used to answer user questions.
21+
22+
This sample also demonstrates how to properly pass execution settings (like response format)
23+
when using AgentRegistry.create_from_yaml().
1624
"""
1725

1826

27+
# Example structure for structured output
28+
class StructuredResult(BaseModel):
29+
"""Example structure for demonstrating response format."""
30+
31+
response: str
32+
category: str
33+
34+
1935
# 1. Define a Sample Plugin
2036
class MenuPlugin:
2137
"""A sample Menu Plugin used for the concept sample."""
@@ -66,24 +82,44 @@ async def main():
6682
kernel = Kernel()
6783
kernel.add_plugin(MenuPlugin(), plugin_name="MenuPlugin")
6884

69-
# 5. Create the agent from YAML + inject the AI service
85+
# 5. Create execution settings with structured output
86+
execution_settings = AzureChatPromptExecutionSettings()
87+
execution_settings.response_format = StructuredResult
88+
89+
# 6. Create KernelArguments with the execution settings
90+
arguments = KernelArguments(settings=execution_settings)
91+
92+
# 7. Create the agent from YAML + inject the AI service
7093
agent: ChatCompletionAgent = await AgentRegistry.create_from_yaml(
71-
AGENT_YAML, kernel=kernel, service=OpenAIChatCompletion()
94+
AGENT_YAML, kernel=kernel, service=AzureChatCompletion(), arguments=arguments
7295
)
7396

74-
# 6. Create a thread to hold the conversation
97+
# 8. Create a thread to hold the conversation
7598
thread: ChatHistoryAgentThread | None = None
7699

77100
for user_input in USER_INPUTS:
78101
print(f"# User: {user_input}")
79-
# 7. Invoke the agent for a response
102+
# 9. Invoke the agent for a response
80103
response = await agent.get_response(messages=user_input, thread=thread)
81104
print(f"# {response.name}: {response}")
82105
thread = response.thread
83106

84-
# 8. Cleanup the thread
107+
# 10. Cleanup the thread
85108
await thread.delete() if thread else None
86109

110+
"""
111+
# Sample output:
112+
113+
# User: Hello
114+
# Assistant: {"response":"Hello! How can I help you today? If you have any questions about the menu, feel free to ask!","category":"Greeting"}
115+
# User: What is the special soup?
116+
# Assistant: {"response":"Today's special soup is Clam Chowder. Would you like to know more about it or see other specials?","category":"Menu Specials"}
117+
# User: What does that cost?
118+
# Assistant: {"response":"The Clam Chowder special soup costs $9.99.","category":"Menu Pricing"}
119+
# User: Thank you
120+
# Assistant: {"response":"You're welcome! If you have any more questions or need assistance with the menu, just let me know. Enjoy your meal!","category":"Polite Closing"}
121+
""" # noqa: E501
122+
87123

88124
if __name__ == "__main__":
89125
asyncio.run(main())

python/semantic_kernel/agents/azure_ai/azure_ai_agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,11 @@ async def _from_dict(
501501
if args:
502502
arguments = KernelArguments(**args)
503503

504+
# Handle arguments from kwargs, merging with any arguments from data
505+
if "arguments" in kwargs and kwargs["arguments"] is not None:
506+
incoming_args = kwargs["arguments"]
507+
arguments = arguments | incoming_args if arguments is not None else incoming_args
508+
504509
if spec.id:
505510
existing_definition = await client.agents.get_agent(spec.id)
506511

python/semantic_kernel/agents/chat_completion/chat_completion_agent.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,15 @@ async def _from_dict(
255255
if "function_choice_behavior" in kwargs:
256256
fields["function_choice_behavior"] = kwargs["function_choice_behavior"]
257257

258+
# Handle arguments from kwargs, merging with any arguments from _normalize_spec_fields
259+
if "arguments" in kwargs and kwargs["arguments"] is not None:
260+
incoming_args = kwargs["arguments"]
261+
if fields.get("arguments") is not None:
262+
# Use KernelArguments' built-in merge operator, with incoming_args taking precedence
263+
fields["arguments"] = fields["arguments"] | incoming_args
264+
else:
265+
fields["arguments"] = incoming_args
266+
258267
return cls(**fields, kernel=kernel)
259268

260269
# endregion

python/semantic_kernel/agents/open_ai/openai_assistant_agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ async def _from_dict(
466466
if args:
467467
arguments = KernelArguments(**args)
468468

469+
# Handle arguments from kwargs, merging with any arguments from data
470+
if "arguments" in kwargs and kwargs["arguments"] is not None:
471+
incoming_args = kwargs["arguments"]
472+
arguments = arguments | incoming_args if arguments is not None else incoming_args
473+
469474
if spec.id:
470475
existing_definition = await client.beta.assistants.retrieve(spec.id)
471476

python/semantic_kernel/agents/open_ai/openai_responses_agent.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,11 @@ async def _from_dict(
537537
if args:
538538
arguments = KernelArguments(**args)
539539

540+
# Handle arguments from kwargs, merging with any arguments from data
541+
if "arguments" in kwargs and kwargs["arguments"] is not None:
542+
incoming_args = kwargs["arguments"]
543+
arguments = arguments | incoming_args if arguments is not None else incoming_args
544+
540545
if not (spec.model and spec.model.id):
541546
raise AgentInitializationException("model.id required when creating a new OpenAI Responses Agent.")
542547

0 commit comments

Comments
 (0)