MCP + DeepSeek 对话框:让 AI 自动操控你的 WinForms 应用

MCP + DeepSeek 对话框:让 AI 自动操控你的 WinForms 应用

前言

前面我们实现了 MCP 客户端和服务端,但都是手动操作工具。如果能让 AI 自己决定何时调用什么工具,那才是真正的智能。

本文将 MCP 客户端 + DeepSeek 大模型 集成到一个对话框中,实现:

  1. 连接 MCP Server,自动获取工具列表
  2. 将 MCP 工具注册为 DeepSeek 的 function calling 工具
  3. 用户输入自然语言,AI 自动调用 MCP 工具完成操作
  4. 支持多轮对话和连续工具调用

架构设计

┌──────────────────────────────────────────────────────────────┐
│                    WinForms 对话框 UI                         │
│  ┌──────────────────────────────────────────────────────┐   │
│  │  [用户] 请帮我把文本框改成 Hello                        │   │
│  │  [AI]   好的,我来帮你设置。(调用 SetTextBoxContent)   │   │
│  │  [AI]   已经设置好了。                                  │   │
│  └──────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────┐  ┌──────────┐              │
│  │ 输入消息...                  │  │ 发送     │              │
│  └─────────────────────────────┘  └──────────┘              │
└──────────────────────────────────────────────────────────────┘
         │                                    │
         ▼                                    ▼
┌─────────────────┐              ┌─────────────────────┐
│  DeepSeek API   │              │  MCP Server         │
│  (OpenAI 兼容)  │──tool_call──▶│  (tools/list,       │
│  deepseek-v4    │◀─result─────│   tools/call)       │
└─────────────────┘              └─────────────────────┘

核心流程

1. 连接 MCP Server → 获取工具列表
2. 将 MCP 工具转换为 OpenAI function calling 格式
3. 用户输入消息 → 发送给 DeepSeek(附带工具定义)
4. DeepSeek 返回 tool_calls → 执行 MCP 工具调用
5. 将工具结果反馈给 DeepSeek → 获取最终回复
6. 循环 3-5 直到 AI 不再调用工具

环境准备

工具 版本要求 用途
.NET SDK 8.0+ 编译运行项目
DeepSeek API Key - 从 platform.deepseek.com 获取
MCP Server - 已有 MCP 服务器(如 McpTestDemo1)

文件结构

Deepseek/
├── DeepSeekClient.cs        # DeepSeek API 客户端
├── ToolConverter.cs         # MCP 工具 → OpenAI 格式转换
├── ChatOrchestrator.cs      # 对话编排器(LLM + MCP 工具循环)
├── ChatForm.cs              # 对话框逻辑
└── ChatForm.Designer.cs     # 对话框布局

第一步:DeepSeek API 客户端 DeepSeekClient.cs

