自由Agent很爽,但我为什么还是自己动手写了个”专项Agent”?

前一段圈子里最火的两个词,一个是 OpenClaw(外号”龙虾”),一个是 Hermes Agent(外号”爱马仕”)。

这俩都属于”自由型个人Agent”——你装好之后,它就像一个24小时在线的数字员工:帮你查资料、发消息、操作浏览器、管理文件,甚至直接跑代码。微信、Telegram、Discord 发句话就能干活,想想是不是很香?

香是真香。但用久了你会发现,这类”什么都能干”的Agent,恰恰在”稳定把一件事干好”上,经常掉链子。

今天这篇文章就聊聊:

  1. 自由型Agent到底强在哪、坑在哪
  2. 为什么正经业务我还是选择自己写代码构建专项Agent
  3. 最后附上我的完整实战代码——一个能保存状态、失败后从检查点恢复、不用重跑省Token的AI简报发布工作流

一、先夸夸:自由型Agent为什么这么火

平心而论,OpenClaw 和 Hermes Agent 能火不是没有道理的。

1. 自由

它们不是只会聊天的机器人,而是真的能”动手”:读写本地文件、操作浏览器、执行终端命令、联网搜索……你的电脑它基本都能碰。想让它干啥,一句话的事。

2. 灵活

接入渠道多得吓人。Hermes Agent 支持十几个消息平台、两百多个大模型、四十多个内置工具,还能接 MCP 服务器扩展能力。OpenClaw 也是通过一个统一的 Gateway 中枢,调度各类本地或远程工具。想怎么组合就怎么组合。

3. 社区支持猛,迭代快

OpenClaw 上线短短几天 GitHub star 就突破了16万;Hermes Agent 开源后42天从 v0.1.0 干到 v0.8.0,社区贡献者超过240人。你今天遇到的问题,可能下周版本更新就修了。生态里还有技能市场,别人写好的技能直接拿来用。

一句话总结:上手快、玩法多、白嫖爽。 作为个人玩具或者效率工具,真的没什么可挑的。

二、但缺点也是真的明显

爽归爽,一旦你想让它稳定地、可重复地完成一件正经事,问题就全冒出来了。

1. 每次运行的不确定性太强

这是最要命的一点。

自由型Agent的执行路径是模型”现场发挥”出来的:同一个任务,今天它先搜再总结,明天它可能先打开浏览器瞎逛一圈;这次输出格式规规矩矩,下次给你换个花样。你没法保证”每天早上9点,它都能一模一样地把这件事干完”。

更糟的是”误操作”风险。国家互联网应急中心专门发过风险提示:由于错误理解用户的操作指令和意图,OpenClaw 可能会把电子邮件、核心生产数据等重要信息彻底删除。让一个想法飘忽的实习生拿着你全部权限干活,你敢睡安稳觉吗?

2. 安全问题一箩筐

我上网翻了一圈,OpenClaw 的安全问题多到可以单独写一篇:

  • 默认零认证:出厂配置不启用任何身份验证,有人在 Shodan 上一搜,发现大量跑在18789端口的 OpenClaw 网关直接裸奔在公网上,任何人都能访问,执行命令、读文件、偷密钥。
  • API密钥明文存储:AI服务、云服务的Key就明晃晃躺在本地配置文件里,实例一旦被入侵,直接送温暖。
  • 技能市场投毒:安全审计显示,第三方技能市场 ClawHub 上约36.82%的技能存在可被利用的安全缺陷,还发现了341个带键盘记录器、凭据窃取器的恶意技能包。而默认配置下,AI居然可以不经用户确认自动安装技能
  • 高危漏洞频出:2026年3月,GitHub Advisory Database 集中披露了数十个相关安全问题,国家信息安全漏洞库(NVDB)也收录了多款高危漏洞,覆盖认证绕过、命令注入、远程代码执行,CVSS评分最高8.8,还有已发现在野利用的。

有安全研究者用 Simon Willison 提出的”致命三角”框架分析过:能读私有数据 + 处理不可信输入 + 可以对外通信,三者叠加,数据泄露就不是概率问题,而是时间问题。而自由型Agent,天生把这三样全占了。

3. 排查和审计基本靠猜

工具调用过程缺乏透明度——谁在调用什么、以什么权限调用、调用结果合不合规,你很难追踪。出了问题想复盘?黑盒一个,审计形同虚设。

4. Token烧起来没数

