LLM数值提取-计算场景示例
之前探索了LLM长上下文和数值类有效输出的关系
https://blog.csdn.net/liliang199/article/details/159175752
这里选用 苹果公司 2023 财年 10-K 年报(约 90 页,约 70K tokens)作为测试文本。
任务包括:
1)直接数值提取:从文本中找出指定财务数据(如总营收、净利润)。
2)基于提取值的计算:如计算“研发费用占总营收的比例”。
3)结构化输出:要求模型以 JSON 格式返回结果,便于程序解析。
将通过两种方式处理长文本:
1)一次性传入,如果模型上下文窗口足够,如 128K context的模型。
2)分块 + 摘要/检索,模拟超长文本无法一次性处理的情况。
1 环境准备
假设openai、faiss等相关工具已按照,这里示例环境设置和数据获取过程。
1.1 设置环境
模拟分块检索需要向量模型,可能存在hf访问问题,所以这里先设置hf国内镜像。
同时设置api key、user_url、model_name等。
import os
os.environ['HF_ENDPOINT'] = "https://hf-mirror.com"
os.environ['OPENAI_API_KEY'] = "sk-xxxxxx"
os.environ['OPENAI_BASE_URL'] = "https://llm_provider.com/v1"
model_name = "qwen3-xxxx"
这里实际运行采用qwen3.5系列模型。
1.2 数据获取
从 SEC EDGAR 下载苹果 2023 年 10-K 文本,链接如下所示
https://www.sec.gov/Archives/edgar/data/320193/000032019323000106/aapl-20230930.htm
由于是网页数据,这里采用选中所有内容后复制,然后在本地粘贴的方式,在本地构建aapl-20230930.txt文件。
1.3 预估token量
使用 tiktoken 预估 token 数,确保不超过模型限制。
import tiktoken
def num_tokens_from_string(string: str, encoding_name: str = "cl100k_base") -> int:
encoding = tiktoken.get_encoding(encoding_name)
num_tokens = len(encoding.encode(string))
return num_tokens
with open("./aapl-20230930.txt") as f:
full_text = f.read()
tokens = num_tokens_from_string(full_text)
print(f"文档 token 数: {tokens}")
if tokens < 128000:
print("可直接使用128K上下文的模型")
else:
print("需采用分块策略")
输出示例如下
文档 token 数: 45185
可直接使用128K上下文的模型
2 基于单轮对话的提取计算
这里构造提示并调用 API,示例单轮对话的提取计算过程。
2.1 设计示例
首先设计提示词,要求模型提取指定数据并以 JSON 返回,细节如下。
from openai import OpenAI
client = OpenAI()
def ask_model(document, questions):
prompt = f"""
你是一个财务分析专家。以下是苹果公司2023财年10-K年报的部分文本。
请根据文本回答以下问题,并以JSON格式返回结果。JSON键为问题编号,值为对应的答案(数值或字符串)。
文本内容:
{document}
问题:
{questions}
请直接输出JSON,不要包含其他文字。
"""
response = client.chat.completions.create(
model=model_name, # 支持128K上下文的模型
messages=[{"role": "user", "content": prompt}],
temperature=0, # 降低随机性,提高数值准确性
max_tokens=500
)
return response.choices[0].message.content
# 截取前60000 token(若文档超长可截断,此处假设一次性传入)
if tokens > 120000:
# 简单截断(更优做法是按段落截取,保证完整性)
encoding = tiktoken.get_encoding("cl100k_base")
encoded = encoding.encode(full_text)
truncated = encoding.decode(encoded[:120000])
document = truncated
else:
document = full_text
question_list = [
"1. 2023财年总营收(Total net sales)是多少?请以百万美元为单位,只输出数字。",
"2. 2023财年研发费用(Research and Development)是多少?请以百万美元为单位,只输出数字。",
"3. 研发费用占总营收的比例是多少?请以百分比形式输出,保留两位小数,如\"15.23%\"。",
]
questions = "\n".join(question_list)
result = ask_model(document, questions)
print(result)
返回如下所示
{
"1": 383285,
"2": 29915,
"3": "7.80%"
}
2.2 解析 JSON 并验证
在获取LLM返回后,解析json并进行验证,代码细节如下。
import json
import re
def extract_json(text):
# 从模型输出中提取JSON部分(有时会夹杂额外文本)
json_pattern = r'\{.*\}' # 简单匹配,实际可用json.loads尝试
match = re.search(json_pattern, text, re.DOTALL)
if match:
return json.loads(match.group())
else:
return json.loads(text) # 如果整个输出就是JSON
try:
data = extract_json(result)
print("解析结果:", data)
# 真实值(根据实际年报)
ground_truth = {
"1": 383285, # 百万美元,约3833亿美元
"2": 29915, # 约299亿美元
"3": 7.804 # 比例 = 29915/383285 ≈ 7.81%
}
print("真实值:", ground_truth)
# 简单比较
for q, pred in data.items():
gt = ground_truth[q]
if q == "3":
# 百分比字符串转浮点比较
pred_val = float(pred.strip('%'))
print(f"Q{q}: 预测 {pred_val}% vs 真实 {gt}%,误差 {abs(pred_val-gt):.2f}%")
else:
print(f"Q{q}: 预测 {pred} vs 真实 {gt},误差 {abs(int(pred)-gt)}")
except Exception as e:
print("解析失败:", e)
print("原始输出:", result)
输出示例如下
解析结果: {'1': 383285, '2': 29915, '3': '7.80%'}
真实值: {'1': 383285, '2': 29915, '3': 7.804}
Q1: 预测 383285 vs 真实 383285,误差 0
Q2: 预测 29915 vs 真实 29915,误差 0
Q3: 预测 7.8% vs 真实 7.804%,误差 0.00%
可以在上下文长度允许的情况下,单次传入可以获得精确结果。
3 基于分块+检索的提取计算
这里假设文本远超模型上下文时,这时可采用检索增强生成RAG的思路。
将文档分块,用向量检索相关块,再将相关块送入模型。
3.1 分块与向量化
这里将文本划分为1000大小块,使用all-MiniLM-L6-v2将分块文本转向量,使用faiss管理向量。
from sentence_transformers import SentenceTransformer
import numpy as np
import faiss
# 分块函数(按段落或固定长度)
def chunk_text(text, chunk_size=500, overlap=50):
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size - overlap):
chunk = ' '.join(words[i:i+chunk_size])
chunks.append(chunk)
return chunks
chunks = chunk_text(full_text, chunk_size=1000) # 每个块约1000词
print(f"分块数量: {len(chunks)}")
# 生成向量(使用轻量模型)
model = SentenceTransformer('all-MiniLM-L6-v2')
chunk_embeddings = model.encode(chunks, show_progress_bar=True)
# 构建FAISS索引
dimension = chunk_embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(np.array(chunk_embeddings))
示例如下
分块数量: 32
Batches: 100%|██████████| 1/1 [00:00<00:00, 1.38it/s]
3.2 检索与问答
在将full_text文本向量化后,这里进行分块检索和LLM回答示例,代码如下所示。
def retrieve_relevant_chunks(query, k=3):
query_emb = model.encode([query])
distances, indices = index.search(query_emb, k)
return [chunks[i] for i in indices[0]]
def ask_with_rag(questions):
# 将多个问题合并成一个查询,检索相关块
combined_query = " ".join(questions.values() if isinstance(questions, dict) else questions)
relevant = retrieve_relevant_chunks(combined_query, k=5)
context = "\n\n---\n\n".join(relevant)
prompt = f"""
根据以下从苹果10-K年报中提取的相关段落,回答问题。以JSON格式返回。
相关段落:
{context}
问题:
1. 2023财年总营收(Total net sales)是多少?以百万美元为单位。
2. 2023财年研发费用(Research and Development)是多少?以百万美元为单位。
3. 研发费用占总营收的比例是多少?以百分比形式。
JSON输出示例:
{{"1": 383285, "2": 29915, "3": "7.81%"}}
"""
response = client.chat.completions.create(
model=model_name, # 可使用较小模型
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=300
)
return response.choices[0].message.content
questions_dict = {
"1": "2023财年总营收",
"2": "2023财年研发费用",
"3": "研发费用占总营收的比例"
}
rag_result = ask_with_rag(questions_dict)
print(rag_result)
输出如下
{
"1": 383285,
"2": 29915,
"3": "7.80%"
}
4 数值可靠性增强技巧
以上示例了基于LLM以及RAG,对苹果财报的总营收、研发投入,以及研发占比进行提取和计算。
实现逻辑简单清洗,然而在实际案例中,可能需要提取更复杂的数据。
数据可能不是显式分布,可能更隐蔽,更难发现,这时可能要采用更可靠的增强技巧。
比如CoT、取均值、以及应用外部计算工具。
1)思维链多步推理
要求模型先解释推理过程,再给出数值,可减少计算错误。
cot_prompt = """
请逐步推理并计算:
1. 从文本中找到总营收数字。
2. 找到研发费用数字。
3. 计算比例。
最后以JSON输出结果。
"""
2)多次运行取均值
temperature能调节模型的灵敏度,多次采用不同的temperature采样,取多数答案或平均值。
另外,采用幂采样的方式优化模型输出,也能获得更好的结果,缺点是会拖慢处理速度。
参考链接如下
https://blog.csdn.net/liliang199/article/details/154833697
3)应用外部计算工具
LLM运行计算并不是总是可靠的,有可能出现幻觉,导致计算错误。
让模型输出表达式,用 Python eval() 执行计算,可以避免模型内部计算错误,示例如下。
calc_prompt = """
请输出一个Python表达式来计算研发费用占比,例如 "x / y * 100",其中x和y是提取的数字。
"""
该方法的前提是数据已就绪,否值还需要补充数据提取和清洗过程。
reference
---
LLM长上下文和数值类有效输出的关系探索
https://blog.csdn.net/liliang199/article/details/159175752
如何基于幂采样优化LLM推理-原理&代码&示例
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)