从零实现 C++ AI 大模型接入 SDK(七):GeminiProvider 与兼容接口全量/流式实现
目录前言一、第三个 Provider统一的是能力不是协议1.1 初始化继续沿用 api_key base_url二、GeminiProvider 沿用 Chat Completions 结构2.1 公共 Message 怎样变成 messages2.2 请求参数仍然由 Provider 自己解释三、全量响应从请求到 choices[0].message.content3.1 发送请求3.2 先检查网络再检查 HTTP 状态3.3 解析 JSON四、流式响应真正复用的是 SSE 处理骨架4.1 请求端只增加几个流式设置4.2 response_handler 只负责 HTTP 层4.3 content_receiver 先处理网络 chunk4.4 从 buffer 中拆完整事件五、从 data: 到 delta.content再到结束回调5.1 先清理行再找 data:5.2 [DONE] 不是普通 JSON5.3 普通事件读取 choices[0].delta.content六、流式结束6.1 正常结束6.2 网络层失败6.3 HTTP 失败6.4 连接结束但没收到 [DONE]七、三个 Provider 放在一起八、测试 GeminiProvider写在最后前言系列从零实现 C AI 大模型接入 SDK第六篇项目源码AI-CHAT-SDKhttp://​ https://gitee.com/kuang-zhenting/my_ai_cpp_project ​前面几篇已经完成了两套不同的模型接入。DeepSeekProvider让我们第一次把统一 Provider 接口真正跑起来Message ↓ Chat Completions 风格请求 ↓ 全量 JSON / SSE ↓ std::string / callback到了第六篇ChatGPTProvider又换成了另一套 Responses APIMessage ↓ input ↓ output[] / response.output_text.delta ↓ std::string / callback这一篇继续接入第三个 ProviderGeminiProvider。第三个 Provider 的意义已经不只是“SDK 再多支持一个模型”。因为现在我们可以进一步验证一件事当底层协议再次变化时上层的ILLMProvider接口能不能继续保持不变当前源码中的GeminiProvider使用的是 Chat Completions 兼容结构POST /v1/chat/completions messages temperature max_tokens stream全量响应从choices[0].message.content取文本流式响应则继续使用choices[0].delta.content并通过data: [DONE]结束一轮 SSE。这意味着它和 DeepSeek 在“数据形状”上很接近但它仍然是一个独立 ProviderAPI Key、Endpoint、模型名、错误信息和实际服务都封装在GeminiProvider内部。所以这一篇不会重新从头讲一遍 SSE而是重点看三件事第三个 Provider 怎样继续复用ILLMProviderGemini 的兼容请求怎样映射成当前项目的统一输入已经写过的全量 / 流式处理逻辑哪些可以复用哪些仍然必须单独核对。一、第三个 Provider统一的是能力不是协议先看GeminiProvider.h。当前源码里的类声明很简单#pragma once #include ILLMProvider.h namespace ai_chat_sdk { class GeminiProvider : public ILLMProvider { public: bool initModel( const std::mapstd::string, std::string model_config) override; bool isAvailable() override; std::string getModelName() const override; std::string getModelDesc() const override; std::string sendMessage( const std::vectorMessage messages, const std::mapstd::string, std::string request_param) override; std::string sendMessageStream( const std::vectorMessage messages, const std::mapstd::string, std::string request_param, std::functionvoid(const std::string , bool) callback) override; }; }它没有再定义一套_api_key、_endpoint和_isAvailable。这些字段已经放在ILLMProvider的protected区域protected: bool _isAvailable false; std::string _api_key; std::string _endpoint;所以三个云端 Provider 的基本形状开始稳定下来ILLMProvider ├── DeepSeekProvider ├── ChatGPTProvider └── GeminiProvider这里最值得注意的不是继承语法而是抽象边界。上层只关心能不能初始化模型叫什么能不能发全量消息能不能发流式消息它不应该关心底层叫 messages 还是 input文本藏在 choices 还是 output流式结束靠 [DONE] 还是事件类型这些都应该留在具体 Provider 内部。1.1 初始化继续沿用 api_key base_url当前GeminiProvider::initModel()读取auto it1 model_config.find(api_key); if (it1 model_config.end()) { ERR(api_key is not found in model_config); return false; } _api_key it1-second;然后读取auto it2 model_config.find(base_url); if (it2 model_config.end()) { _endpoint https://www.nodapi.com; } else { _endpoint it2-second; }最后_isAvailable true;这和前面的 Provider 保持了同一种初始化方式model_config ↓ api_key base_url ↓ Provider 内部状态当前源码的模型名由getModelName()固定返回std::string GeminiProvider::getModelName() const { return gemini-3.5-flash; }因此这一篇后面的请求都按当前项目实际使用的gemini-3.5-flash展开。二、GeminiProvider 沿用 Chat Completions 结构如果刚写完上一篇的ChatGPTProvider这里很容易产生一个误解既然都是大模型 API是不是请求 JSON 差不多并不是。上一篇当前项目使用的是POST /v1/responses而GeminiProvider当前使用POST /v1/chat/completions它的请求体重新回到了我们在 DeepSeek 中已经很熟悉的结构{ model: gemini-3.5-flash, temperature: 0.7, max_tokens: 2048, messages: [ { role: user, content: 你好 } ] }如果是流式请求再增加{ stream: true }这就是当前项目选择兼容接口后最大的工程价值之一只要目标服务提供兼容的 Chat Completions 形状我们已经写过的大量消息组织、全量解析和 SSE 处理思路都可以复用。但这里的“复用”不是直接复制完就结束。我们仍然要核对Endpoint请求路径模型名认证方式参数名全量返回结构流式增量结构结束标记错误响应。2.1 公共 Message 怎样变成 messages上层仍然传入std::vectorMessage messages;Provider 内部把它转换成 JSON 数组Json::Value messages_array(Json::arrayValue); for (const auto message : messages) { Json::Value msg; msg[role] message._role; msg[content] message._content; messages_array.append(msg); }于是 SDK 的公共结构Message{ _role, _content }就被转换成{ role: user, content: 你好 }这个过程再次说明Message是 SDK 的公共语言JSON 才是具体 Provider 的协议语言。2.2 请求参数仍然由 Provider 自己解释当前GeminiProvider支持temperature max_tokens默认值double temperature 0.7; int max_tokens 2048;如果上层传入对应参数就覆盖默认值auto it request_param.find(temperature); if (it ! request_param.end()) { temperature std::stod(it-second); } it request_param.find(max_tokens); if (it ! request_param.end()) { max_tokens std::stoi(it-second); }然后写入请求体request_body[model] getModelName(); request_body[temperature] temperature; request_body[max_tokens] max_tokens; request_body[messages] messages_array;到这里公共输入已经完成第一次转换Message request_param ↓ GeminiProvider ↓ Chat Completions 兼容 JSON三、全量响应从请求到 choices[0].message.content全量响应的实现可以分成四步构造 JSON ↓ 发送 POST ↓ 解析完整响应体 ↓ 提取 choices[0].message.content3.1 发送请求当前源码创建客户端httplib::Client client(_endpoint); client.set_connection_timeout(30, 0); client.set_read_timeout(60, 0);请求头httplib::Headers headers { {Authorization, Bearer _api_key}, {Content-Type, application/json} };然后发送auto response client.Post( /v1/chat/completions, headers, json_string, application/json );这里不要把_endpoint和接口路径混在一起理解。当前代码的完整请求由两部分组成_endpoint /v1/chat/completions因此base_url可以通过配置变化而 Provider 的协议路径仍然保持明确。3.2 先检查网络再检查 HTTP 状态client.Post()返回以后第一层判断是if (!response) { ERR(Failed to connect to Gemini API); return ; }这表示连 HTTP 响应都没有拿到。第二层才是 HTTP 状态码if (response-status ! 200) { ERR(Gemini API returned status: {}, body: {}, response-status, response-body); return ; }这两类错误要分开看没有 response → 网络 / TLS / 连接层问题有 response但 status ! 200 → 服务端已经响应只是请求失败。3.3 解析 JSON拿到完整响应后Json::Value response_json; Json::CharReaderBuilder reader_builder; std::string parse_errors; std::istringstream response_stream(response-body); if (!Json::parseFromStream( reader_builder, response_stream, response_json, parse_errors)) { ERR(Failed to parse Gemini response: {}, parse_errors); return ; }解析成功并不代表一定能直接访问response_json[choices][0][message][content]因为异常响应、字段变化或者空数组都有可能让这个路径不存在。所以当前源码先逐层检查if (response_json.isMember(choices) response_json[choices].isArray() !response_json[choices].empty()) { const Json::Value choice response_json[choices][0]; if (choice.isMember(message) choice[message].isMember(content)) { return choice[message][content].asString(); } }这条路径最终可以压缩成response_json ↓ choices[0] ↓ message ↓ content ↓ std::string如果结构不符合预期就记录ERR(Invalid Gemini response format);而不是继续用固定下标硬取。四、流式响应真正复用的是 SSE 处理骨架前面第五篇已经把 SSE 最难的一部分讲过了network chunk ≠ 完整 SSE event所以到了 Gemini这一篇不需要再从零解释 SSE。我们真正要确认的是Gemini 当前返回的 SSE 结构能不能继续走已经形成的 buffer → event → data → JSON → delta.content 这条链答案从当前源码来看是可以的。4.1 请求端只增加几个流式设置请求体增加request_body[stream] true;请求头增加{Accept, text/event-stream}同时不再直接使用简单的client.Post(...)而是手动构造httplib::Request req; req.method POST; req.path /v1/chat/completions; req.headers headers; req.body json_string;因为流式请求需要两个关键回调response_handler content_receiver4.2 response_handler 只负责 HTTP 层当前代码req.response_handler [](const httplib::Response response) { statusCode response.status; if (statusCode ! 200) { gotError true; errorMsg HTTP error: std::to_string(statusCode); ERR({}, errorMsg); return true; } return true; };这里即使状态码失败也没有立刻return false;原因很重要。如果现在就终止接收我们可能只能知道HTTP 401却拿不到服务端返回的详细错误正文。所以当前实现选择继续接收 response body让content_receiver把错误文本追加进errorMsg4.3 content_receiver 先处理网络 chunk真正的流式数据从这里进来req.content_receiver [](const char *data, size_t len, uint64_t, uint64_t) { ... };每次回调只代表网络层这一次交给了我们len个字节。它并不保证一次 callback 一条完整 SSE event。因此当前源码仍然使用std::string buffer;然后buffer.append(data, len);注意这里不是buffer data;因为data只是一个指针当前有效长度由len明确给出不应该假设它以\0结束。4.4 从 buffer 中拆完整事件当前实现同时寻找buffer.find(\n\n);和buffer.find(\r\n\r\n);然后选择先出现的事件边界。如果两种都没有找到if (eventEnd std::string::npos) { break; }这代表现在只有半截事件此时不解析也不删除。等下一次网络数据到来以后继续buffer.append(data, len);直到拼成完整 event。这套逻辑与 DeepSeek 流式实现高度相似。这也是代码复用之外更重要的一种复用我们已经形成了稳定的流式数据处理模型。五、从 data: 到 delta.content再到结束回调拿到完整 SSE event 以后还不能直接解析 JSON。因为 SSE 本身还有一层文本协议。一条事件可能是data: {choices:[{delta:{content:你好}}]}也可能是: keep-alive data: {choices:[...]}还可能是data: [DONE]因此代码先逐行读取std::istringstream eventStream(event); std::string line; while (std::getline(eventStream, line)) { ... }5.1 先清理行再找 data:如果使用\r\ngetline()后面可能留下\r所以先处理if (!line.empty() line.back() \r) { line.pop_back(); }空行和注释行跳过if (line.empty() || line[0] :) { continue; }只有以data:开头的行才进入 payloadif (line.rfind(data:, 0) 0) { std::string value line.substr(5); if (!value.empty() value.front() ) { value.erase(value.begin()); } payload value; }5.2 [DONE] 不是普通 JSONpayload 得到以后第一件事不是 JSON 解析而是判断if (payload [DONE]) { streamFinish true; notifyDone(); continue; }因为[DONE]不是 JSON。如果直接交给 JsonCpp当然会解析失败。5.3 普通事件读取 choices[0].delta.content不是[DONE]再进入 JSON 解析Json::Value chunk; Json::CharReaderBuilder readerBuilder; std::string errors; std::istringstream jsonStream(payload); if (!Json::parseFromStream( readerBuilder, jsonStream, chunk, errors)) { WARN(Gemini SSE JSON parse error: {}, errors); continue; }然后逐层确认if (chunk.isMember(choices) chunk[choices].isArray() !chunk[choices].empty() chunk[choices][0].isMember(delta) chunk[choices][0][delta].isMember(content)) { const std::string content chunk[choices][0][delta][content].asString(); fullResponse content; if (!content.empty() callback) { callback(content, false); } }这一步最终得到choices[0] ↓ delta ↓ content为什么仍然要一层一层检查因为流式事件不保证每一块都有content有些事件可能只有角色信息有些可能只有结束原因。因此“成功解析 JSON”不等于“这一块一定有新文本”。六、流式结束流式接口最容易出现的一种假象是终端已经看到回复文字于是我们就认为实现完成了实际上还差最后一层上层什么时候知道这一轮结束当前接口的 callback 定义是std::functionvoid(const std::string , bool) callback项目约定callback(content, false) → 有新的增量文本callback(, true) → 这一轮结束。所以GeminiProvider定义bool doneCallbackSent false;并统一封装auto notifyDone []() { if (!doneCallbackSent callback) { callback(, true); doneCallbackSent true; } };这样可以避免多个异常分支重复触发结束通知。6.1 正常结束读到data: [DONE]时streamFinish true; notifyDone();6.2 网络层失败真正发送请求auto result client.send(req);如果失败if (!result) { const auto error result.error(); ERR(Network error: {}, httplib::to_string(error)); notifyDone(); return ; }这里使用httplib::to_string(error)而不是把httplib::Error直接交给日志格式化库。6.3 HTTP 失败如果之前response_handler已经设置gotError true;请求结束后统一处理if (gotError) { ERR(Gemini stream request failed: {}, errorMsg); notifyDone(); return ; }6.4 连接结束但没收到 [DONE]最后还要判断if (!streamFinish) { WARN(Stream ended without [DONE] marker); notifyDone(); }这一步非常重要。因为TCP / HTTP 请求结束并不一定代表模型业务层正常结束。中途断网、服务端异常关闭都可能让请求提前结束。所以完整判断应该是有没有网络错误 ↓ 有没有 HTTP 错误 ↓ 有没有收到 [DONE] ↓ 上层是否已经收到结束回调而不是只看终端有没有打印出几段文字。七、三个 Provider 放在一起到这里SDK 已经有三个云端 Provider。它们可以放在一张表里看项目DeepSeekProviderChatGPTProviderGeminiProvider请求路径/v1/chat/completions/v1/responses/v1/chat/completions消息字段messagesinputmessages全量文本choices[0].message.contentoutput[].content[].textchoices[0].message.content流式增量choices[0].delta.contentresponse.output_text.deltachoices[0].delta.content结束语义[DONE]response.completed[DONE]从这张表里能看出一个很有意思的现象。GeminiProvider在协议形状上更接近DeepSeekProvidermessages choices delta [DONE]而ChatGPTProvider使用的是另一套input output event type response.completed但是站在 SDK 上层看三个 Provider 的使用方式仍然是provider-sendMessage(messages, request_param);或者provider-sendMessageStream( messages, request_param, callback );这就说明第三篇里设计的抽象开始真正站住了。Provider 的价值不是消灭厂商差异而是把厂商差异封装在统一能力之后。只要上层不直接依赖具体 JSON 字段以后增加新的 Provider 时业务层就不需要跟着重写。八、测试 GeminiProvider当前项目已经有两组测试GeminiProviderTest.SendMessage GeminiProviderTest.SendMessageStream全量测试的大致流程是ai_chat_sdk::GeminiProvider provider; const char *api_key std::getenv(GEMINI_KEY_API); ASSERT_NE(api_key, nullptr); std::mapstd::string, std::string config; config[api_key] api_key; ASSERT_TRUE(provider.initModel(config)); ASSERT_TRUE(provider.isAvailable()); std::vectorai_chat_sdk::Message messages; messages.emplace_back(user, 你是谁); std::string reply provider.sendMessage(messages, params); EXPECT_FALSE(reply.empty());流式测试则额外累计 callback 收到的内容std::string streamedText; auto writeChunk [](const std::string chunk, bool isDone) { if (!chunk.empty()) { streamedText chunk; INFO(chunk: {}, chunk); } if (isDone) { INFO([DONE]); } }; const std::string fullData provider-sendMessageStream( messages, requestParam, writeChunk ); EXPECT_FALSE(fullData.empty()); EXPECT_FALSE(streamedText.empty()); EXPECT_EQ(fullData, streamedText);写在最后到这里GeminiProvider的全量和流式两条链路都已经清楚了。第三个 Provider 接入以后我们现在得到的已经不只是三份 HTTP 请求代码而是一套逐渐稳定的模型适配方式ILLMProvider ↓ 具体 Provider 负责协议转换 ↓ 统一返回 std::string / callbackDeepSeek 和 Gemini 证明兼容协议可以复用大量工程结构ChatGPT 又证明即使底层换成完全不同的 Responses API上层接口仍然可以保持不变。下一步再接入本地模型时问题会进一步变化云端模型依赖 API Key 和远程 HTTPS如果模型直接运行在本机Provider 这一层还需要调整什么下一篇继续接入OllamaLLMProvider。

