mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
[BREAKING] Python: Add InvokeFunctionTool action for declarative workflows (#3716)
* add(declarative): Declarative workflow InvokeFunctionTool feature * Cleanup * Address PR feedback * Remove InvokeTool kind, consolidate to InvokeFunctionTool * Fix sample locations * pin azure-ai-projects to 2.0.0b3 due to breaking changes
This commit is contained in:
committed by
GitHub
Unverified
parent
f77f40b987
commit
40d2fac29c
@@ -0,0 +1,116 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""Invoke Function Tool sample - demonstrates InvokeFunctionTool workflow actions.
|
||||
|
||||
This sample shows how to:
|
||||
1. Define Python functions that can be called from workflows
|
||||
2. Register functions with WorkflowFactory.register_tool()
|
||||
3. Use the InvokeFunctionTool action in YAML to invoke registered functions
|
||||
4. Pass arguments using expression syntax (=Local.variable)
|
||||
5. Capture function output in workflow variables
|
||||
|
||||
Run with:
|
||||
python -m samples.03-workflows.declarative.invoke_function_tool.main
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.declarative import WorkflowFactory
|
||||
|
||||
|
||||
# Define the function tools that will be registered with the workflow
|
||||
def get_weather(location: str, unit: str = "F") -> dict[str, Any]:
|
||||
"""Get weather information for a location.
|
||||
|
||||
This is a mock function that returns simulated weather data.
|
||||
In a real application, this would call a weather API.
|
||||
|
||||
Args:
|
||||
location: The city or location to get weather for.
|
||||
unit: Temperature unit ("F" for Fahrenheit, "C" for Celsius).
|
||||
|
||||
Returns:
|
||||
Dictionary with weather information.
|
||||
"""
|
||||
# Simulated weather data
|
||||
weather_data = {
|
||||
"Seattle": {"temp": 55, "condition": "rainy"},
|
||||
"New York": {"temp": 70, "condition": "partly cloudy"},
|
||||
"Los Angeles": {"temp": 85, "condition": "sunny"},
|
||||
"Chicago": {"temp": 60, "condition": "windy"},
|
||||
}
|
||||
|
||||
data = weather_data.get(location, {"temp": 72, "condition": "unknown"})
|
||||
|
||||
# Convert to Celsius if requested
|
||||
temp = data["temp"]
|
||||
if unit.upper() == "C":
|
||||
temp = round((temp - 32) * 5 / 9) # type: ignore
|
||||
|
||||
return {
|
||||
"location": location,
|
||||
"temp": temp,
|
||||
"unit": unit.upper(),
|
||||
"condition": data["condition"],
|
||||
}
|
||||
|
||||
|
||||
def format_message(template: str, data: dict[str, Any]) -> str:
|
||||
"""Format a message template with data.
|
||||
|
||||
Args:
|
||||
template: A string template with {key} placeholders.
|
||||
data: Dictionary of values to substitute.
|
||||
|
||||
Returns:
|
||||
Formatted message string.
|
||||
"""
|
||||
try:
|
||||
return template.format(**data)
|
||||
except KeyError as e:
|
||||
return f"Error formatting message: missing key {e}"
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run the invoke function tool workflow."""
|
||||
# Get the path to the workflow YAML file
|
||||
workflow_path = Path(__file__).parent / "workflow.yaml"
|
||||
|
||||
# Create the workflow factory and register our tool functions
|
||||
factory = (
|
||||
WorkflowFactory().register_tool("get_weather", get_weather).register_tool("format_message", format_message)
|
||||
)
|
||||
|
||||
# Create the workflow from the YAML definition
|
||||
workflow = factory.create_workflow_from_yaml_path(workflow_path)
|
||||
|
||||
print("=" * 60)
|
||||
print("Invoke Function Tool Workflow Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Test with different inputs - both location and unit must be provided
|
||||
# as the workflow expects them in Workflow.Inputs
|
||||
test_inputs = [
|
||||
{"location": "Seattle", "unit": "F"},
|
||||
{"location": "New York", "unit": "C"},
|
||||
{"location": "Los Angeles", "unit": "F"},
|
||||
{"location": "Chicago", "unit": "C"},
|
||||
]
|
||||
|
||||
for inputs in test_inputs:
|
||||
print(f"\nInput: {inputs}")
|
||||
print("-" * 40)
|
||||
|
||||
# Run the workflow
|
||||
events = await workflow.run(inputs)
|
||||
|
||||
# Get the outputs
|
||||
outputs = events.get_outputs()
|
||||
for output in outputs:
|
||||
print(f"Output: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,51 @@
|
||||
# Invoke Function Tool Workflow
|
||||
|
||||
name: invoke_function_tool_demo
|
||||
description: Demonstrates the InvokeFunctionTool action for invoking registered functions
|
||||
|
||||
actions:
|
||||
# Set up input location
|
||||
- kind: SetValue
|
||||
id: set_location
|
||||
path: Local.location
|
||||
value: =If(IsBlank(inputs.location), "Seattle", inputs.location)
|
||||
|
||||
# Set up temperature unit
|
||||
- kind: SetValue
|
||||
id: set_unit
|
||||
path: Local.unit
|
||||
value: =If(IsBlank(inputs.unit), "F", inputs.unit)
|
||||
|
||||
# Invoke the get_weather function tool
|
||||
- kind: InvokeFunctionTool
|
||||
id: invoke_weather
|
||||
functionName: get_weather
|
||||
arguments:
|
||||
location: =Local.location
|
||||
unit: =Local.unit
|
||||
output:
|
||||
messages: Local.weatherToolCallItems
|
||||
result: Local.weatherInfo
|
||||
autoSend: true
|
||||
|
||||
# Format a human-readable message using another function
|
||||
- kind: InvokeFunctionTool
|
||||
id: format_output
|
||||
functionName: format_message
|
||||
arguments:
|
||||
template: "The weather in {location} is {temp}°{unit}"
|
||||
data: =Local.weatherInfo
|
||||
output:
|
||||
result: Local.formattedMessage
|
||||
|
||||
# Output the result
|
||||
- kind: SendActivity
|
||||
id: send_weather
|
||||
activity:
|
||||
text: =Local.formattedMessage
|
||||
|
||||
# Store the result in workflow outputs
|
||||
- kind: SetValue
|
||||
id: set_output
|
||||
path: Workflow.Outputs.weather
|
||||
value: =Local.weatherInfo
|
||||
Reference in New Issue
Block a user