DeepSeek 兼容 OpenAI API 格式,通过 HTTP 调用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace McpClientDemo.Deepseek
{
    public class DeepSeekClient
    {
        private readonly HttpClient _http;
        private readonly string _model;

        public DeepSeekClient(string apiKey, string baseUrl, string model)
        {
            _model = model;
            _http = new HttpClient { BaseAddress = new Uri(baseUrl) };
            _http.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
        }

        public async Task<ChatResponse> SendAsync(
            List<ChatMessage> messages,
            List<ToolDefinition>? tools = null)
        {
            // 用 Dictionary 构建请求,精确控制序列化
            var body = new Dictionary<string, object>
            {
                ["model"] = _model,
                ["messages"] = messages.Select(m => SerializeMessage(m)).ToList(),
                ["stream"] = false
            };

            // tools 非空时才添加
            if (tools != null && tools.Count > 0)
            {
                body["tools"] = tools.Select(t => new Dictionary<string, object>
                {
                    ["type"] = "function",
                    ["function"] = new Dictionary<string, object>
                    {
                        ["name"] = t.Function.Name,
                        ["description"] = t.Function.Description ?? "",
                        ["parameters"] = t.Function.Parameters != null
                            ? JsonSerializer.Deserialize<Dictionary<string, object>>(
                                t.Function.Parameters.Value.GetRawText())
                            : new Dictionary<string, object>
                            {
                                ["type"] = "object",
                                ["properties"] = new Dictionary<string, object>()
                            }
                    }
                }).ToList();
            }

            var options = new JsonSerializerOptions
            {
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
            };

            var response = await _http.PostAsJsonAsync("/chat/completions", body, options);

            if (!response.IsSuccessStatusCode)
            {
                var errorBody = await response.Content.ReadAsStringAsync();
                throw new Exception($"DeepSeek API 错误 ({(int)response.StatusCode}): {errorBody}");
            }

            var json = await response.Content.ReadFromJsonAsync<JsonElement>();
            return ParseResponse(json);
        }

        private Dictionary<string, object> SerializeMessage(ChatMessage m)
        {
            var msg = new Dictionary<string, object>
            {
                ["role"] = m.Role
            };

            if (m.Content != null)
                msg["content"] = m.Content;

            if (m.ToolCalls != null && m.ToolCalls.Count > 0)
            {
                msg["tool_calls"] = m.ToolCalls.Select(tc => new Dictionary<string, object>
                {
                    ["id"] = tc.Id,
                    ["type"] = "function",
                    ["function"] = new Dictionary<string, string>
                    {
                        ["name"] = tc.Name,
                        ["arguments"] = tc.Arguments
                    }
                }).ToList();
            }

            if (m.ToolCallId != null)
                msg["tool_call_id"] = m.ToolCallId;

            return msg;
        }

        private ChatResponse ParseResponse(JsonElement json)
        {
            var choice = json.GetProperty("choices")[0];
            var message = choice.GetProperty("message");

            var result = new ChatResponse
            {
                Role = message.GetProperty("role").GetString() ?? "",
                Content = message.TryGetProperty("content", out var c) && c.ValueKind != JsonValueKind.Null
                    ? c.GetString() : null
            };

            if (message.TryGetProperty("tool_calls", out var toolCalls) && toolCalls.ValueKind == JsonValueKind.Array)
            {
                result.ToolCalls = new List<ToolCall>();
                foreach (var tc in toolCalls.EnumerateArray())
                {
                    var func = tc.GetProperty("function");
                    result.ToolCalls.Add(new ToolCall
                    {
                        Id = tc.GetProperty("id").GetString() ?? "",
                        Name = func.GetProperty("name").GetString() ?? "",
                        Arguments = func.GetProperty("arguments").GetString() ?? "{}"
                    });
                }
            }

            return result;
        }
    }

    public class ChatMessage
    {
        [JsonPropertyName("role")]
        public string Role { get; set; } = "";

        [JsonPropertyName("content")]
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public string? Content { get; set; }

        [JsonPropertyName("tool_calls")]
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public List<ToolCall>? ToolCalls { get; set; }

        [JsonPropertyName("tool_call_id")]
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public string? ToolCallId { get; set; }
    }

    public class ChatResponse
    {
        public string Role { get; set; } = "";
        public string? Content { get; set; }
        public List<ToolCall>? ToolCalls { get; set; }
    }

    public class ToolCall
    {
        public string Id { get; set; } = "";
        public string Name { get; set; } = "";
        public string Arguments { get; set; } = "{}";
    }

    public class ToolDefinition
    {
        [JsonPropertyName("type")]
        public string Type { get; set; } = "function";

        [JsonPropertyName("function")]
        public FunctionDefinition Function { get; set; } = new();
    }

    public class FunctionDefinition
    {
        [JsonPropertyName("name")]
        public string Name { get; set; } = "";

        [JsonPropertyName("description")]
        public string Description { get; set; } = "";

        [JsonPropertyName("parameters")]
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public JsonElement? Parameters { get; set; }
    }
}```

## 第二步:工具格式转换 `ToolConverter.cs`

将 MCP 工具列表转换为 DeepSeek(OpenAI)的 function calling 格式:

```csharp
using ModelContextProtocol.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace McpClientDemo.Deepseek
{
    /// <summary>
    /// 将 MCP 工具转换为 OpenAI function calling 格式
    /// </summary>
    public static class ToolConverter
    {
        /// <summary>
        /// 将 MCP 工具列表转换为 OpenAI tools 格式
        /// </summary>
        public static List<ToolDefinition> ToOpenAITools(IList<McpClientTool> mcpTools)
        {
            return mcpTools.Select(tool => new ToolDefinition
            {
                Type = "function",
                Function = new FunctionDefinition
                {
                    Name = tool.Name,
                    Description = tool.Description ?? "",
                    Parameters = tool.JsonSchema.ValueKind == JsonValueKind.Object
                        ? tool.JsonSchema
                        : null
                }
            }).ToList();
        }
    }
}