相关新闻

【C++】泛型编程详解:函数模板与类模板全梳理

【C++】泛型编程详解:函数模板与类模板全梳理

目录 泛型编程 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 1.隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 2.显式实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 模板参数的匹配原则 类模板 泛型编程…

2026/10/12 2:22:21 阅读更多 →
二叉树最大深度:递归、DFS与BFS的三种解法深度解析

二叉树最大深度:递归、DFS与BFS的三种解法深度解析

刷 LeetCode Hot100 刷到第 28 题的时候&#xff0c;说实话我已经有点疲了&#xff0c;而这题《104. 二叉树的最大深度》看上去就是那种"白给"的简单题。但真把它掰开揉碎以后&#xff0c;我发现它其实是递归、DFS、BFS、分治思想的一块绝佳试金石&#xff0c;里面值…

2026/10/12 2:22:21 阅读更多 →
终端里的AI打砖块:用Bash实现Claude流式响应可视化

终端里的AI打砖块:用Bash实现Claude流式响应可视化

1. 项目概述&#xff1a;当AI助手变成终端里的复古游戏机最近在某开发者社区刷到一个标题特别扎眼的帖子&#xff1a;“Claude 干活的时候&#xff0c;在终端里打砖块”——第一反应是怀疑自己看错了。Claude 是那个以长上下文、强推理和文档理解见长的AI模型&#xff0c;不是用…