自由发挥就意味着模型可能绕远路:多试几次、多逛几个页面、多想几轮……每一步都是白花花的Token。任务失败了?对不起,从头再来,前面烧的钱打水漂。

三、自己写代码构建”专项Agent”,好处在哪

所以我的观点很简单:

玩,用自由Agent;干活,写专项Agent。

所谓专项Agent,就是针对一个具体业务场景,自己用代码把流程”钉死”的Agent。以我用 Microsoft Agent Framework(C#)写的这个”AI简报自动发布工作流”为例,它只干一件事:

每天采集AI新闻 → 翻译成日语简报 → 自动登录网站发布智能海报。

自己写代码,能拿到自由型Agent给不了的四样东西:

1. 流程确定,结果可控

工作流是我用 WorkflowBuilder 一行行搭出来的:先执行采集Executor,再执行发布Executor,边(Edge)连得明明白白。模型只在每个节点内部发挥,整体流程永远不会跑偏。输出格式?用 JSON Schema 强制约束(ChatResponseFormat.ForJsonSchema<PushResult>()),模型想自由发挥都没门。

2. 权限最小化,安全自己说了算

专项Agent只挂它需要的工具:采集节点只有搜索工具,发布节点只有 Playwright 浏览器工具。密钥怎么存、网络怎么走、要不要沙箱,全在我自己的代码里,没有”默认裸奔”这回事。

3. 每一步可观测、可审计

工作流的每个事件——执行器完成、检查点保存、异常抛出——都流式吐出来,Token用量和费用精确到每一次调用,一张表打印得清清楚楚。出问题了看日志,一目了然。

4. 最关键的:状态可保存,失败可恢复,省Token!

这是我这段代码里最想安利的设计,展开讲讲。


四、实战:Checkpoint检查点,让失败不再烧钱

先说场景:我的工作流分两步——

  • 第一个Agent(CollectAINewExecutor):调用搜索工具,从 OpenAI、Anthropic、机器之心等一堆网站采集昨天的AI新闻,翻译成日语简报。这一步要联网搜索+大量生成,Token消耗大头在这
  • 第二个Agent(PushReportExecutor):通过 Playwright MCP 操作浏览器,登录网站、切语言、点按钮、贴内容、发布海报。这一步全是浏览器操作,天然容易失败——页面加载慢了、按钮位置变了、网络抖一下,都可能翻车。

问题来了:如果发布失败,难道要把整个工作流从头跑一遍?采集那一步的Token不就白烧了吗?

答案是:不用。用检查点(Checkpoint)。

核心思路三步走:

第一步:开启检查点管理器

var checkpointManager = CheckpointManager.CreateInMemory();
把它传给工作流执行入口,框架就会在每个"超级步骤"(SuperStep)完成时自动打检查点。

第二步:第一个Agent把状态存进检查点

在采集Executor里重写 OnCheckpointingAsync,把采集到的简报内容存入状态:

protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, ...){    return context.QueueStateUpdateAsync("CollectAINew", _aiMessage, ...);}
这样,采集结果就被"冻结"在检查点里了。

第三步:发布失败?从检查点恢复,直接重试发布

在外层监听工作流事件,一旦发布结果 PushResult.Result == false,就从最近的检查点恢复:

if (!pushResult.Result && times < 3){    times++;    await run.RestoreCheckpointAsync(checkpoints.Last());}
恢复之后,工作流直接从发布节点重新开始,采集Agent的结果原封不动地从状态里读回来(OnCheckpointRestoredAsync),一个搜索Token都不用重新花。

最多重试3次,次次失败才认输,并打印失败原因。发布成功则汇总两个Agent的总费用,收工。

一句话总结这个设计的价值:

把”贵的、稳定的”和”便宜的、易失败的”步骤拆开,中间打检查点。失败只重跑便宜的那段,贵的那段永远只跑一次。

这种精细的成本控制和容错设计,你在自由型Agent里是很难做到的——它们连”上一步花了多少Token”都未必跟你算清楚。


五、写在最后

自由型Agent和专项Agent,不是谁取代谁的关系:

  • OpenClaw / Hermes Agent:适合个人探索、临时任务、快速验证想法。享受它的自由,但记得做好安全加固,别把它暴露在公网上,别乱装第三方技能。
  • 自己写代码的专项Agent:适合有明确流程、需要稳定重复执行、对成本和安全敏感的正经业务。前期多写点代码,换来的是确定性、可观测性和真金白银的Token节省。