第三步:对话编排器 ChatOrchestrator.cs

核心组件——管理 DeepSeek 对话 + MCP 工具调用循环:

using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using McpClientSdk = ModelContextProtocol.Client.McpClient;
namespace McpClientDemo.Deepseek
{
    /// <summary>
    /// 对话编排器:管理 DeepSeek 对话 + MCP 工具调用循环
    /// </summary>
    public class ChatOrchestrator
    {
        private readonly DeepSeekClient _deepSeek;
        private McpClientSdk? _mcpClient;
        private List<ChatMessage> _messages = new();
        private List<ToolDefinition> _tools = new();

        /// <summary>日志事件</summary>
        public event EventHandler<string>? LogMessage;

        /// <summary>工具调用事件(用于 UI 显示)</summary>
        public event EventHandler<string>? ToolCallStarted;

        public ChatOrchestrator(DeepSeekClient deepSeek)
        {
            _deepSeek = deepSeek;
        }

        /// <summary>
        /// 连接 MCP 服务器并加载工具
        /// </summary>
        public async Task ConnectMcpServerAsync(IClientTransport transport)
        {
            _mcpClient = await McpClientSdk.CreateAsync(transport);

            // 获取 MCP 工具列表
            var mcpTools = await _mcpClient.ListToolsAsync();

            // 转换为 OpenAI function calling 格式
            _tools = ToolConverter.ToOpenAITools(mcpTools);

            OnLog($"已连接 MCP 服务器,加载了 {_tools.Count} 个工具");
            foreach (var tool in _tools)
                OnLog($"  - {tool.Function.Name}: {tool.Function.Description}");
        }

        /// <summary>
        /// 发送用户消息并获取 AI 回复(自动处理工具调用循环)
        /// </summary>
        public async Task<string> SendMessageAsync(string userMessage)
        {
            // 添加用户消息
            _messages.Add(new ChatMessage { Role = "user", Content = userMessage });

            // 工具调用循环(最多 10 次,防止死循环)
            for (int i = 0; i < 10; i++)
            {
                // 调用 DeepSeek API
                var response = await _deepSeek.SendAsync(
                    _messages, _tools.Count > 0 ? _tools : null);

                // 如果没有工具调用,直接返回文本回复
                if (response.ToolCalls == null || response.ToolCalls.Count == 0)
                {
                    var reply = response.Content ?? "(无回复)";
                    _messages.Add(new ChatMessage { Role = "assistant", Content = reply });
                    return reply;
                }

                // 有工具调用:执行工具并继续循环
                _messages.Add(new ChatMessage
                {
                    Role = "assistant",
                    ToolCalls = response.ToolCalls
                });

                foreach (var call in response.ToolCalls)
                {
                    OnToolCall($"调用工具: {call.Name}({call.Arguments})");

                    var result = await ExecuteToolCallAsync(call);

                    _messages.Add(new ChatMessage
                    {
                        Role = "tool",
                        ToolCallId = call.Id,
                        Content = result
                    });

                    OnLog($"工具结果: {result}");
                }
            }

            return "(工具调用循环次数超限)";
        }