2026/10/12 2:22:21 阅读更多 →

最新新闻

GESP C++三级真题:日历制作题完整思路与避坑指南

GESP C++三级真题:日历制作题完整思路与避坑指南

备考 GESP C 三级的朋友&#xff0c;看到真题解析里出现“日历制作”四个字&#xff0c;第一反应往往是&#xff1a;这能叫编程题&#xff1f;打印一个日历表还不简单。真坐到电脑前动手写&#xff0c;才发现到处都是细节——这个月有多少天、2 月到底多不多一天、1 号落在星期…

2026/10/12 4:51:52 阅读更多 →
Apache Beam Python RunInference 变换实战:在 PCollection 上执行本地与远程机器学习推理

Apache Beam Python RunInference 变换实战:在 PCollection 上执行本地与远程机器学习推理

批处理流处理大数据 【免费下载链接】beam Apache Beam is a unified programming model for Batch and Streaming data processing. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/beam15/beam 点击查看 免费下载 RunInference 是 Apache Beam 面向机器学习推理场景的…

2026/10/12 4:51:52 阅读更多 →
高并发架构设计面试拆解:量化推演、流量分层与兜底策略

高并发架构设计面试拆解:量化推演、流量分层与兜底策略

最近帮几位朋友复盘技术面试&#xff0c;十次有八次会遇到同一道题&#xff1a;高并发系统架构设计。这道题看似人人都能聊几句&#xff0c;缓存、队列、分库分表、限流熔断&#xff0c;名词往外一甩好像就答完了&#xff0c;但面试官往往并不买账。我见过太多候选人挂在同一个…

