news 2026/9/8 18:32:51

ClickHouse 关联测试选择器深入解析:基于逐测试行级覆盖率的 find_tests.py 架构与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ClickHouse 关联测试选择器深入解析:基于逐测试行级覆盖率的 find_tests.py 架构与实现

ClickHouse 关联测试选择器深入解析:基于逐测试行级覆盖率的 find_tests.py 架构与实现

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

导读

在 ClickHouse 的 CI 体系中,ci/jobs/scripts/find_tests.py扮演着“定向测试选择器”的角色:它对每个 PR 的 diff 做行级分析,再通过查询记录“每个测试覆盖了哪些源码行”的覆盖率数据库(CIDB),找出最可能回归该改动的高价值 stateless 测试。本文从 关联测试选择开发指南 出发,梳理 NightlyCoverage 数据流水线、CIDB 表结构、find_tests.py 六阶段选择算法与评分公式、本地运行方式、已知局限与质量度量,并对照 find_tests.py、export_coverage.py、coverage.cpp、CoverageCollection.cpp 等源码给出实现级印证。读完本文,你将理解这套系统如何从“每个测试跑到过哪些代码行”推导出“哪个测试最可能挡住本次回归”,并能本地复跑该选择器、核查覆盖率数据的时效性。


一、系统定位与总体架构

1.1 它解决什么问题

ClickHouse 的 PR 往往只改动src/programs/等目录中的若干 C++ 文件,而回归测试全量执行成本极高。与其跑完整套 stateless 测试,不如利用历史运行中积累的逐测试覆盖率反查:如果某个测试曾经覆盖过本次改动的源码行,那么它极有可能在本次改动后回归。

这一选择逻辑全部集中在Targeting类(find_tests.py 中定义),由get_all_relevant_tests_with_info()作为统一入口输出最终的关联测试清单。

1.2 两条关键前提

从源码结构与文档可以提炼出两个核心设计前提:

  1. 覆盖数据必须按“单个测试”粒度收集。默认的 LLVM 覆盖率计数是进程级累加的,只有做到“跑一个测试 → 记录 → 清零”,才能回答“这个测试覆盖了哪些行”。为此 CI 在服务端逐测试执行SYSTEM SET COVERAGE TEST 'test_name'(测试前)与SYSTEM SET COVERAGE TEST ''(测试后冲洗并重置计数器),见 tests/clickhouse-test 与 CoverageCollection.cpp 中的system.coverage_log/system.coverage_indirect_calls写入逻辑。
  2. 覆盖行与 diff 行必须可比对。源码构建时使用-ffile-prefix-map=/ClickHouse=.,CIDB 中路径统一存为./src/...;find_tests.py 把 diff 中的路径规整后再补回./前缀(见find_tests.py_strip_path_prefix/路径归一化逻辑),从而保证 join 正确。

1.3 数据流全景

文档给出的端到端数据流如下,夜间的 NightlyCoverage 任务是数据源头:

NightlyCoverage CI job(UTC 02:13 每日运行) └─ 构建 amd_per_test_coverage 二进制 (WITH_COVERAGE=ON -DWITH_COVERAGE_DEPTH=ON -finstrument-functions-after-inlining) └─ 运行 stateless 测试并携带 --long(clickhouse-test --collect-per-test-coverage) ├─ SYSTEM SET COVERAGE TEST 'test_name' (每个测试前) └─ SYSTEM SET COVERAGE TEST '' (测试后:flush + 重置计数器) └─ export_coverage.py(读取本地服务器表,写入 CIDB) ├─ system.coverage_log → checks_coverage_lines └─ system.coverage_indirect_calls → checks_coverage_indirect_calls

这一流程可通过对源码的逐一印证得到确认:

  • 工作流定义在 nightly_coverage.py,其中cron_schedules=["13 2 * * *"](UTC 02:13),使用的构建任务是coverage_build_jobs[1],即带WITH_COVERAGE + depth instrumentationBuild (amd_llvm_coverage_per_test)
  • 逐测试覆盖收集开关为--collect-per-test-coverage(clickhouse-test 命令行参数,见 tests/clickhouse-test),脚本会校验“启用收集时二进制必须以WITH_COVERAGE_DEPTH=ON构建”,不满足则给出明确报错。
  • 端到端导出由 export_coverage.py 完成:它通过remoteSecure(...)INSERT INTO FUNCTION的方式把本地system.coverage_logsystem.coverage_indirect_calls分别灌入远端 CIDB 的default.checks_coverage_linesdefault.checks_coverage_indirect_calls表。

