基于上一节基础跑通实现FunctionCalling,上一节:https://blog.csdn.net/weixin_42800530/article/details/161492135?spm=1011.2124.3001.6209
1. 代码实现 FunctionCalling功能
csharp
using ConsoleApp1.Common;
using ConsoleApp1.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace ConsoleApp1.BLL
{
public class CommonClass
{
CommonHelper commHelper = new CommonHelper();
HttpClient client ;
string apiKey =""; //此处写你申请的API Key
string url = "";
public CommonClass()
{
apiKey = ConfigCommon.apiKey; //此处写你申请的API Key
url = ConfigCommon.url_chat;
client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
}
//1. 添加:保存对话历史
private List<ChatMessage> _history=new List<ChatMessage>();
public async Task<string> CallAPIWithTools(string userMessage, object[] tools)
{
string sOutValue = "";
_history.Add(new ChatMessage("user", userMessage));
//请求体 第1次请求:带tools参数
var requestBody = new
{
model = "qwen-turbo",
messages = _history, //MessagesIn
tools = tools,
tool_choice = "auto", //让AI自动决定是否调用工具
stream = false
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content); //1. 第一次调用AI接口
var responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new Exception($"API调用失败: {response.StatusCode}");
}
//2. 解析AI出参,判断是否需要调用方法(第一次出参)
FunctionCallResponse resp1 = ParseFunctionCallResponse(responseString);
if (resp1.FinishReason == "tool_calls")
{
//3. 把AI的tool_calls响应加入历史
_history.Add(resp1.Message);
//4. 执行方法
var toolCall = resp1.ToolCalls[0];
string toolResult = commHelper.ExecuteTool(toolCall.Function.Name, toolCall.Function.Arguments);
//5. 把工具执行结果加入历史(tool角色)
_history.Add(new ChatMessage("tool", toolResult)
{
ToolCallId = toolCall.Id
});
//6. 第2次请求,不带tools,让AI根据结果生成回答
var resp2 = await CallAPIWithoutTools(_history);
_history.Add(resp2.Message);
sOutValue = resp2.Message.Content;
}
else
{
sOutValue = resp1.Message.Content;
}
return sOutValue;
}
//解析Function Calling响应
public FunctionCallResponse ParseFunctionCallResponse(string jsonString)
{
FunctionCallResponse result = new FunctionCallResponse();
var doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement;
//解析Finish_Reason
result.FinishReason = root.GetProperty("choices")[0].GetProperty("finish_reason").GetString();
//解析message
var messageElement = root.GetProperty("choices")[0].GetProperty("message");
result.Message = new ChatMessage(
messageElement.GetProperty("role").GetString(),
messageElement.GetProperty("content").GetString() ?? ""
);
//解析tool_calls
if(messageElement.TryGetProperty("tool_calls",out var toolCallsElement))
{
result.ToolCalls = new List<ToolCall>();
foreach(var tc in toolCallsElement.EnumerateArray())
{
var toolCall = new ToolCall
{
Id = tc.GetProperty("id").GetString(),
Type = tc.GetProperty("type").GetString(),
Function = new FunctionCall
{
Name = tc.GetProperty("function").GetProperty("name").GetString(),
Arguments = tc.GetProperty("function").GetProperty("arguments").GetString()
}
};
result.ToolCalls.Add(toolCall);
}
}
return result;
}
/// <summary>
/// 不带tools的API调用(用于第2次请求)
/// </summary>
private async Task<ChatResponse> CallAPIWithoutTools(List<ChatMessage> messages)
{
var requestBody = new
{
model = "qwen-turbo",
messages = messages,
stream = false
};
var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new Exception($"API调用失败: {response.StatusCode}");
}
return ParseChatResponse(responseString);
}
/// <summary>
/// 解析普通Chat响应
/// </summary>
private ChatResponse ParseChatResponse(string jsonString)
{
var doc = JsonDocument.Parse(jsonString);
var root = doc.RootElement;
var firstChoice = root.GetProperty("choices")[0];
var result = new ChatResponse();
var messageElement = firstChoice.GetProperty("message");
result.Message = new ChatMessage(
messageElement.GetProperty("role").GetString(),
messageElement.GetProperty("content").GetString() ?? ""
);
result.FinishReason = firstChoice.GetProperty("finish_reason").GetString();
return result;
}
}
/// <summary>
/// 2. 设置系统提示词(可选,放在历史第一位)
/// </summary>
public void SetSystemPrompt(string systemPrompt)
{
// 如果已经有system消息,先移除
if (_history.Count > 0 && _history[0].Role == "system")
{
_history.RemoveAt(0);
}
// 插入到最前面
_history.Insert(0, new ChatMessage("system", systemPrompt));
}
/// <summary>
/// 工具定义:获取当前时间
/// </summary>
private object GetCurrentTimeTool()
{
return new
{
type = "function",
function = new
{
name = "get_current_time",
description = "获取当前的日期和时间。用户询问当前时间、现在几点时必须调用此函数。",
parameters = new { }
}
};
}
// 在 CommonHelper 类中
public string ExecuteTool(string toolName, string arguments)
{
switch (toolName)
{
case "get_current_time":
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 未来添加更多工具
default:
return $"未知工具: {toolName}";
}
}
2. 辅助类
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ConsoleApp1.Model
{
//消息体
public class ChatMessage
{
public ChatMessage() { }
public ChatMessage(string role, string content)
{
Role = role;
Content = content;
}
/// <summary>
/// 消息角色:system / user / assistant
/// </summary>
[JsonPropertyName("role")]
public string Role { get; set; } = string.Empty;
/// <summary>
/// 消息内容
/// </summary>
[JsonPropertyName("content")]
public string Content { get; set; } = string.Empty;
[JsonPropertyName("tool_call_id")]
public string ToolCallId { get; set; } //用于tool角色消息
[JsonPropertyName("tool_calls")]
public List<ToolCall> ToolCalls { get; set; } // 🆕 用于assistant消息
public override string ToString()
{
return $"{Role}: {Content}";
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json.Serialization;
namespace ConsoleApp1.Model
{
public class ChatResponse
{
[JsonPropertyName("choices")]
public List<Choice> Choices { get; set; } = new List<Choice>();
[JsonPropertyName("finish_reason")]
public string FinishReason { get; set; } = "";
[JsonPropertyName("Message")]
public ChatMessage Message { get; set; } = new ChatMessage();
}
public class Choice
{
[JsonPropertyName("message")]
public ChatMessage Message { get; set; } = new ChatMessage();
[JsonPropertyName("finish_reason")]
public string FinishReason { get; set; } = "";
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1.Model
{
public class FunctionCall
{
public string Name { get; set; } = "";
public string Arguments { get; set; } = "";
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1.Model
{
public class FunctionCallResponse
{
public string FinishReason { get; set; } = "";
public ChatMessage Message { get; set; } = new ChatMessage();
public List<ToolCall> ToolCalls { get; set; } = new List<ToolCall>();
}
}
3.调用
csharp
Console.WriteLine($"FunctionCalling Study\r\n");
CommonClass client = new CommonClass();
client.SetSystemPrompt("你是一个智能助手。当用户询问时间、日期时,必须调用 get_current_time 函数获取真实时间,不要自己编造。");
var tools = new object[] { GetCurrentTimeTool() };
// 测试1:查询时间
Console.WriteLine("用户:现在几点了? 当前时间");
var reply1 = await client.CallAPIWithTools("现在几点了?", tools);
Console.WriteLine($"AI回答(1):{reply1}\n");
4. 运行效果
用户:现在几点了?
AI回答:现在是下午2点30分25秒
5. 常见问题
Q1:第1次请求没有返回tool_calls,而是直接回答了?
原因:AI认为不需要调用工具(比如用户没问时间)
解决:正常情况,直接返回AI的回答即可。
Q2:返回了tool_calls但函数名不对?
原因:工具定义中的name和description不够清晰
解决:确保description让AI能准确理解什么时候该调用。
Q3:第2次请求后AI还是返回工具调用?
原因:工具结果不够详细,AI还需要更多信息
解决:检查工具返回的内容是否清晰。
Q4:第一次调用AI直接输出了错误的时间
原因:说明AI认为自己可以直接回答时间问题,不需要调用工具。AI的训练数据中包含了大量"当前时间"的示例,但它回答的是训练数据中的时间,不是真实时间。
解决方法:明确设置提示词:"你是一个智能助手。当用户询问时间、日期时,必须调用 get_current_time 函数获取真实时间,不要自己编造。"