///|
/// LLM 上下文组装器:把检索结果按 token 预算拼成可直接喂给大模型的提示词。
pub struct ContextBuilder {
max_tokens : Int
tokenizer : Tokenizer
} derive(ToJson, FromJson)
///|
/// A selected chunk and its citation number in the generated prompt.
pub struct ContextCitation {
number : Int
doc_id : String
score : Double
text : String
estimated_tokens : Int
} derive(ToJson)
///|
/// Prompt plus the exact chunks used to construct it.
pub struct ContextReport {
prompt : String
citations : Array[ContextCitation]
used_content_tokens : Int
estimated_prompt_tokens : Int
skipped_results : Int
} derive(ToJson)
///|
pub fn ContextBuilder::new(
max_tokens? : Int = 800,
tokenizer? : Tokenizer = Tokenizer::new(),
) -> ContextBuilder {
{ max_tokens, tokenizer, }
}
///|
/// 组装上下文:按输入顺序选取预算内的片段,并跳过放不下的结果。
pub fn ContextBuilder::build(
self : ContextBuilder,
query : String,
results : Array[SearchResult],
) -> String {
self.build_report(query, results).prompt
}
///|
/// Select chunks under the retrieved-content budget and retain citation data.
pub fn ContextBuilder::build_report(
self : ContextBuilder,
query : String,
results : Array[SearchResult],
) -> ContextReport {
let citations : Array[ContextCitation] = []
let budget = if self.max_tokens > 0 { self.max_tokens } else { 0 }
let mut used = 0
let mut skipped = 0
for result in results {
let estimated_tokens = self.tokenizer.estimate_tokens(result.text)
if budget == 0 || estimated_tokens > budget - used {
skipped = skipped + 1
continue
}
citations.push({
number: citations.length() + 1,
doc_id: result.doc_id,
score: result.score,
text: result.text,
estimated_tokens,
})
used = used + estimated_tokens
}
let sb = StringBuilder()
sb.write_string(
"请仅根据以下资料回答问题,不要编造资料中没有的内容。\n\n",
)
sb.write_string("问题:")
sb.write_string(query)
sb.write_string("\n\n资料:\n")
for citation in citations {
sb.write_string("\n[")
sb.write_string(citation.number.to_string())
sb.write_string("](来源:")
sb.write_string(citation.doc_id)
sb.write_string(")\n")
sb.write_string(citation.text)
sb.write_string("\n")
}
let prompt = sb.to_string()
{
prompt,
citations,
used_content_tokens: used,
estimated_prompt_tokens: self.tokenizer.estimate_tokens(prompt),
skipped_results: skipped,
}
}
///|
/// 每个原始文档只保留最高分分块,再按 token 预算组装上下文。
pub fn ContextBuilder::build_diverse(
self : ContextBuilder,
query : String,
results : Array[SearchResult],
max_sources? : Int = 10,
) -> String {
self.build_diverse_report(query, results, max_sources~).prompt
}
///|
/// Keep the strongest chunk per source and return its citation report.
pub fn ContextBuilder::build_diverse_report(
self : ContextBuilder,
query : String,
results : Array[SearchResult],
max_sources? : Int = 10,
) -> ContextReport {
let selected = deduplicate_by_source(results, limit=max_sources)
self.build_report(query, selected)
}