值得注意:长测试(--long)已被纳入覆盖收集(夜间运行不再传--no-long),因此00900_long_parquet02340_parts_refcnt_mergetree这类长测试也会出现在覆盖率数据中。


二、CIDB:覆盖率数据库的表结构与访问方式

CIDB 是一个 ClickHouse 集群上的库,以只读play用户对外暴露查询。文档列出的三张核心表如下:

内容用途
checks_coverage_lines每个测试的文件/行区间覆盖——主表find_tests 直接查询
checks_coverage_indirect_calls每个测试的虚函数/函数指针被调方偏移(callee offsets)Pass 3 间接调用协同
checks_coverage_inverted旧的符号→测试倒排索引find_tests 不再使用

checks_coverage_lines的建表语义(文档 + 代码交叉印证):

file LowCardinality(String), line_start UInt32, line_end UInt32, check_start_time DateTime('UTC'), check_name LowCardinality(String), test_name LowCardinality(String), min_depth UInt8, branch_flag UInt8 -- ORDER BY (check_start_time, file, line_start, check_name, test_name) -- PARTITION BY toYYYYMM(check_start_time) -- Indexes: bloom_filter on file, minmax on line_start

字段要点:

  • min_depth:该覆盖区间内最浅的调用深度(255 表示深度未跟踪,按“深调用”处理,见评分章节);
  • branch_flag:覆盖类型标记;
  • file列带 bloom_filter、line_start带 minmax 索引,服务于“按文件+行号快速命中区间”的查询形态;
  • 按月分区便于按保留窗口裁剪。

访问 CIDB 的示例

from ci.praktika.cidb import CIDB from ci.praktika.settings import Settings cidb = CIDB(url=Settings.CI_DB_READ_URL, user="play", passwd="") result = cidb.query("SELECT count() FROM checks_coverage_lines WHERE ...", log_level="")

注意源码层面的一个细节:Targeting._ci_db()在 CI 生产环境中使用的是特权 CI 账号(从 secretSECRET_CI_DB_CONNECTION读取连接串,见 find_tests.py),因为play用户有限流与行数限制,仅适合生成供人类点击的统计链接。本地调试与文档中的“freshness check”才直接使用play


三、find_tests.py 主流程:三层来源的合并

get_all_relevant_tests_with_info()把三类来源去重后按固定优先级并入结果列表(顺序即优先级):

get_all_relevant_tests_with_info() 1. get_changed_tests() — PR diff 中直接改动的测试文件(最高优先级) 2. get_previously_failed_tests() — 该 PR 在 CI 中近期失败过的测试 3. get_most_relevant_tests() — 基于覆盖率的六阶段选择(详见下一节)

几点由源码确认的语义:

  • changed/new tests:PR 自身新增或修改的测试文件一定入选。处理逻辑见get_changed_or_new_tests_with_info():它把tests/queries/0_stateless/下改动文件映射为测试名,还会识别.reference.tsv等“支撑文件”并回溯到同名测试源(例如.reference.j2更新会命中00172_hits_joins.sql.j2),同时避免把孤立的纯数据文件(如02995_settings_26_4_1.tsv)误判成测试。
  • harness smoke tests:当 PR 改动的是测试框架本身(harness),例如 pull_request.yml、functional_tests.py、find_tests.py、clickhouse-test 等_STATELESS_HARNESS_PATHS中的文件时,会注入STATELESS_HARNESS_SMOKE_TESTS00001_select_1.01109_exchange_tables.)等兜底测试,避免选择结果为空。
  • coverage 阶段只对 stateless job 生效:代码中显式注释“TODO: Add coverage support for Integration tests”,即 integration 相关任务当前仅有 changed/previously-failed 两层来源,且 coverage 阶段异常会被捕获并以 best-effort 降级处理。

3.1 曾失败测试(Previously-failed)的召回逻辑

get_previously_failed_tests()对 CIDB 的checks表执行如下形态的查询(文档提供,与 find_tests.py 内嵌 SQL 一致):

SELECT test_name FROM checks WHERE pull_request_number = {PR_NUMBER} AND check_name LIKE '{JOB_TYPE}%' AND check_status = 'failure' AND test_status = 'FAIL' AND check_start_time >= now() - interval 30 day GROUP BY test_name ORDER BY count() * exp(-dateDiff('day', max(check_start_time), now()) / 7.) DESC LIMIT 100