        /// <summary>
        /// 执行单个工具调用
        /// </summary>
        private async Task<string> ExecuteToolCallAsync(ToolCall call)
        {
            if (_mcpClient == null)
                return "错误:未连接 MCP 服务器";

            try
            {
                // 解析参数
                var argsDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
                    call.Arguments);
                var args = argsDict?.ToDictionary(
                    kvp => kvp.Key,
                    kvp => (object?)kvp.Value);

                // 调用 MCP 工具
                var result = await _mcpClient.CallToolAsync(call.Name, args);

                // 提取文本结果
                var textParts = result.Content
                    .OfType<TextContentBlock>()
                    .Select(c => c.Text);

                return string.Join("\n", textParts);
            }
            catch (Exception ex)
            {
                return $"工具调用失败: {ex.Message}";
            }
        }

        /// <summary>清空对话历史</summary>
        public void ClearHistory() => _messages.Clear();

        /// <summary>设置系统提示词</summary>
        public void SetSystemPrompt(string prompt)
        {
            _messages.RemoveAll(m => m.Role == "system");
            _messages.Insert(0, new ChatMessage { Role = "system", Content = prompt });
        }

        private void OnLog(string msg) => LogMessage?.Invoke(this, msg);
        private void OnToolCall(string msg) => ToolCallStarted?.Invoke(this, msg);
    }
}

第四步:对话框 UI ChatForm.cs

using ModelContextProtocol.Client;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using static System.Net.Mime.MediaTypeNames;

namespace McpClientDemo.Deepseek
{
    public partial class ChatForm : Form
    {
        private readonly ChatOrchestrator _orchestrator;
        private bool _isLoading = false;


        string Key = "sk-4826f0";
        public ChatForm()
        {
            InitializeComponent();

            // 初始化 DeepSeek 客户端
            var deepSeek = new DeepSeekClient(
               Key,
                "https://api.deepseek.com",
                "deepseek-v4-flash");

            _orchestrator = new ChatOrchestrator(deepSeek);
            _orchestrator.LogMessage += (s, msg) => AppendLog(msg);
            _orchestrator.ToolCallStarted += (s, msg) => AppendLog(msg);

            // 设置系统提示词
            _orchestrator.SetSystemPrompt(
                "你是一个工控上位机助手,可以通过 MCP 工具操控 WinForms 应用程序。" +
                "当用户要求操作界面时,请调用相应的工具完成。" +
                "回复时使用中文,简洁明了。");

            btnConnect.Click += async (s, e) => await ConnectMcpAsync();
            btnSend.Click += async (s, e) => await SendMessageAsync();
            btnClear.Click += (s, e) =>
            {
                _orchestrator.ClearHistory();
                rtbMessages.Clear();
            };
            txtInput.KeyDown += async (s, e) =>
            {
                if (e.KeyCode == Keys.Enter && !e.Shift)
                {
                    e.Handled = true;
                    await SendMessageAsync();
                }
            };
        }

        private async Task ConnectMcpAsync()
        {
            btnConnect.Enabled = false;
            try
            {
                var url = txtMcpUrl.Text.Trim();
                if (string.IsNullOrEmpty(url))
                {
                    MessageBox.Show("请输入 MCP 服务器地址"); return;
                }

                var transport = new HttpClientTransport(new HttpClientTransportOptions
                {
                    Endpoint = new Uri(url),
                    TransportMode = HttpTransportMode.AutoDetect,
                });

                await _orchestrator.ConnectMcpServerAsync(transport);
                lblStatus.Text = "已连接";
                lblStatus.ForeColor = Color.Green;
                btnSend.Enabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}");
                lblStatus.Text = "连接失败";
                lblStatus.ForeColor = Color.Red;
            }
            finally { btnConnect.Enabled = true; }
        }

