构建LangChain应用程序的示例代码:59、如何使用OpenAI函数来结构化问答系统的输出

OpenAI函数允许结构化响应输出。这在问答中特别有用,因为你不仅想得到最终答案,还想获得支持证据、引用等。

在这个笔记本中,我们展示了如何使用OpenAI函数作为整体检索管道的一部分的LLM链。

from langchain.chains import RetrievalQA
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import CharacterTextSplitter

# 导入必要的库
# RetrievalQA: 用于创建检索式问答系统
# TextLoader: 用于加载文本文件
# Chroma: 向量存储库
# OpenAIEmbeddings: OpenAI的文本嵌入模型
# CharacterTextSplitter: 用于将文本分割成小块
loader = TextLoader("../../state_of_the_union.txt", encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
for i, text in enumerate(texts):
    text.metadata["source"] = f"{
     
     i}-pl"
embeddings = OpenAIEmbeddings()
docsearch = Chroma.from_documents(texts, embeddings)

# 加载文本文件
# 将文档分割成小块
# 为每个文本块添加源信息
# 创建文本嵌入
# 使用Chroma创建向量存储
from langchain.chains import create_qa_with_sources_chain
from langchain.chains.combine_documents.stuff import StuffDocumentsChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 导入创建问答链的函数
<think>我们将在现有代码基础上进行修改,主要目标:1.根据用户输入的sat_name判断是否在initial工作表中存在2.若不存在,则退出并打印错误3.若存在,读取对应sat_name的Excel文件(假设在initial表中有一列指定了每个sat_name对应的数据文件路径)4.根据用户输入的时间段(可能是自然语言描述,通过大模型解析)来提取数据并生成交互式图表需要修改的部分:1.在_load_excel_data方法中,我们原本只读取了一个固定文件路径的Excel。现在需要改变:-首先读取initial表,并构建一个sat_name到数据文件的映射-然后根据用户输入,从映射中获取对应的数据文件路径-读取该数据文件(可能是另一个Excel文件,或者是同一个Excel的不同sheet?这里假设每个sat_name对应一个单独的数据文件)2.用户输入中需要包含sat_name,所以我们需要修改process_query方法,使其能够解析出sat_name。但是,我们目前的设计是用户输入一句自然语言,然后由大模型来解析并调用工具。因此,我们需要在工具函数中增加对sat_name的判断。然而,我们注意到原有工具函数(如analyze_date_range和generate_interactive_chart)并没有考虑不同的sat_name对应的不同数据。所以需要重构。考虑到这些,我们决定:a)在类初始化时,读取initial表,建立sat_name到数据文件的映射(作为一个字典self.sat_data_map),并存储在一个实例变量中。b)在工具函数中,需要接收sat_name参数,并根据sat_name获取对应的数据。c)修改工具函数的参数,增加sat_name。d)在process_query中,我们传递给工具函数的参数应该包含sat_name。但是,由于我们使用大模型来解析自然语言,我们需要确保大模型知道它必须提取sat_name。因此,我们需要修改工具的描述,使其包含这个参数。具体步骤:1.修改_load_excel_data方法:-现在只读取initial表,不读取其他表。因为我们不知道用户要查询哪个sat_name,所以先不加载所有数据。-将initial表的内容转换为一个字典:key为sat_name,value为对应的数据文件路径。2.当用户输入一个查询时,我们期望查询中包含sat_name和日期范围。大模型会调用工具,并传入这些参数。3.在工具函数中,我们根据sat_name在self.sat_data_map中查找对应的数据文件路径,然后加载该数据文件(或者如果已经加载过可以缓存,避免重复加载)。4.然后根据日期范围筛选数据,并生成图表或分析结果。5.如果sat_name不存在,则工具函数返回错误信息。由于工具函数需要动态加载数据文件,我们在工具函数中实现数据加载。但是,为了避免重复加载同一个文件,我们可以使用缓存(例如,在实例中存储已经加载过的DataFrame)。重构后的代码:步骤1:修改__init__和_load_excel_data方法原代码中,_load_excel_data读取整个Excel文件,包括initial和所有其他sheet。现在我们改变其功能,只读取initial表,并建立映射。同时,我们不再在初始化时加载所有数据,而是等到需要的时候再加载。步骤2:修改工具函数,增加sat_name参数,并在工具函数中加载对应数据文件。步骤3:修改工具描述,让大模型知道需要传入sat_name。步骤4:在工具函数中,如果sat_name不存在,则返回错误。由于原有代码中,工具函数的参数是一个字符串(JSON格式),我们需要解析出sat_name。我们以generate_interactive_chart工具函数为例:原参数:{'start_date':'...','end_date':'...','metrics':[...]}修改后:{'sat_name':'...','start_date':'...','end_date':'...','metrics':[...]}同样,analyze_date_range也需要加入sat_name。考虑到这些,我们修改_create_tool方法中的描述,明确告诉模型需要sat_name。同时,我们需要修改参数验证函数,现在不需要在初始化时验证,而是放在工具函数中,因为数据是动态加载的。具体代码修改如下:</think> 我将基于您提供的代码框架,结合langchain和Qwen模型集成,实现一个完整的卫星数据分析系统。这个解决方案专注于:卫星名称校验、交互式图表生成和Qwen模型集成分析。 ```python # -*- coding: utf-8 -*- """ Qwen大模型API集成与交互式卫星数据分析系统 主要功能: 1. 使用Qwen API进行自然语言交互 2. 卫星数据存在性校验 3. 创建支持缩放交互的时间序列图表 """ import pandas as pd import os import plotly.graph_objects as go from langchain_community.chat_models import ChatOpenAI from langchain_community.tools import Tool from langchain_core.utils.function_calling import convert_to_openai_tool from dotenv import load_dotenv import json import sys # 加载环境变量 load_dotenv() class SatelliteAnalyzer: """卫星数据分析主程序""" def __init__(self): """初始化模型、工具链和卫星索引""" self.model = self._initialize_qwen_model() self.tools = self._load_tools() self.satellite_index = self._load_satellite_index() def _initialize_qwen_model(self) -> ChatOpenAI: """初始化Qwen模型""" return ChatOpenAI( model="qwen-plus", openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1", openai_api_key=os.getenv("DASHSCOPE_API_KEY"), temperature=0.3, max_tokens=2048 ) def _load_satellite_index(self) -> dict: """加载卫星索引表并构建映射""" index_path = "data/initial.xlsx" if not os.path.exists(index_path): print(f"错误: 索引文件不存在 - {index_path}") sys.exit(1) try: index_df = pd.read_excel(index_path, sheet_name="initial") return index_df.set_index("sat_name")["data_file"].to_dict() except KeyError: print("错误: 索引文件缺少sat_name或data_file列") sys.exit(1) def _load_tools(self) -> list: """注册工具函数""" tools = [ self._create_tool("卫星数据验证", "验证卫星名称是否存在", self.verify_satellite), self._create_tool("卫星数据分析", "分析卫星数据并生成图表", self.analyze_and_visualize) ] return [convert_to_openai_tool(tool) for tool in tools] def _create_tool(self, name: str, description: str, func: callable) -> Tool: """创建标准化工具""" return Tool( name=name, description=description, func=func, return_direct=True ) def verify_satellite(self, params: str) -> str: """验证卫星是否存在""" try: args = json.loads(params) sat_name = args['satellite'] if sat_name not in self.satellite_index: print(f"错误: 卫星 '{sat_name}' 不存在于索引文件") return f"卫星名称 '{sat_name}' 未找到,请检查输入" return f"卫星 '{sat_name}' 存在,数据文件路径: {self.satellite_index[sat_name]}" except (KeyError, json.JSONDecodeError) as e: return f"参数错误: {str(e)}" def analyze_and_visualize(self, params: str) -> str: """卫星数据分析与可视化主函数""" try: # 解析参数 args = json.loads(params) sat_name = args['satellite'] start_date = args['start_date'] end_date = args['end_date'] # 验证卫星存在性 if sat_name not in self.satellite_index: return f"错误: 卫星 '{sat_name}' 不存在" # 加载卫星数据 data_path = self.satellite_index[sat_name] if not os.path.exists(data_path): return f"错误: 数据文件不存在 - {data_path}" df = pd.read_excel(data_path) # 验证时间列 if 'timestamp' not in df.columns: return "错误: 数据文件中缺少时间戳列" # 转换时间格式并筛选 df['timestamp'] = pd.to_datetime(df['timestamp']) time_mask = (df['timestamp'] >= pd.to_datetime(start_date)) & \ (df['timestamp'] <= pd.to_datetime(end_date)) df_filtered = df.loc[time_mask] if df_filtered.empty: return f"错误: {start_date} 至 {end_date} 无数据" # 生成交互式图表 return self._generate_interactive_chart(df_filtered, sat_name, start_date, end_date) except Exception as e: return f"错误: {str(e)}" def _generate_interactive_chart(self, df: pd.DataFrame, sat_name: str, start_date: str, end_date: str) -> str: """创建交互式Plotly图表""" # 生成时间序列图表 fig = go.Figure() # 添加所有数值列为数据线 for col in df.select_dtypes(include=['float', 'int']).columns: fig.add_trace(go.Scatter( x=df['timestamp'], y=df[col], mode='lines+markers', name=col, hoverinfo='x+y+name', hovertemplate='%{x|%Y-%m-%d %H:%M}<br>%{y:.2f}<extra></extra>' )) # 添加时间轴控件 fig.update_layout( title=f"{sat_name}卫星数据 ({start_date} 至 {end_date})", xaxis_title="时间", yaxis_title="值", hovermode="x unified", xaxis=dict( rangeselector=dict( buttons=list([ dict(count=1, label="1天", step="day", stepmode="backward"), dict(count=7, label="1周", step="day", stepmode="backward"), dict(count=1, label="1月", step="month", stepmode="backward"), dict(step="all") ]) ), rangeslider=dict(visible=True), type="date" ), yaxis=dict(fixedrange=False) ) # 保存HTML图表 chart_path = f"results/{sat_name}_chart.html" os.makedirs(os.path.dirname(chart_path), exist_ok=True) fig.write_html(chart_path, include_plotlyjs="cdn") # 返回分析结果 analysis_result = self._analyze_data_with_qwen(df) return (f"图表已生成: {chart_path}\n" f"模型分析结果:\n{analysis_result}") def _analyze_data_with_qwen(self, df: pd.DataFrame) -> str: """使用Qwen模型分析数据特征""" # 构建简化版数据摘要供模型分析 data_summary = { "columns": list(df.columns), "data_types": df.dtypes.astype(str).to_dict(), "statistics": df.describe().to_dict() } try: # 创建分析提示词 prompt = ("你是一个卫星数据分析专家。请基于以下数据摘要进行专业分析:\n" f"{json.dumps(data_summary, indent=2)}\n\n" "指出任何异常值、趋势特征或需要关注的潜在问题。") # 发送到Qwen模型 response = self.model.invoke(prompt) return response.content except Exception as e: return f"模型分析失败: {str(e)}" def process_query(self, user_input: str) -> str: """处理用户查询""" try: # 创建模型调用消息 message = { "role": "user", "content": user_input, "tools": self.tools } # 执行API调用 response = self.model.invoke([message]) result = response.content # 处理工具调用 if hasattr(response, 'tool_calls') and response.tool_calls: tool_call = response.tool_calls[0] tool_name = tool_call.function.name tool_params = tool_call.function.arguments # 调用对应的工具函数 if tool_name == "卫星数据验证": return self.verify_satellite(tool_params) elif tool_name == "卫星数据分析": return self.analyze_and_visualize(tool_params) return result except Exception as e: return f"查询处理错误: {str(e)}" if __name__ == "__main__": print("="*50) print("卫星数据分析系统启动") print("="*50) # 初始化系统 print("\n[步骤1] 初始化Qwen模型和卫星索引...") analyzer = SatelliteAnalyzer() print("✓ 系统初始化完成") # 示例查询 queries = [ "验证sat_name为1001的卫星是否存在", "读取sat_name为1002的2025-01-01到2025-01-10的数据并生成图表" ] for i, query in enumerate(queries, 2): print(f"\n[步骤{i}] 执行查询: '{query}'") result = analyzer.process_query(query) print(f"\n[结果]\n{'-'*50}\n{result}\n{'-'*50}") print("\n✓ 程序执行完成") ``` ### 系统架构说明 1. **卫星索引管理**: - 使用`initial.xlsx`作为卫星注册表 - 建立sat_name到数据文件的映射关系 - 自动校验索引文件结构完整性 2. **数据验证流程**: ```mermaid graph TD A[用户查询] --> B[Qwen解析请求] B --> C{卫星存在?} C -->|存在| D[加载数据文件] C -->|不存在| E[返回错误信息] D --> F{时间范围有数据?} F -->|是| G[生成交互图表] F -->|否| H[返回无数据错误] ``` 3. **交互式图表功能**: - **时间缩放**:通过rangeselector实现1天/1周/1月/全部时间范围切换 - **纵轴调整**:设置`yaxis_fixedrange=False`支持Y轴自由缩放 - **数据悬停**:显示精确时间点和数值 - **多数据序列**:自动添加所有数值列为独立曲线 4. **Qwen模型集成**: - 数据摘要统计:`df.describe()`结果 - 专业分析提示词:指导模型进行趋势和异常分析 - 结果整合:图表+分析的双重输出 ### 文件结构要求 ``` 项目目录/ ├── data/ │ ├── initial.xlsx # 卫星索引表 │ └── 1001_data.xlsx # 卫星1001数据 │ └── 1002_data.xlsx # 卫星1002数据 ├── results/ # 图表输出目录 ├── .env # API密钥配置 └── satellite_analyzer.py # 主程序 ``` ### 相关扩展问题 1. **如何优化大型数据集性能?** - 使用Pandas的chunksize参数分批处理[^1] - 应用Downsampling技术减少数据点数量 - 实现WebGL加速渲染:`fig.update_traces(marker_opacity=0.5)` 2. **如何添加卫星轨道可视化?** ```python # 在_generate_interactive_chart中添加 if 'latitude' in df.columns and 'longitude' in df.columns: map_fig = px.scatter_geo(df, lat='latitude', lon='longitude', projection="natural earth") map_fig.write_html(f"results/{sat_name}_orbit.html") ``` 3. **如何集成实时数据源?** - 使用`schedule`库定期更新数据 - 建立SQLite/InfluxDB时间序列数据库 - 通过WebSocket实现实时数据推送 4. **如何扩展分析维度?** - 添加统计分析工具(移动平均、傅里叶变换) - 集成异常检测算法(Isolation Forest, LOF) - 开发预测模型接口(Prophet, LSTM)[^2] [^1]: 当处理超过100万行数据时,建议使用Dask替代Pandas进行并行处理 [^2]: 时间序列预测可参考: https://otexts.com/fpp3/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hugo_Hoo

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值