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( modelmodel_name, # 支持128K上下文的模型 messages[{role: user, content: prompt}], temperature0, # 降低随机性提高数值准确性 max_tokens500 ) 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(fQ{q}: 预测 {pred_val}% vs 真实 {gt}%误差 {abs(pred_val-gt):.2f}%) else: print(fQ{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误差 0Q2: 预测 29915 vs 真实 29915误差 0Q3: 预测 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_size500, overlap50): words text.split() chunks [] for i in range(0, len(words), chunk_size - overlap): chunk .join(words[i:ichunk_size]) chunks.append(chunk) return chunks chunks chunk_text(full_text, chunk_size1000) # 每个块约1000词 print(f分块数量: {len(chunks)}) # 生成向量使用轻量模型 model SentenceTransformer(all-MiniLM-L6-v2) chunk_embeddings model.encode(chunks, show_progress_barTrue) # 构建FAISS索引 dimension chunk_embeddings.shape[1] index faiss.IndexFlatL2(dimension) index.add(np.array(chunk_embeddings))示例如下分块数量: 32Batches: 100%|██████████| 1/1 [00:0000:00, 1.38it/s]3.2 检索与问答在将full_text文本向量化后这里进行分块检索和LLM回答示例代码如下所示。def retrieve_relevant_chunks(query, k3): 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, k5) 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( modelmodel_name, # 可使用较小模型 messages[{role: user, content: prompt}], temperature0, max_tokens300 ) 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/1548336973应用外部计算工具LLM运行计算并不是总是可靠的有可能出现幻觉导致计算错误。让模型输出表达式用 Pythoneval()执行计算可以避免模型内部计算错误示例如下。calc_prompt 请输出一个Python表达式来计算研发费用占比例如 x / y * 100其中x和y是提取的数字。 该方法的前提是数据已就绪否值还需要补充数据提取和清洗过程。reference---LLM长上下文和数值类有效输出的关系探索https://blog.csdn.net/liliang199/article/details/159175752如何基于幂采样优化LLM推理-原理代码示例https://blog.csdn.net/liliang199/article/details/154833697