iTranslated by AI
Trying out Microsoft Agent Framework with Local LLMs: Part 5 (AIFunction)
Series Overview
List
Trying Microsoft Agent Framework with Local LLM Part 1
Trying Microsoft Agent Framework with Local LLM Part 2
Trying Microsoft Agent Framework with Local LLM Part 3 (LoggingFactory)
Trying Microsoft Agent Framework with Local LLM Part 4 (Tool)
Trying Microsoft Agent Framework with Local LLM Part 5 (AIFunction)
Trying Microsoft Agent Framework with Local LLM Part 6 (ChatHistoryProvider)
Trying Microsoft Agent Framework with Local LLM Part 7 (ChatHistoryProvider)
Trying Microsoft Agent Framework with Local LLM Part 8 (ChatHistoryProvider)
Trying Microsoft Agent Framework with Local LLM Part 9 (AIContextProvider)
Trying Microsoft Agent Framework with Local LLM Part 10 (AIContextProvider)
Trying Microsoft Agent Framework with Local LLM Part 11 (Simple RAG with AIContextProvider)
Trying Microsoft Agent Framework with Local LLM Part 12 (Structured Output)
Trying Microsoft Agent Framework with Local LLM Part 13 (Middleware)
Introduction
In Part 4 of trying Microsoft Agent Framework with a local LLM, I implemented a Human-in-the-loop configuration. In Part 5, I will try creating a custom AIFunction.
Creating a Custom AIFunction
public class GetSampleDataFunction : AIFunction
{
private string jsonString = """
{
"type": "object",
"properties": {
"inputData": {
"type": "string",
"enum": ["small", "medium", "large"]
}
},
"required": ["inputData"]
}
""";
private string returnJsonString = """
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["items"]
}
""";
public override string Name => "GetSampleData";
public override string Description => "Retrieves sample data.";
public override JsonElement JsonSchema => JsonDocument.Parse(jsonString).RootElement;
public override JsonElement? ReturnJsonSchema => JsonDocument.Parse(returnJsonString).RootElement;
protected override ValueTask<object?> InvokeCoreAsync(AIFunctionArguments arguments, CancellationToken cancellationToken)
{
string? inputData = null;
if (arguments.TryGetValue("inputData", out var value))
{
if (value is JsonElement jsonElement)
{
inputData = jsonElement.GetString();
}
else if (value is string str)
{
inputData = str;
}
}
if (string.IsNullOrEmpty(inputData))
{
return new ValueTask<object?>("error");
}
var items = inputData switch
{
"small" => new[] { "small_item_1", "small_item_2", "small_item_3" },
"medium" => new[] { "medium_item_1", "medium_item_2", "medium_item_3", "medium_item_4" },
"large" => new[] { "large_item_1", "large_item_2", "large_item_3", "large_item_4", "large_item_5" },
_ => new[] { "default_item_1", "default_item_2" }
};
var result = new { items = items };
var json = JsonSerializer.Serialize(result);
return new ValueTask<object?>(json);
}
}
- I define the input and output schemas using
JsonSchemaandReturnJsonSchema.- The
JsonSchemaandReturnJsonSchemainMicrosoft.Extensions.AIrepresent JSON Schema. - They are used to define the input and output formats based on OpenAPI specifications.
- The
- In the
InvokeCoreAsyncmethod, I return different sample data depending on the value ofinputData. - I use
TryGetValueto retrieve arguments, handling cases where values might be missing or have different types. - When the agent calls this function as a tool, it is expected to pass one of "small", "medium", or "large" to
inputData. - The return value is an object containing
items, which is serialized into JSON and passed to the model as the result of theAIFunction.
Registering as a Tool in AIAgent
AIAgent agent = chatClient.AsAIAgent(
tools: [
new GetSampleDataFunction()
]);
AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync("Show me some sample data", session);
Console.WriteLine(response);
- Just like with standard AIFunctions, you create an AIAgent by registering the custom
GetSampleDataFunctionin thetoolslist. - By passing the session created with
CreateSessionAsynctoRunAsync, you can maintain the conversation history across subsequent turns.
Request to LM Studio
1st Turn
{
"messages": [
{
"role": "user",
"content": "Show me some sample data"
}
],
"model": "openai/gpt-oss-20b",
"tools": [
{
"type": "function",
"function": {
"description": "Retrieves sample data.",
"name": "GetSampleData",
"parameters": {
"type": "object",
"required": [
"inputData"
],
"properties": {
"inputData": {
"type": "string",
"enum": [
"small",
"medium",
"large"
]
}
},
"additionalProperties": false
}
}
}
],
"tool_choice": "auto"
}
- You can see that the custom AIFunction is correctly recognized as a tool.
- The
Name,Description, andJsonSchemaare correctly reflected. - The schema defined in
JsonSchemais included inparameters.- The
enumchoices defined are also included in theparameters.
- The
- The
2nd Turn
{
"messages": [
{
"role": "user",
"content": "Show me some sample data"
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "980090291",
"type": "function",
"function": {
"name": "GetSampleData",
"arguments": "{\r\n \"inputData\": \"medium\"\r\n}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "980090291",
"content": "{\"items\":[\"medium_item_1\",\"medium_item_2\",\"medium_item_3\",\"medium_item_4\"]}"
}
],
"model": "openai/gpt-oss-20b",
"tools": [
{
"type": "function",
"function": {
"description": "Retrieves sample data.",
"name": "GetSampleData",
"parameters": {
"type": "object",
"required": [
"inputData"
],
"properties": {
"inputData": {
"type": "string",
"enum": [
"small",
"medium",
"large"
]
}
},
"additionalProperties": false
}
}
}
],
"tool_choice": "auto"
}
- You can see that the tool call request and response are performed correctly.
- The assistant's
tool_callsincludes the call to theGetSampleDatafunction. - The tool's
contentcontains the return value of theGetSampleDatafunction.
- The assistant's
- The tool's input parameters are passed correctly.
- You can see that "medium" is passed as the value for
inputDatainarguments.
- You can see that "medium" is passed as the value for
- While I return an object in the code, you can see that the tool result passed to LM Studio is represented as JSON text.
Output
Here is the retrieved sample data for the "medium" size.
| Index | Item Name |
|--------------|----------------------|
| 1 | medium_item_1 |
| 2 | medium_item_2 |
| 3 | medium_item_3 |
| 4 | medium_item_4 |
This is the sample data returned from the `GetSampleData` function. Please let me know if you would like to see other sizes (small/large) or have it displayed in a different format!
- The tool execution result is correctly reflected in the agent's response.
- Based on the JSON representation passed as the tool result, the contents of
itemsare displayed as a Markdown table.
- Based on the JSON representation passed as the tool result, the contents of
- It is clear that the agent understands the choices defined in the
enum.
Summary
- I implemented a custom tool by inheriting from
AIFunction. - I defined the tool's input format using
JsonSchema. - I registered it as a tool in
AIAgentand confirmed that tool calling works by inspecting the requests to LM Studio. - Next time, I would like to delve into the LLM input parameters or the chat history mechanism.
Discussion