        private async Task SendMessageAsync()
        {
            if (_isLoading) return;
            var text = txtInput.Text.Trim();
            if (string.IsNullOrEmpty(text)) return;

            _isLoading = true;
            btnSend.Enabled = false;
            txtInput.Clear();

            AppendMessage("用户", text);

            try
            {
                var reply = await _orchestrator.SendMessageAsync(text);
                AppendMessage("AI", reply);
            }
            catch (Exception ex)
            {
                AppendMessage("错误", ex.Message);
            }
            finally
            {
                _isLoading = false;
                btnSend.Enabled = true;
            }
        }

        private void AppendMessage(string role, string content)
        {
            var prefix = role == "用户" ? "👤 " : "🤖 ";
            rtbMessages.AppendText($"{prefix}{role}: {content}\r\n\r\n");
            rtbMessages.ScrollToCaret();
        }

        private void AppendLog(string msg)
        {
            var timestamp = DateTime.Now.ToString("HH:mm:ss");
            rtbMessages.AppendText($"  [{timestamp}] {msg}\r\n");
        }

        private void btnSend_Click(object sender, EventArgs e)
        {

        }
    }
}

对话框布局 ChatForm.Designer.cs

using System.Xml.Linq;
using static System.Net.Mime.MediaTypeNames;

namespace McpClientDemo.Deepseek
{
    partial class ChatForm
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            lblMcpUrl = new Label();
            txtMcpUrl = new TextBox();
            btnConnect = new Button();
            lblStatus = new Label();
            rtbMessages = new RichTextBox();
            txtInput = new TextBox();
            btnSend = new Button();
            btnClear = new Button();
            SuspendLayout();
            // 
            // lblMcpUrl
            // 
            lblMcpUrl.AutoSize = true;
            lblMcpUrl.Location = new Point(12, 15);
            lblMcpUrl.Name = "lblMcpUrl";
            lblMcpUrl.Size = new Size(78, 17);
            lblMcpUrl.TabIndex = 0;
            lblMcpUrl.Text = "MCP 服务器:";
            // 
            // txtMcpUrl
            // 
            txtMcpUrl.Location = new Point(90, 12);
            txtMcpUrl.Name = "txtMcpUrl";
            txtMcpUrl.Size = new Size(350, 23);
            txtMcpUrl.TabIndex = 1;
            txtMcpUrl.Text = "http://localhost:5000/mcp";
            // 
            // btnConnect
            // 
            btnConnect.Location = new Point(450, 11);
            btnConnect.Name = "btnConnect";
            btnConnect.Size = new Size(70, 25);
            btnConnect.TabIndex = 2;
            btnConnect.Text = "连接";
            // 
            // lblStatus
            // 
            lblStatus.AutoSize = true;
            lblStatus.ForeColor = Color.Red;
            lblStatus.Location = new Point(530, 15);
            lblStatus.Name = "lblStatus";
            lblStatus.Size = new Size(44, 17);
            lblStatus.TabIndex = 3;
            lblStatus.Text = "未连接";
            // 
            // rtbMessages
            // 
            rtbMessages.BackColor = SystemColors.Window;
            rtbMessages.Font = new System.Drawing.Font("Microsoft YaHei UI", 10F);
            rtbMessages.Location = new Point(12, 45);
            rtbMessages.Name = "rtbMessages";
            rtbMessages.ReadOnly = true;
            rtbMessages.Size = new Size(588, 350);
            rtbMessages.TabIndex = 4;
            rtbMessages.Text = "";
            // 
            // txtInput
            // 
            txtInput.Font = new System.Drawing.Font("Microsoft YaHei UI", 10F);
            txtInput.Location = new Point(12, 405);
            txtInput.Multiline = true;
            txtInput.Name = "txtInput";
            txtInput.ScrollBars = ScrollBars.Vertical;
            txtInput.Size = new Size(480, 50);
            txtInput.TabIndex = 5;
            // 
            // btnSend
            // 
            btnSend.Enabled = false;
            btnSend.Location = new Point(500, 405);
            btnSend.Name = "btnSend";
            btnSend.Size = new Size(100, 24);
            btnSend.TabIndex = 6;
            btnSend.Text = "发送";
            btnSend.Click += btnSend_Click;
            // 
            // btnClear
            // 
            btnClear.Location = new Point(500, 431);
            btnClear.Name = "btnClear";
            btnClear.Size = new Size(100, 24);
            btnClear.TabIndex = 7;
            btnClear.Text = "清空";
            // 
            // ChatForm
            // 
            AutoScaleDimensions = new SizeF(7F, 17F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(614, 471);
            Controls.Add(lblMcpUrl);
            Controls.Add(txtMcpUrl);
            Controls.Add(btnConnect);
            Controls.Add(lblStatus);
            Controls.Add(rtbMessages);
            Controls.Add(txtInput);
            Controls.Add(btnSend);
            Controls.Add(btnClear);
            FormBorderStyle = FormBorderStyle.FixedDialog;
            MaximizeBox = false;
            Name = "ChatForm";
            StartPosition = FormStartPosition.CenterParent;
            Text = "MCP 对话助手";
            ResumeLayout(false);
            PerformLayout();
        }