2026/10/12 4:51:52 阅读更多 →
软件测试笔试高频题型拆解:概念、用例设计到接口自动化

软件测试笔试高频题型拆解:概念、用例设计到接口自动化

软件测试岗位的笔试题&#xff0c;说到底就是一道筛选漏斗&#xff1a;先筛掉那些连基本概念都含混不清的人&#xff0c;再筛掉只会背题、不会思考的人&#xff0c;最后留下的不一定技术最牛&#xff0c;但一定是思路最清楚、最懂“怎么把测试这件事说明白”的人。这几年我面过…

2026/10/12 4:51:52 阅读更多 →
深入解析AnyPS5:用兼容层技术在PC上运行主机游戏

深入解析AnyPS5:用兼容层技术在PC上运行主机游戏

1. 项目概述&#xff1a;AnyPS5 到底解决什么问题先聊几句。如果你混过主机模拟器和兼容层这个圈子&#xff0c;应该见过不少项目&#xff0c;名字里带“Any”的一般都不简单&#xff0c;比如 AnyX、AnyY 这类&#xff0c;主打的就是“什么都行”的野心。这个 AnyPS5 项目也不例…

2026/10/12 4:51:52 阅读更多 →
UI设计中标准化与灵动有趣的平衡之道

UI设计中标准化与灵动有趣的平衡之道

说实话&#xff0c;做网站UI设计这么久&#xff0c;我经常被问到同一个问题&#xff1a;“设计风格到底应该走标准化&#xff0c;还是做得灵动有趣一些&#xff1f;”问的人有刚入行的新人&#xff0c;也有带团队的组长&#xff0c;甚至有甲方爸爸。每次讨论到最后&#xff0c;…

2026/10/12 4:50:51 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天&#xff0c;画面可以做到绝对的锐利、平滑与无瑕。然而&#xff0c;当一张秋日手账插画或拍立得照片过于“平整无瑕”时&#xff0c;往往会散发出一种冰冷生硬的“数码塑料感&#xff08;Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中&#xff0c;横排&#xff08;Horizontal Layout&#xff09;早已经成为了绝对的主流。然而&#xff0c;当我们翻开泛黄的线装古籍、宋版木刻诗集&#xff0c;或是欣赏一张茶道雅集的手写便签时&#xff0c;那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点&#xff0c;很多人心里都会悄悄亮起一盏警示灯。 在心理学上&#xff0c;这种现象有一个专门的称谓——“周日夜晚焦虑症&#xff08;Sunday Scaries&#xff09;”。明天又是周一&#xff0c;闹钟又要重新在七点响彻卧房&#xff1b;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →