Chunk 按函数切会丢失跨过程流;按文件切会噪声过大,你如何建模层级?

第121题:Chunk 按函数切会丢失跨过程流;按文件切会噪声过大,你如何建模层级?

在这里插入图片描述

1. 核心回答

我会把 Retrieval GranularityGeneration Granularity 分开设计。

核心原则是:

小粒度 Chunk 负责精准召回,结构关系负责跨过程扩展,较大的上下文单元负责最终推理。

具体可以建成:

Repository
└── Module / Package
    └── File
        └── Class / Struct
            └── Function / Method
                └── Statement / AST Subtree

同时建立横向结构边:

Call Graph
Data Flow
Import
Inheritance
Reference / Definition

因此最终检索流程是:

先召回 Function / Symbol
↓
沿 Call Graph / DFG 等结构关系扩展
↓
必要时提升到 Parent File / Class
↓
在固定 Token Budget 内重排
↓
组装最终 Context

这样能够同时控制两个问题:

  • Function Chunk 太小导致跨过程证据丢失;
  • File Chunk 太大导致无关代码挤占上下文。

2. 为什么固定 Token Chunk 不适合代码

普通文本 RAG 经常使用:

每 512 Token 切一块

但代码具有明确语法边界。

例如:

def check_permission(...):
    ...

如果正好从中间切开:

Chunk A:
函数签名 + 前半部分

Chunk B:
后半部分

就可能丢失:

  • 函数签名;
  • 参数含义;
  • Return;
  • Control Flow;
  • 调用关系。

安全代码分析中问题更明显。

例如一条完整漏洞链是:

HTTP Input
↓
Controller
↓
Service
↓
DAO
↓
SQL Execution

固定 Token Chunk 很容易把这条链拆成多个互不关联的文本块。

所以代码切分首先应该尊重:

Symbol / AST Boundary


3. 为什么只按 Function 切也不够

函数是一个很好的基本 Retrieval Unit。

但很多漏洞具有跨过程性质。

例如:

def handler(request):
    name = request.args["name"]
    query_user(name)

另一个文件:

def query_user(name):
    sql = "SELECT * FROM users WHERE name='" + name + "'"
    execute(sql)

如果只看:

query_user()

可以看到:

SQL Sink

但可能不知道:

name

是否真的由攻击者控制。

如果只看:

handler()

又看不到最终危险 Sink。

完整证据是:

Source
request.args["name"]

↓ interprocedural propagation

query_user(name)

↓

Sink
execute(sql)

所以 Function Chunk 适合作为召回入口,但跨过程安全分析还需要结构扩展。


4. 为什么整个 File 作为 Chunk 又太大

假设一个文件有:

2000 LOC
30 个函数
10 个类
大量常量和 Helper

当前 Query 只关心:

parse_request()

把整个文件放入 Context 会产生:

Relevant Information≪Total Context Relevant\ Information \ll Total\ Context Relevant InformationTotal Context

结果可能是:

  • Embedding 被大量无关代码稀释;
  • Retriever 排名下降;
  • LLM 注意力被无关代码占用;
  • Token 成本提高;
  • 真正漏洞证据被挤出 Context Window。

因此文件适合作为:

Parent Context

通常不适合作为默认最小检索单元。


5. 我会设计三层核心结构

5.1 Leaf Layer:Symbol Chunk

基本 Chunk 以代码符号为单位:

Function
Method
Constructor
Class
Struct
Interface
Macro
Global Definition

每一个 Chunk 保存:

chunk_id
repository_id
file_id
symbol_id
symbol_type
qualified_name
signature
start_line
end_line
language
content

例如:

symbol:
UserDAO.query_user

file:
dao/user.py

type:
function

Leaf Chunk 主要用于:

BM25 / Dense Retrieval


5.2 Structural Layer:Relationship Graph

每个 Symbol 之间建立结构关系。

例如:

CALLS
CALLED_BY
IMPORTS
DEFINES
REFERENCES
INHERITS
OVERRIDES
DATA_FLOWS_TO

形成:

G=(V,E) G=(V,E) G=(V,E)

其中:

V=Code Symbols V=\text{Code Symbols} V=Code Symbols

E=Structural Relations E=\text{Structural Relations} E=Structural Relations

例如:

handler()
    │ CALLS
    ▼
validate()
    │ CALLS
    ▼
query_user()
    │ DATA_FLOW
    ▼
execute()

这一层负责恢复函数 Chunk 切分以后丢失的跨过程关系。


5.3 Parent Layer:File / Class / Module Context

再建立 Parent Relationship:

Function
↓
Class
↓
File
↓
Module
↓
Repository

例如:

chunk_id = func_123
parent_file = file_17
parent_class = UserDAO
module = database

当一个 Leaf Chunk 缺乏局部定义时,可以向父级扩展。

例如函数中出现:

self.config.db_url

可以进一步补入:

Class Fields
Relevant Imports
Config Definition

而不需要把整个仓库全部加入 Context。


6. 一个完整的层级索引可以这样画

Repository
│
├── File A
│   ├── Function A1
│   ├── Function A2
│   └── Function A3
│
├── File B
│   ├── Function B1
│   └── Function B2
│
└── File C
    └── Function C1


Structural Edges:

A1 ──CALLS──────► B2
B2 ──CALLS──────► C1
A2 ──DATAFLOW───► B1
B1 ──INHERITS───► C1

于是系统同时拥有:

Tree Hierarchy
+
Graph Relations

Tree 表达代码组织关系。

Graph 表达程序语义关系。


7. 查询时第一步仍然召回小块

假设 Query 是:

这段代码是否存在命令注入?

首先使用:

BM25
Dense Retrieval
Hybrid Retrieval

找到 Top-K Leaf Chunk:

func_A
func_B
func_C
...

这个阶段使用小 Chunk 的好处是匹配更加精准。

例如:

subprocess.Popen
os.system
exec
shell=True

这类安全相关 symbol 不会被整个大型文件中的无关内容严重稀释。


8. 第二步再做结构扩展

假设首先命中:

def run_command(cmd):
    os.system(cmd)

只看它无法确定:

cmd 是否攻击者可控

此时根据:

CALLED_BY

向上扩展:

run_command()
↑
process_request()
↑
HTTP Handler

如果存在 Data Flow Edge,则优先沿:

Source
→
Propagation
→
Sink

扩展。

最终 Context 可能从:

1 个函数

扩展成:

Source Function
+
Intermediate Function
+
Sink Function

这比直接放整个文件更加聚焦。


9. 对漏洞检测,我会优先做 Evidence-oriented Expansion

普通代码补全关注:

什么代码有助于生成下一段?

漏洞检测更关心:

什么代码能够证明或反驳漏洞成立?

因此扩展优先级可以设计成:

Data-flow neighbor
>
Direct caller/callee
>
Definition/reference
>
Class context
>
File context
>
Generic semantic neighbor

例如 Injection 类型漏洞优先寻找:

Source
Sanitizer
Sink
Propagator

生命周期问题优先寻找:

Allocation
Ownership
Release
Alias
Use

权限问题优先寻找:

Sensitive Operation
Authorization Check
Caller Identity

所以扩展策略应该与当前安全假设相关。


10. 检索分数可以同时考虑语义、结构和成本

例如对候选 Chunk ccc 定义:

$$
Score©

\alpha S_{dense}©
+
\beta S_{lexical}©
+
\gamma S_{graph}©
+
\delta S_{evidence}©

\lambda Cost©
$$

其中:

  • SdenseS_{dense}Sdense:语义相关性;
  • SlexicalS_{lexical}Slexical:BM25/identifier 精确匹配;
  • SgraphS_{graph}Sgraph:与命中节点的图距离;
  • SevidenceS_{evidence}Sevidence:是否补全当前漏洞证据;
  • CostCostCost:加入该 Chunk 消耗的 Token。

例如:

Caller 距离 = 1

可以获得较高 Graph Score。

一个完全无关但语义相似的函数即使 Dense Score 较高,也可能因为结构距离远而降低最终优先级。


11. 最终 Context Assembly 需要受 Token Budget 约束

假设模型允许使用:

B=16K B=16K B=16K

Token。

不能无限沿 Call Graph 扩展。

可以定义:

Query / Target Code:
4K

Direct Evidence:
6K

Structural Expansion:
4K

Metadata / Prompt:
2K

具体比例应通过验证集决定。

选择过程可以理解为:

max⁡∑iUtility(ci) \max \sum_i Utility(c_i) maxiUtility(ci)

满足:

∑iTokens(ci)≤B \sum_i Tokens(c_i)\le B iTokens(ci)B

即在有限上下文中选择证据价值最高的 Chunk。


12. 小块负责 Retrieval,大块负责 Comprehension

这一点是整个设计的核心。

我会显式区分两个 Context。

12.1 Retrieval Context

用于算 Embedding / BM25。

尽量紧凑:

Function Signature
Docstring
Function Body
Key Metadata

目的:

精准找到候选

12.2 Comprehension Context

候选被检索以后扩展:

Function
+
Caller
+
Callee
+
Relevant Definition
+
Data-flow Neighbor
+
Necessary Parent Context

目的:

让模型完成推理

因此同一个代码节点可以具有:

Compact Retrieval Representation

和:

Expanded Generation Representation


13. Parent File 不应该默认全部展开

如果命中:

func_A

不应立刻:

include entire file

更合理的是逐级提升。

例如:

Level 0:
func_A

Level 1:
func_A + imports + signature dependencies

Level 2:
func_A + relevant class members

Level 3:
func_A + relevant file symbols

Level 4:
whole file

只有当前证据不足且 Token Budget 允许时继续扩展。

这样可以避免:

Parent Expansion
→
重新退化成 File Chunk


14. 超长函数怎么办

Function Chunk 本身也可能超过预算。

例如:

一个函数 3000 行

此时进一步按照语法结构拆。

例如:

Function
├── Initialization
├── If Branch
├── Loop
├── Try/Catch
└── Return

可以基于:

AST Subtree
Statement Block
Basic Block

建立子 Chunk。

但每个子 Chunk 必须继续保存:

parent_function_id
function_signature
local_scope
start/end position

这样召回子 Chunk 后能够重新恢复其父函数上下文。


15. AST / Symbol 边界为什么比纯 Token 边界更合理

代码解析工具可以得到:

function_definition
class_definition
call_expression
if_statement
assignment

等结构节点。

所以可以将:

Function
Method
Class

作为稳定的语义边界。

如果节点超过 Token Budget,再递归进入:

child AST node

形成:

File
↓
Function
↓
Statement Block
↓
Expression

的多粒度结构。

这比:

每 512 Token 截断

更容易保持程序结构。


16. 跨过程 Source-Sink 怎么组装

假设已经识别:

Source = f1
Sink = f4

并存在:

f1
→
f2
→
f3
→
f4

我不会机械地加入:

四个函数的所有代码

可以优先抽取:

f1:
Source 附近语句

f2:
参数接收 + 关键传播

f3:
Sanitizer / Transformation

f4:
Sink 附近语句

然后保留调用关系:

f1 → f2 → f3 → f4

最终 Context 更接近:

Evidence Path

而不是若干彼此独立的文件片段。


17. 一个安全场景例子

假设仓库中存在:

# controller.py

def handle(req):
    username = req.args["name"]
    service.find_user(username)
# service.py

def find_user(name):
    dao.query(name)
# dao.py

def query(name):
    sql = "select * from users where name='" + name + "'"
    db.execute(sql)

Function Retrieval 首先可能命中:

dao.query()

因为它与 SQL Injection 最相关。

随后 Structural Expansion 查询:

caller(query)

得到:

service.find_user()

继续得到:

controller.handle()

最终恢复:

request.args
↓
find_user
↓
query
↓
db.execute

完整 Source-Sink Evidence 才进入模型。


18. 怎样验证这种层级方案确实有效

不能只展示 Architecture。

至少比较:

A. Fixed Token Chunk
B. Function Chunk
C. File Chunk
D. Function + Parent Expansion
E. Function + Call Graph Expansion
F. Function + Data-flow / Structural Expansion
G. Hierarchical Full Method

所有实验固定:

  • Dataset;
  • Split;
  • Retriever;
  • Generator;
  • Token Budget;
  • Evaluation Metric。

这样才能隔离 Chunking / Expansion 本身的贡献。


19. 检索层应该测什么

首先测普通 Retrieval 指标:

Recall@K Recall@K Recall@K

MRR MRR MRR

NDCG@K NDCG@K NDCG@K

但安全任务还应该测:

19.1 Evidence Recall

Ground Truth 需要的证据节点有多少被召回。

例如:

$$
EvidenceRecall

\frac{
|\text{Retrieved Evidence}\cap\text{Required Evidence}|
}{
|\text{Required Evidence}|
}
$$

19.2 Path Coverage

对于 Source-Sink 类型漏洞:

Source
→
Intermediate
→
Sink

是否完整覆盖。

可以定义:

$$
PathCoverage

\frac{
\text{被 Context 覆盖的关键 Path Node}
}{
\text{Ground Truth Path Node}
}
$$

这样能够直接判断:

函数级 Chunk 是否真的因为缺少跨过程证据而失败。


20. 端到端还必须继续测

最终还要比较:

  • Vulnerability Recall;
  • Precision;
  • F1;
  • Localization Accuracy;
  • Evidence Correctness;
  • False Positive;
  • Context Tokens;
  • Retrieval Latency;
  • Generation Latency;
  • Cost。

例如可能出现:

Hierarchical Retrieval
Recall@20 ↑

但:

End-to-End F1 ≈

这说明 Retriever 已经找到证据,Generator 没有有效利用。

此时继续扩大 Chunk 已经没有意义。


21. Oracle Context 可以定位瓶颈

再加入:

Oracle Context

人工给模型完整 Ground Truth Evidence。

得到:

Moracle M_{oracle} Moracle

与真实 Retrieval:

Mretrieval M_{retrieval} Mretrieval

比较。

如果:

Moracle≫Mretrieval M_{oracle}\gg M_{retrieval} MoracleMretrieval

说明主要瓶颈位于:

Retrieval / Context Assembly

如果:

Moracle≈Mretrieval M_{oracle}\approx M_{retrieval} MoracleMretrieval

但整体效果仍低,瓶颈更可能在:

Generator / Reasoning

这样能够防止把所有问题都归因于 Chunk Size。


22. Chunk 大小应该通过消融决定

这里没有一个跨任务固定的最佳值。

Chunk 太小:

语义不完整
跨过程关系丢失
调用背景不足

Chunk 太大:

噪声增加
Embedding 被稀释
Token Cost 增加
有用证据被挤压

所以应该画:

Performance=f(ChunkSize) Performance=f(ChunkSize) Performance=f(ChunkSize)

以及:

Cost=g(ChunkSize) Cost=g(ChunkSize) Cost=g(ChunkSize)

最后在:

Retrieval Quality
End-to-End Quality
Latency
Token Cost

之间选择 Pareto 合理点。


23. 结构本身也存在失败边界

层级检索并不能保证完整恢复程序语义。

例如:

  • Dynamic Dispatch;
  • Reflection;
  • Function Pointer;
  • Runtime Dependency Injection;
  • Native Call;
  • Missing Library;
  • Incomplete Call Graph;
  • Incomplete DFG。

都可能让结构边缺失或包含假边。

因此 Graph Edge 应保存:

edge_type
analysis_method
confidence

并允许:

Lexical / Dense Retrieval

作为补充通道。

不能把静态 Call Graph 或 DFG 直接当作完整 Ground Truth。


24. 面试时可以压缩成下面这段

我会把检索粒度和生成粒度分开。

底层先按照 Function、Method、Class 等 Symbol/AST Boundary 建小 Chunk,因为小 Chunk 更适合 BM25 和 Dense Retrieval;每个 Chunk 同时保留 parent file、class、module,以及 caller/callee、import、inheritance、data-flow 等结构边。

Query 到来后,先召回 Function-level Leaf Chunk,然后根据任务进行结构扩展。对于漏洞检测,我会优先沿 source-sink、caller/callee 和 definition/reference 扩展,只把与当前安全证据相关的邻居加入 Context;如果仍缺少定义,再逐级向 Class 或 File Parent 提升。这样小 Chunk 负责精准匹配,扩展后的 Context 负责跨过程推理。

整个扩展过程受固定 Token Budget 控制。超长函数则继续按 AST Subtree 或 Statement Block 切分,但保留 parent function 信息。

实验上我会比较 fixed-token、function、file、function+parent、function+graph 和完整 hierarchical retrieval,在相同 Generator 和 Token Budget 下同时报告 Recall@K、source-sink path coverage、最终漏洞检测 F1、Context Token 和 Latency。

如果 Oracle Context 明显优于真实检索,说明问题主要在 Retrieval/Context Assembly;如果 Oracle 也没有提高,则需要检查 Generator。这样能够真正判断层级 Chunking 是否解决了跨过程证据缺失,而不只是增加了更多上下文。


25. 当前资料能够支持到什么程度

源文件明确支持以下设计:

  1. 固定 Token Chunk 可能切断函数、调用链和 Source-Sink 证据;
  2. 代码优先按照 Symbol / AST Boundary 建立小块;
  3. 保留 Parent File、Call Graph、Data Flow 和 Version Metadata;
  4. 小块用于匹配;
  5. 大块或邻接扩展用于最终生成上下文;
  6. Chunk 过小会缺语义;
  7. Chunk 过大会增加噪声和成本;
  8. 必须同时通过 Retrieval Coverage 和 End-to-End Result 做消融。

当前材料没有提供这位候选人真实系统中的:

具体 Chunk Token 数
AST Parser
Graph Construction Algorithm
Expansion Hop
Token Budget
Recall@K
Path Coverage
最终 F1

因此正式面试中不能声称某个具体层级设计已经经过实验验证。

如果实验尚未完成,应该把:

fixed-token
function
file
hierarchical expansion
oracle context

列为待补消融。


26. 来源

  1. 源文件第121题:Symbol/AST 小块、Parent File、Call Graph、Data Flow、邻接扩展以及 Chunk Size 消融。
  2. Tree-sitter Documentation:可以将源代码解析成带层级、节点类型和源码位置的语法树,为 Symbol/AST Boundary 切分提供基础。
  3. Zhang et al., RepoCoder: Repository-Level Code Completion Through Iterative Retrieval and Generation, EMNLP 2023:说明 repository-level 有用上下文会分散在不同文件中,并采用跨仓库片段检索增强代码模型。
  4. Cheng et al., Dataflow-Guided Retrieval Augmentation for Repository-Level Code Completion (DraCo), ACL 2024:将代码解析成实体,并通过扩展数据流关系构造 repository-specific context graph,再据此检索相关跨文件上下文。
  5. Oh & Lee, Late Code Chunking, ACL 2026:区分面向检索的 Code Retrieval Context 与检索后扩展的 Code Comprehension Context,用于降低 Chunking 导致的结构/行为语义损失。
  6. Shi et al., AIRCoder, ACL 2026:将 textual similarity、dependency existence 和 structural hierarchy 作为互补检索维度,并使用 structure-preserving chunking。
  7. GitHub CodeQL Documentation, Exploring Data Flow with Path Queries:Path Query 将漏洞数据流表示为 Source 到 Sink 的一系列传播步骤,可作为安全场景中结构化 Evidence Path 的参考。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白羊丨

开始面试题与解析

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值