特征:

  • 近因加权失败次数降序排序,权重使用 7 天半衰期的指数衰减(exp(-days/7));
  • 30 天时间窗覆盖 PR 的完整开发周期;
  • 上限 100 个测试;
  • 支持的 job 类型模式:Stateless(测试名形如^[0-9]{5}_)与 Integration(^test_)。

四、覆盖率选择算法:六个 pass + 评分 + 分层

这是整个系统的核心。get_most_relevant_tests()首先取得 PR 的 changed lines 与 hunk 边界,再对每个 changed line 汇总候选测试并打分排序。

4.1 Pass 概述(以文档骨架为准,数值以当前仓库源码为准)

Pass名称机制权重(源码当前值)
1直接行覆盖get_tests_by_changed_linesgh pr diff得到(file, line_no),过滤到src/programs/utils/base/COVERAGE_TRACKED_PREFIXES),查询checks_coverage_lines精确覆盖这些行的测试;跳过被 >MAX_TESTS_PER_LINE个测试覆盖的区间PASS_WEIGHT_DIRECT = 1.0
1bHunk 上下文同一 hunk 内紧邻改动行的上下文行;权重低于直接命中但高于间接/同级PASS_WEIGHT_HUNK_CONTEXT = 0.50
2同级目录_query_sibling_dir_tests对改动文件名做 CamelCase 拆分、去常见词得到领域关键词(如CHColumnToArrowColumn.cpp → ["Arrow"]MergeTreeIndexConditionText.cpp → ["Index","Text"]),找覆盖同目录“兄弟文件”的测试,以及覆盖这些兄弟文件的其他测试;赋予合成宽度SIBLING_DIR_WIDTH = 3000使排名低于直接命中PASS_WEIGHT_SIBLING = 0.25
3间接调用被调方协同_query_indirect_call_testschecks_coverage_indirect_callscallee_offset自连接,找与 primary 测试共享虚函数/函数指针被调方的测试;自适应 Jaccard 阈值max(1, 70 - (200 - min_seed_rc) × 0.5)%(特定文件低至 15%,宽文件高达 70%);自适应上限max(50, min(200, 200 × min_seed_rc / 40));在 ≥300 个测试中出现的被调方视为无处不在(日志、malloc)而排除PASS_WEIGHT_INDIRECT = 0.50
4Broad-tier2通过很宽区间(rc 2001–8000)覆盖改动文件的测试;按cov_regions × files_covered排序(覆盖改动面更多的优先)PASS_WEIGHT_BROAD2 = 0.40
5稀疏文件扩展当改动文件直接命中极少(最大 rc ≤ 8)时触发,取该文件全部窄区间(rc ≤MAX_TESTS_PER_LINE)为间接种子补充;SPARSE_FILE_WIDTH = 600PASS_WEIGHT_SPARSE_FILE = 0.30
兜底关键词匹配覆盖率结果极少/为空时,用改动文件领域关键词匹配测试文件名,取关键词特异性 Top-30PASS_WEIGHT_KEYWORD = 0.20

4.2 对几处源码关键差异的说明

对照 find_tests.py 中常量区(约 520–580 行),文档所列数值部分已过时,本文以仓库当前代码为准

  • MAX_TESTS_PER_LINE当前为150(注释举例:SignalHandlers.cppContext.cppSettings.cpp这类被几乎所有测试触碰的基础设施文件,改动行没有诊断价值,必须跳过以免淹没primary_tests并触发 HTTP 表单过长错误);
  • 直接查询的 server 端 HAVING 上界BROAD_REGION_HARD_CAP当前为3000;真正丢弃全知区间的上界VERY_BROAD_REGION_CAP8000(rc 落在 3000–8000 区间属于 Pass 4 的 broad-tier2,>8000 被彻底排除);
  • 该文件顶部还有一批针对极端情况的常量,例如SHARED_REGISTRY_FILES集合:ProfileEvents.cpp/hCurrentMetrics.cpp/hErrorCodes.cpp/hSettingsChanges.cppSettings.cppSettingsChangesHistory.*这类“共享注册表文件”的改动几乎总是新增枚举项,任何测试都会覆盖,因此从 coverage、sibling、indirect、keyword 各阶段整体跳过,真实信号寄托于同一 PR 改动的其他文件。

4.3 间接调用(indirect-call)收集的实现细节