用大白话说就是:自由Agent是瑞士军刀,专项Agent是流水线上的专用机床。削个苹果用军刀没问题,量产零件还得靠机床。

附:完整代码

基于 Microsoft Agent Framework + Azure OpenAI + Playwright MCP,.NET 控制台程序,直接上:

using Azure.AI.OpenAI;using Microsoft.Agents.AI;using Microsoft.Agents.AI.Workflows;using Microsoft.Extensions.AI;using ModelContextProtocol.Client;using OpenAI.Responses;using Spectre.Console;using System.ClientModel;using System.ComponentModel;#pragma warning disableConsoleOutput.WriteHeader();var checkpointManager = CheckpointManager.CreateInMemory();var checkpoints = new List<CheckpointInfo>();// 创建两个工作流执行器:// 1. translator:负责把中文翻译成日语初稿// 2. optimizer:负责对日语初稿进行润色优化var modelID = "gpt-5.6-luna";var collectAINewExecutor = new CollectAINewExecutor("CollectAINewExecutor", modelID);var pushReportExecutor = new PushReportExecutor("PushReportExecutor", modelID);// 构建工作流:先执行翻译,再执行优化,最终输出优化器的结果。var workflow = new WorkflowBuilder(collectAINewExecutor)    .AddEdge(collectAINewExecutor, pushReportExecutor)    .WithOutputFrom(pushReportExecutor)    .Build();var mermaid = workflow.ToMermaidString();ConsoleOutput.WriteWorkflowEvent("工作流已构建", "CollectAINewExecutor -> PushReportExecutor");// 以流式方式执行工作流。// 输入是一段中文文本,执行过程中会不断产出中间事件和最终输出。ConsoleOutput.WriteWorkflowEvent("开始执行", $"给出{DateTime.Now.AddDays(-1)}的8条AI相关新闻。");var run = await InProcessExecution.RunStreamingAsync(    workflow,    input: $"""    给出{DateTime.Now.AddDays(-1)}的8条AI相关新闻。    """,    checkpointManager);var times = 0;await GetWorkflowOutput(run);async Task GetWorkflowOutput(StreamingRun run){    await foreach (WorkflowEvent evt in run.WatchStreamAsync())    {        switch (evt)        {            case SuperStepCompletedEvent superStepCompletedEvt:
                CheckpointInfo? checkpoint = superStepCompletedEvt.CompletionInfo?.Checkpoint;                if (checkpoint is not null)                {                    checkpoints.Add(checkpoint);                    ConsoleOutput.WriteCheckpoint(checkpoints.Count, superStepCompletedEvt.StepNumber, checkpoint);                }                break;
            case ExecutorCompletedEvent executorCompletedEvent:
                ConsoleOutput.WriteExecutorCompleted(executorCompletedEvent.ExecutorId, executorCompletedEvent.Data);                break;
            // 输出工作流最终产物。            case WorkflowOutputEvent outputEvent:
                if (outputEvent != null && outputEvent.Data != null && outputEvent.Data is PushResult pushResult)                {                    ConsoleOutput.WritePushResult(pushResult);                    if (!pushResult.Result)                    {                        if (times < 3)                        {                            times++;                            if (checkpoints.Last() is null)                            {                                ConsoleOutput.WriteWorkflowWarning("没有可用于重试发布的检查点,无法恢复执行。");                                break;                            }
                            ConsoleOutput.WriteWorkflowEvent("恢复执行", "正在尝试从发布前的检查点恢复执行...");                            await run.RestoreCheckpointAsync(checkpoints.Last());                        }                        else                        {                            ConsoleOutput.WriteWorkflowWarning($"重试次数已达上限。失败原因:{pushResult.FailureReason}");                        }                    }                    else                    {                        ConsoleOutput.WriteWorkflowEvent($"发布成功", $"工作流已完成。共计费用: {collectAINewExecutor.SumPrice + pushReportExecutor.SumPrice} 美元");                    }                }                break;            // 输出工作流中的异常信息,便于排查模型调用或数据解析问题。            case WorkflowErrorEvent errorEvent:                ConsoleOutput.WriteWorkflowError(errorEvent.Exception);                break;        }    }}partial class CollectAINewExecutor : Executor{    public decimal SumPrice { get; private set; }    private readonly AIAgent _agent;    // 复用会话,避免每次处理消息都重新创建 agent session。    private AgentSession? _session;    private string _aiMessage;    private readonly string _modelID;    public CollectAINewExecutor(string id, string modelID) : base(id)    {        _modelID = modelID;        var aiConfig = new AIConfig.AzureOpenAIConfig(_modelID);        var credential = new ApiKeyCredential(aiConfig.Key);
        var chatClient = new AzureOpenAIClient(new Uri(aiConfig.Endpoint), credential)            .GetResponsesClient()            .AsIChatClient(aiConfig.DeploymentName);
        var instructions = """    https://openai.com/news    https://www.anthropic.com/news    https://deepmind.google/discover/blog/    https://www.therundown.ai    https://tldr.tech/ai    https://www.jiqizhixin.com/    https://www.qbitai.com/    https://huggingface.co/blog
    你是一个AI简报的汇总助手。按照用户要求,调用搜索工具,从以上网站搜索指定日期的AI相关新闻。不询问,直接给出结果。
    格式要求:    YYYY 年 MM 月 DD 日 AIニュース速報
    1、新闻标题(翻译成日语)    新闻摘要(翻译成日语,不超过100字)    新闻url链接(给出原文链接)
    2、新闻标题(翻译成日语)    新闻摘要(翻译成日语,不超过100字)    新闻url链接(给出原文链接)    ……    """;        var searchAgent = chatClient            .AsAIAgent(new ChatClientAgentOptions            {                Name = "searchAgent",                ChatOptions = new()                {                    Instructions = instructions,                    Tools = [                        ResponseTool.CreateWebSearchTool().AsAITool(),                        AIFunctionFactory.Create([Description("获取当前时间")]()=>DateTime.Now)                    ]                }            });        this._agent = searchAgent;    }    [MessageHandler]    public async ValueTask<string> HandleAsync(        string message,        IWorkflowContext context,        CancellationToken cancellationToken = default)    {        // 延迟初始化会话,仅在首次执行时创建。        this._session ??= await this._agent.CreateSessionAsync(cancellationToken);
        // 调用模型,并将返回的 JSON 反序列化为 TranslationResult。        var result = await this._agent.RunAsync(message, this._session, cancellationToken: cancellationToken);        var pricing = GptPricing.Prices[_modelID];        SumPrice += ConsoleOutput.WriteAgentUsage("CollectAINewExecutor", result.Usage.InputTokenCount, result.Usage.OutputTokenCount, pricing.Input, pricing.Output);        _aiMessage = result.Text;        return result.Text;    }    protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default)    {        return context.QueueStateUpdateAsync("CollectAINew", _aiMessage, cancellationToken: cancellationToken);    }    protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default)    {        _aiMessage = await context.ReadStateAsync<string>("CollectAINew", cancellationToken: cancellationToken);    }}[YieldsOutput(typeof(PushResult))]partial class PushReportExecutor : Executor<string>{    public decimal SumPrice { get; private set; }    private readonly AIAgent _agent;      private AgentSession? _session;    private PushResult _pushResult;    private readonly string _modelID;    public PushReportExecutor(string id, string modelID) : base(id)    {        _modelID = modelID;        var aiConfig = new AIConfig.AzureOpenAIConfig(_modelID);        var credential = new ApiKeyCredential(aiConfig.Key);        var chatClient = new AzureOpenAIClient(new Uri(aiConfig.Endpoint), credential)    .GetChatClient(aiConfig.DeploymentName)    .AsIChatClient();        var transport = new StdioClientTransport(new StdioClientTransportOptions        {            Name = "Playwright",            Command = "npx",            //Arguments = ["@playwright/mcp@latest", "--sandbox"],            // 想看到浏览器界面就别加 --headless;想跑无头加上即可;保留 --sandbox 可避免 --no-sandbox 安全警告            Arguments = ["@playwright/mcp@latest", "--sandbox", "--headless"],            EnvironmentVariables = new Dictionary<string, string> { { "PW_CHROMIUM_ARGS", "--start-maximized" } }        });        var mcpClient = McpClient.CreateAsync(transport).Result;        var tools = mcpClient.ListToolsAsync().Result;
        var linkoraConfig = new AIConfig.LinkoraConfig("linkora");        var browserAgent = chatClient            .AsAIAgent(new ChatClientAgentOptions            {                Name = "BrowserAgent",                ChatOptions = new()                {                    Instructions = $"""                            1、打开 https://linkora.com/user/login,输入用户名:{linkoraConfig.UserName},密码:{linkoraConfig.Password},然后勾选下方的多选框,再点击“登录”按钮。                            2、进入主页 https://linkora.com/home 后,在顶部右侧找到语言切换下拉菜单,将语言切换成“简体中文”。                            3、然后在找到内容为“AI动态发布”的按钮,并且点击。                            4、在弹出的层中找到“智能海报”选项卡,并点击。                            5、然后在“智能海报”的同一层的下方文本框中输入用户提供的【AI简报的内容】,点击下方的“生成并预览”按钮。                            6、等待生成后,点击右下方的“发布海报”按钮。                            7、打开主页 https://linkora.com/home 找最新发布的几篇海报,查看是否有今天发布的海报,确认是否发布成功。                            """,                    Tools = [.. tools.Cast<AITool>(),                      AIFunctionFactory.Create([Description("获取当前时间")]()=>DateTime.Now)                    ],                    ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema<PushResult>()                }            });        this._agent = browserAgent;    }    public override async ValueTask HandleAsync(        string message,        IWorkflowContext context,        CancellationToken cancellationToken = default)    {        // 延迟创建会话。        this._session ??= await this._agent.CreateSessionAsync(cancellationToken);        ConsoleOutput.WriteWorkflowEvent("开始执行 PushReportExecutor", "调用模型进行内容发布...");        // 调用模型并解析优化结果。        var result = await this._agent.RunAsync<PushResult>($"""            【AI简报的内容】如下:            {message}            """, this._session, cancellationToken: cancellationToken);
        var pricing = GptPricing.Prices[_modelID];        SumPrice += ConsoleOutput.WriteAgentUsage("PushReportExecutor", result.Usage.InputTokenCount, result.Usage.OutputTokenCount, pricing.Input, pricing.Output);        _pushResult = result.Result;        // 输出最终结果,供工作流外部消费。        await context.YieldOutputAsync(_pushResult, cancellationToken);    }    protected override ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellationToken = default)    {        _session = null;        _pushResult = default!;        return ValueTask.CompletedTask;    }}public static class GptPricing{    public static readonly IReadOnlyDictionary<string, (decimal Input, decimal CachedInput, decimal Output)> Prices =        new Dictionary<string, (decimal Input, decimal CachedInput, decimal Output)>        {            ["gpt-5.5"] = (5.00m, 0.50m, 30.00m),            ["gpt-5.4"] = (2.50m, 0.25m, 15.00m),            ["gpt-5.6-luna"] = (0.75m, 0.08m, 4.50m),            ["gpt-5.6-sol"] = (5.00m, 0.50m, 30.00m),            ["gpt-5.6-terra"] = (2.50m, 0.25m, 15.00m),            ["gpt-5.6-luna"] = (1m, 0.08m, 6m),        };}static class ConsoleOutput{    public static void WriteHeader()    {        AnsiConsole.Write(new Rule("[bold cyan1]AI Report[/]").RuleStyle("cyan1").Centered());        AnsiConsole.Write(new FigletText("AI Report").Centered().Color(Spectre.Console.Color.Cyan1));        AnsiConsole.Write(new Panel("[bold white]AI 简报代理工作流[/]\n\n[grey]采集 AI 新闻[/] [cyan1]→[/] [grey]生成日语简报[/] [cyan1]→[/] [grey]发布智能海报[/]\n[grey]运行模式:[/][white]Streaming Workflow + Checkpoint Recovery[/]")            .Header("[bold green] WORKFLOW [/]")            .Border(BoxBorder.Double)            .BorderColor(Spectre.Console.Color.Cyan1)            .Padding(2, 1)            .Expand());        AnsiConsole.WriteLine();    }    public static void WriteWorkflowEvent(string title, string message)    {        AnsiConsole.Write(new Panel($"[bold white]{Markup.Escape(title)}[/]\n[grey]{Markup.Escape(message)}[/]")            .Header("[blue] EVENT [/]")            .Border(BoxBorder.Rounded)            .BorderColor(Spectre.Console.Color.Blue)            .Padding(1, 0));    }    public static void WriteWorkflowWarning(string message)    {        AnsiConsole.Write(new Panel($"[yellow]{Markup.Escape(message)}[/]")            .Header("[yellow] WARNING [/]")            .Border(BoxBorder.Rounded)            .BorderColor(Spectre.Console.Color.Yellow)            .Padding(1, 0));    }    public static void WriteCheckpoint(int checkpointIndex, int stepNumber, CheckpointInfo checkpoint)    {        var table = new Table()            .Border(TableBorder.Rounded)            .BorderColor(Spectre.Console.Color.Yellow)            .Title($"[bold yellow]Checkpoint #{checkpointIndex}[/]")            .AddColumn(new TableColumn("[yellow]字段[/]").LeftAligned())            .AddColumn(new TableColumn("[yellow]值[/]").LeftAligned())            .Expand();
        table.AddRow("[grey]StepNumber[/]", $"[white]{stepNumber}[/]");        table.AddRow("[grey]CheckpointId[/]", $"[white]{Markup.Escape(checkpoint.CheckpointId)}[/]");        table.AddRow("[grey]SessionId[/]", $"[white]{Markup.Escape(checkpoint.SessionId)}[/]");
        AnsiConsole.Write(table);    }    public static void WriteExecutorCompleted(string executorId, object? data)    {        AnsiConsole.Write(new Panel($"[green]✓[/] [bold white]{Markup.Escape(executorId)}[/]\n\n{Markup.Escape(data?.ToString() ?? "<null>")}")            .Header("[green] EXECUTOR COMPLETED [/]")            .Border(BoxBorder.Rounded)            .BorderColor(Spectre.Console.Color.Green)            .Padding(1, 1)            .Expand());    }    public static void WritePushResult(PushResult pushResult)    {        var status = pushResult.Result ? "[green]成功[/]" : "[red]失败[/]";        var table = new Table()            .Border(TableBorder.Rounded)            .BorderColor(pushResult.Result ? Spectre.Console.Color.Green : Spectre.Console.Color.Red)            .Title(pushResult.Result ? "[bold green]最终推送结果[/]" : "[bold red]最终推送结果[/]")            .AddColumn(new TableColumn("[bold]字段[/]").LeftAligned())            .AddColumn(new TableColumn("[bold]值[/]").LeftAligned())            .Expand();
        table.AddRow("[grey]结果[/]", status);        if (!pushResult.Result)        {            table.AddRow("[grey]失败原因[/]", Markup.Escape(pushResult.FailureReason ?? string.Empty));        }
        AnsiConsole.Write(table);    }    public static void WriteWorkflowError(Exception? exception)    {        var message = exception?.Message ?? "未知错误";        AnsiConsole.Write(new Panel($"[bold red]{Markup.Escape(message)}[/]\n\n[grey]{Markup.Escape(exception?.ToString() ?? message)}[/]")            .Header("[red] WORKFLOW ERROR [/]")            .Border(BoxBorder.Rounded)            .BorderColor(Spectre.Console.Color.Red)            .Padding(1, 1)            .Expand());    }    public static decimal WriteAgentUsage(string agentName, long? inputeTokenCount, long? outputTokenCount, decimal inputPrice, decimal outputPrice)    {        var table = new Table()            .Border(TableBorder.Rounded)            .BorderColor(Spectre.Console.Color.Cyan1)            .Title($"[bold cyan1]Agent Usage · {Markup.Escape(agentName)}[/]")            .AddColumn(new TableColumn("[cyan1]指标[/]").LeftAligned())            .AddColumn(new TableColumn("[cyan1]值[/]").RightAligned())            .AddColumn(new TableColumn("[cyan1]费用[/]").RightAligned())            .Expand();
        table.AddRow("[grey]输入Token数[/]", $"[white]{inputeTokenCount}[/]", $"[white]${(inputeTokenCount ?? 0) * inputPrice / 1_000_000:F6}[/]");        table.AddRow("[grey]输出Token数[/]", $"[white]{outputTokenCount}[/]", $"[white]${(outputTokenCount ?? 0) * outputPrice / 1_000_000:F6}[/]");
        AnsiConsole.Write(table);        return (inputeTokenCount ?? 0) * inputPrice / 1_000_000 + (outputTokenCount ?? 0) * outputPrice / 1_000_000;    }}class PushResult{    [Description("本次推送是否成功")]    public bool Result { get; set; }    [Description("本次推送失败时的原因")]    public string FailureReason { get; set; }

声明:来自硅基-桂迹,仅代表创作者观点。链接:https://eyangzhen.com/8682.html

硅基-桂迹的头像硅基-桂迹

相关推荐

添加微信
添加微信
Ai学习群
返回顶部