        private Label lblMcpUrl;
        private TextBox txtMcpUrl;
        private Button btnConnect;
        private Label lblStatus;
        private RichTextBox rtbMessages;
        private TextBox txtInput;
        private Button btnSend;
        private Button btnClear;
    }
}```

## 核心 API 解释

### DeepSeek Tool Calls 流程

用户: 请帮我把文本框改成 Hello

DeepSeek 请求:
{
model: "deepseek-v4-flash",
messages: [...],
tools: [
{ type: "function", function: { name: "SetTextBoxContent", ... } }
]
}

DeepSeek 响应:
{
choices: [{
message: {
role: "assistant",
tool_calls: [{
id: "call_001",
function: { name: "SetTextBoxContent", arguments: "{\"newText\":\"Hello\"}" }
}]
}
}]
}

→ 执行 MCP 工具调用 → 返回结果
→ 将结果反馈给 DeepSeek → 获取最终回复


## 测试验证

### 1. 启动 MCP Server

```bash
cd McpTestDemo1
dotnet run

2. 启动 MCP Client(带对话框)

cd McpClient
dotnet run

3. 打开对话框,输入测试

  1. 输入 MCP 服务器地址 http://localhost:5000/mcp,点击"连接"
  2. 输入"请帮我把文本框改成 Hello"
  3. 观察:AI 自动调用 SetTextBoxContent 工具,McpTestDemo1 的文本框内容变化

踩坑总结

1. DeepSeek API 400 Bad Request

// tools 为 null 时不能发送,否则报 400
// 错误: body["tools"] = tools; // tools 可能是 null
// 正确: 只在 tools 非空时才添加
if (tools != null && tools.Count > 0)
    body["tools"] = ...;

2. messages 中的 null 字段

// OpenAI 格式不允许发送 null 字段
// 用 JsonIgnore(WhenWritingNull) 过滤
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Content { get; set; }

3. MCP 工具 parameters 格式

// MCP 工具的 JsonSchema 可能不是标准 OpenAI 格式
// 需要手动转换为 Dictionary 确保格式正确
[JsonPropertyName("parameters")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public JsonElement? Parameters { get; set; }

4. 工具调用循环

// AI 可能连续调用多个工具,需要循环处理
// 最多循环 10 次防止死循环
for (int i = 0; i < 10; i++) {
    var response = await deepSeek.SendAsync(messages, tools);
    if (response.ToolCalls == null) break; // 没有工具调用,返回
    // 执行工具调用...
}

扩展方向

  1. 流式输出 — 使用 SSE 实现打字机效果
  2. 多轮记忆 — 支持上下文对话
  3. 思考模式 — 启用 DeepSeek 的 thinking 功能
  4. 工具调用可视化 — 在对话框中显示工具调用过程
  5. 配置持久化 — 将 API Key 和服务器地址保存到文件

参考资料

posted @ 2026-07-11 14:29  家煜宝宝  阅读(18)  评论(0)    收藏  举报