文档专门辟出一节讲 LLVM value profiling 的坑,源码 coverage.cpp 与之完全对应:

  • 构建期开启-enable-value-profiling=true,LLVM 在每次虚调用/函数指针调用点把运行时被调方地址记录到ValueProfNode链表中(源码中定义了一个与 compiler-rt 布局一致的结构体ValueProfNode,含valuecountnext字段);
  • 关键坑__llvm_profile_reset_counters()并不会重置这些节点的计数。若不处理,每个测试都会累积之前所有测试的间接调用;
  • 修复coverage.cpp在读取每个ValueProfNode后把node->count = 0,保证每个测试从零开始;
  • callee_offset = callee_address - load_base对同一构建的二进制在 ASLR 重启之间保持稳定,从而可作为 join 键。

对应的 CIDB 自连接查询形态(节选):

SELECT DISTINCT ic2.test_name FROM checks_coverage_indirect_calls ic1 JOIN checks_coverage_indirect_calls ic2 ON ic1.callee_offset = ic2.callee_offset WHERE ic1.test_name IN ({primary_tests}) AND ic2.test_name NOT IN ({primary_tests}) AND (ic2.test_name, ic1.callee_offset) IN ( SELECT test_name, callee_offset FROM checks_coverage_indirect_calls GROUP BY test_name, callee_offset HAVING uniqExact(test_name) < 300 -- 排除无处不在的被调方 ) AND count(DISTINCT ic1.callee_offset) * 100.0 / ic2_tot.tot_callees >= {JACCARD_MIN_PCT} LIMIT {INDIRECT_LIMIT}

4.4 评分公式与分层排序

每个测试的分数是“所有命中的改动行”上的累加:

score(test) = Σ(所有匹配到的改动行上)pass_weight / (region_width × region_test_count)

信号语义(源码注释中表述为 four signals):

  • pass_weight:按 pass 来源乘折扣,保证即使最弱的直接命中也能压过最强的间接/兄弟命中(1.0 > 0.5 = 0.5 > 0.40 > 0.30 > 0.25 > 0.20);
  • region_width:覆盖区间越窄越精确,取倒数加权;
  • region_test_count:覆盖该区间的测试越少越特异,取倒数加权;
  • min_depth:测试触达该路径的最浅调用深度——浅意味着该测试直接走通了这条路径。

排序前先套用**分层(tier)**规则:

条件
A(最佳)窄区间(width ≤ NARROW_REGION_MAX_LINES = 40)且浅调用(depth ≤ DIRECT_CALL_MAX_DEPTH
B窄区间但深调用链
C仅宽区间命中

每层内部按width_score = Σ 1/region_width排序(奖励通过窄区间覆盖更多改动行的测试)。其中 255 = 深度未跟踪,按深调用(B 层)处理。

输出过滤

  • MIN_SCORE = 1e-8—— 绝对地板;
  • MAX_SCORE_RATIO = 3000—— 丢弃分数低于最高直接命中 1/3000 的测试;effective_min = min(1e-6, max(1e-8, top_score / 3000))
  • MAX_OUTPUT_TESTS = 300—— 最终输出硬上限。

关键词补充 pass 的细节:即使某个 C++ 文件已有直接覆盖率命中,仍会对它运行补充关键词匹配(例如CHColumnToArrowColumn.cpp的改动会命中01273_arrow.sh这类通过高层调用链回归同一领域、行覆盖抓不到的宽回归测试),并注入_keyword_guarantee保证其在排序后仍留在输出中。这些测试因PASS_WEIGHT_KEYWORD最低,只排在所有覆盖率命中之后。


五、已知局限(文档)

设计者明确记录了下述退化场景,理解它们有助于判断选择结果的可靠边界:

改动类型结果原因
声明处的constexpr/static const0 个测试编译期常量,无运行时计数器
超宽基础设施文件(IMergeTreeDataPartContext被截断rc > 8000 被排除;MAX_OUTPUT=300会裁掉大量候选
小众 C++(LDAP、CLI、crash handlers 等)0 个或仅关键词命中stateless 套件根本覆盖不到这些文件
PR 年龄 > 30 天质量下降CIDB 保留窗口限制
长测试已覆盖逐测试覆盖运行已去掉--no-long
同 PR 新增测试正确检出get_changed_tests()直接拾取(此时尚未进入 CIDB)

另外,共享注册表文件(ProfileEvents/CurrentMetrics/ErrorCodes/Settings 系列)因几乎被每个测试触碰,在四个 pass 中都被整体跳过。


六、质量度量与漏检归因(截至 2026-03-30,100 个 PR)

针对 2026-03-25 至 2026-03-28 合并、含src/改动的 100 个 PR,与旧版基于 DWARF 符号的目标运行对照,结果如下:

指标
相对旧算法的平均召回57.8%
中位召回65.2%
加权召回49.2%
完美召回(100%)10/28(有 targeted 数据的 PR)
小 PR(旧算法 ≤10 个测试)平均 68.9%
大 PR(旧算法 >10 个测试)平均 45.0%

漏检根因分解

  • ~40% 为 previously-failed 类测试(只在 PR 活跃开发期出现,合并后不可复现);
  • ~25% 来自 rc > 8000 的基础设施文件(被VERY_BROAD_REGION_CAP排除);
  • ~15% 被MAX_OUTPUT=300上限裁掉(已排序但被截断);
  • ~14% 实为旧算法 DWARF 假阳性(旧算法经内联链找到了错误测试,新算法跳过是正确行为);
  • ~2% 为同 PR 新增测试文件(尚未入库);
  • ~4% 为其他真实漏检。

同时文档强调:81% 的“漏检测试”(333/411 已核对对)确实存在于checks_coverage_lines中改动文件的记录里——新算法对宽文件找到的是“不同但同样有效的子集”,并非完全丢失。对比方法学细节参见 compare_find_tests_algos.md。


七、本地运行 find_tests.py

在仓库根目录下执行(需要ci/可被PYTHONPATH找到并具备 CIDB 访问权限):

cd /path/to/ClickHouse # 为某个 PR 获取关联测试 PYTHONPATH=./ci:. python3 ci/jobs/scripts/find_tests.py <PR_NUMBER> # 跳过 previously-failed 阶段(更快,仅覆盖率) PYTHONPATH=./ci:. python3 ci/jobs/scripts/find_tests.py <PR_NUMBER> --coverage-only # 使用预拉取的 diff(规避 GitHub API 限流) gh pr diff <PR_NUMBER> > /tmp/pr.diff PYTHONPATH=./ci:. python3 ci/jobs/scripts/find_tests.py <PR_NUMBER> --diff-file /tmp/pr.diff

命令行入口定义于 find_tests.py 的__main__块:提供--coverage-only(只走get_most_relevant_tests(),省一次 GitHub API 调用,适合评测)与--diff-file(注入_diff_text,让get_changed_lines_from_diff与覆盖率阶段都从本地 diff 读取)。运行形态是“本地模式”,job 名固定为Stateless

典型输出示例:

[find_tests] sibling-dir query: 0.15s, response=175 bytes [find_tests] sibling-dir: 4 additional test candidates [find_tests] indirect-call query: 1.88s, 200 additional test candidates (top jaccard=100%) [find_tests] done in 2.66s: 6/6 lines matched, 275 unique tests selected All selected tests (275): 00900_long_parquet.sh ... Found 275 relevant tests

八、NightlyCoverage 工作流与手动触发

工作流定义在 nightly_coverage.py,cron 为13 2 * * *(UTC 每日 02:13)在 master 上运行,使用coverage_build_jobs[1]=Build (amd_llvm_coverage_per_test),构建参数为WITH_COVERAGE=ON -DWITH_COVERAGE_DEPTH=ON -finstrument-functions-after-inlining长测试已纳入覆盖收集functional_tests.py中已从逐测试覆盖运行移除--no-long),因此00900_long_parquet02340_parts_refcnt_mergetree等长测试会出现在checks_coverage_lines

在分支上手动触发:

gh workflow run NightlyCoverage --repo ClickHouse/ClickHouse --ref <branch> gh run list --repo ClickHouse/ClickHouse --workflow=NightlyCoverage --limit 5

九、运行时与导出链路的关键文件速查

文件职责
find_tests.py主算法,Targeting
functional_tests.py运行测试;调用get_all_relevant_tests_with_info()
export_coverage.py本地覆盖表导出到 CIDB
nightly_coverage.pyNightlyCoverage 工作流定义
coverage.cpp运行时:读取 LLVM profile 数据、按测试重置、收集间接调用
CoverageCollection.cpp服务端:计数器映射到源码区间并写入 system 表
LLVMCoverageMapping.cpp启动时解析 ELF 的__llvm_covmap/__llvm_covfun
clickhouse-test创建system.coverage_logsystem.coverage_indirect_calls表,封装逐测试采集开关

服务端写入细节值得展开:CoverageCollection.cpp在收到SYSTEM SET COVERAGE TEST '<name>'后,把当前计数器的区间结果以INSERT INTO system.coverage_log (time, test_name, file, line_start, line_end, min_depth, branch_flag) VALUES ...落库,并把间接调用观测写入system.coverage_indirect_calls;插入显式关闭async_insertasync_insert=0),保证每个测试的覆盖记录可被立即查询。


十、CIDB 数据新鲜度自查

选择器的质量直接取决于夜间数据是否及时入库。可用如下脚本核查(play用户,仅限人工查阅):

PYTHONPATH=./ci:. python3 - << 'EOF' from ci.praktika.cidb import CIDB from ci.praktika.settings import Settings cidb = CIDB(url=Settings.CI_DB_READ_URL, user="play", passwd="") # 检查各 check 的新鲜度与长测试覆盖 print(cidb.query(""" SELECT check_name, max(toDate(check_start_time)) AS last_run, uniqExact(test_name) AS tests, count() AS rows FROM checks_coverage_lines WHERE check_start_time > now() - interval 7 days GROUP BY check_name ORDER BY last_run DESC LIMIT 10 """, log_level="")) # 验证某个长测试确实覆盖了指定文件 print(cidb.query(""" SELECT test_name, count() AS regions FROM checks_coverage_lines WHERE file = './src/Storages/MergeTree/MergeTreeRangeReader.cpp' AND check_start_time > now() - interval 7 day AND check_name LIKE 'Stateless%' AND test_name LIKE '02340%' GROUP BY test_name """, log_level="")) EOF

结语:从“跑全量”到“跑对量”

ClickHouse 的关联测试选择器把“哪个测试保护了这行代码”这一历史事实沉淀为可查询的覆盖数据,再以 diff 为输入、以“直接行覆盖 → hunk 上下文 → 兄弟目录 → 间接调用协同 → 宽区间 → 稀疏文件 → 关键词兜底”的多级策略逼近最优子集,最后通过pass_weight/(width×rc)评分与 A/B/C 分层排序输出上限 300 个的定向测试清单。它并非完美——超宽基础设施文件、编译期改动与小众模块仍会退化——但结合质量度量的诚实归因、30 天窗口内此前失败的强召回,以及逐测试间接调用计数的正确重置,构成了 ClickHouse CI 面向海量回归测试的一种高性价比工程实践。若要深入调试或扩展该算法,入口都在ci/jobs/scripts/find_tests.pyTargeting类与ci/workflows/nightly_coverage.py的数据流水线中。

【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 18:32:02

终端Agent实战:opencode安装配置、Skills与Memory使用指南

最近在终端里写代码的流程又有了变化。以前是IDE里开一个对话面板&#xff0c;让AI帮我补全函数、解释报错&#xff0c;然后我手动复制粘贴代码。现在主流玩法变成了终端Agent&#xff0c;AI不再只是“回答问题的助手”&#xff0c;而是真的能自己跑测试、读文件、改代码、提交…

作者头像 李华
网站建设 2026/9/8 18:29:09

手把手教你学Simulink--基于虚拟阻抗的并网逆变器输出特性控制

### 手把手教你学Simulink--基于虚拟阻抗的并网逆变器输出特性控制 #### 摘要 本研究旨在探讨如何借助Simulink实现基于虚拟阻抗的并网逆变器输出特性控制。通过理论分析,深入剖析虚拟阻抗对并网逆变器输出特性的影响机制;借助Simulink搭建详细模型,包括主电路、虚拟阻抗模…

作者头像 李华
网站建设 2026/9/8 18:27:41

眼睛开闭检测数据集实战:VOC转YOLO格式与YOLOv8训练全流程

简介&#xff1a;面向眼睛睁开闭合检测任务的数据集包&#xff0c;为计算机视觉目标检测训练提供标准化数据基础&#xff0c;适用于疲劳驾驶预警、注意力分析等场景。数据集包含12884张人脸/眼部图片&#xff0c;对应VOC格式XML标注与YOLO格式TXT标签各12884份&#xff0c;标注…

作者头像 李华
网站建设 2026/9/8 18:25:46

MCP如何让Tool与LLM真正解耦?从零搭建MCP Server的避坑实录

你如果最近在折腾 Agent&#xff0c;会发现 MCP、Tool、LLM 解耦这些词频繁出现在同一条时间线里。我踩了一周坑后确认&#xff1a;MCP 真正擅长的是把工具调用做成跨进程、跨语言的标准通信&#xff0c;而不是给 LLM 塞一份更大的 JSON。很多人第一次看到 MCP 的反应是&#x…

作者头像 李华