Spaces:
Running
Running
| import streamlit as st | |
| from prompt_engineer.call_llm import LLMClient | |
| class ModelingCodingAgent(LLMClient): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.allowed_libs = [ | |
| "numpy", "sklearn.model_selection", "sklearn.preprocessing", "sklearn.ensemble", 'torch', 'torchvision', 'torchaudio', 'xgboost', 'lightgbm' | |
| ] | |
| self.code = None | |
| self.result = None | |
| self.suggestion = None | |
| self.user_selection = None | |
| self.par_content = "" | |
| self.inference_code = None | |
| self.best_model = None | |
| self.inference_data = None | |
| self.inference_processed_df = None | |
| self.abstract=None | |
| self.full = None | |
| self.error = None | |
| self.inference_error = None | |
| self.target = None | |
| self.finish_auto_task = False | |
| self.best_model_gz_bytes = None | |
| self.debug_num = 0 | |
| self.refined_suggestions = None | |
| def finish_auto(self): | |
| self.finish_auto_task = True | |
| def save_best_model_gz_bytes(self, best_model_gz_bytes): | |
| self.best_model_gz_bytes = best_model_gz_bytes | |
| def load_best_model_gz_bytes(self): | |
| return self.best_model_gz_bytes | |
| def save_target(self, target): | |
| self.target = target | |
| def load_target(self): | |
| return self.target | |
| def save_error(self, error): | |
| self.error = error | |
| def load_error(self): | |
| return self.error | |
| def save_inference_error(self, inference_error): | |
| self.inference_error = inference_error | |
| def load_inference_error(self): | |
| return self.inference_error | |
| def save_inference_data(self, inference_data): | |
| self.inference_data = inference_data | |
| def load_inference_data(self): | |
| return self.inference_data | |
| def save_inference_processed_df(self, inference_processed_df): | |
| self.inference_processed_df = inference_processed_df | |
| def load_inference_processed_df(self): | |
| return self.inference_processed_df | |
| def save_inference_code(self, code): | |
| self.inference_code = code | |
| def load_inference_code(self): | |
| return self.inference_code | |
| def save_best_model(self, best_model): | |
| self.best_model = best_model | |
| def load_best_model(self): | |
| return self.best_model | |
| def save_code(self, code): | |
| self.code = code | |
| def load_code(self): | |
| return self.code | |
| def save_suggestion(self, suggestion): | |
| self.suggestion = suggestion | |
| def load_suggestion(self): | |
| return self.suggestion | |
| def save_modeling_result(self, result): | |
| self.result = result | |
| def load_modeling_result(self): | |
| return self.result | |
| def save_user_selection(self, user_selection): | |
| self.user_selection = user_selection | |
| def load_user_selection(self): | |
| return self.user_selection | |
| def refine_suggestions(self): | |
| prompt = f""" | |
| 请阅读以下建模建议,并将其转化为对下一个 coding agent 的清晰建模任务指令。 | |
| === 建模建议 === | |
| {self.suggestion} | |
| === 输出要求(必须严格遵守) === | |
| 1. 输出为纯文本,不使用任何 Markdown、编号或符号; | |
| 2. 指令应简洁明确,便于 coding agent 直接理解并执行; | |
| 3. 内容应聚焦于模型构建、训练或评估的具体任务; | |
| 4. 避免解释性或分析性语言,仅描述“需要执行的操作”; | |
| 5. 输出应覆盖所有关键步骤,使 coding agent 能独立完成建模流程。 | |
| """.strip() | |
| refined_suggestions = self.call(prompt) | |
| self.refined_suggestions = refined_suggestions | |
| print(refined_suggestions) | |
| return refined_suggestions | |
| def code_generation(self, df_head: str, user_prompt: str) -> str: | |
| allowed = ", ".join(self.allowed_libs) | |
| if self.refined_suggestions is None: | |
| suggestion = user_prompt | |
| else: | |
| suggestion = self.refined_suggestions | |
| prompt = ( | |
| f"""请**严格只输出纯 Python 代码**,**不要**输出任何解释性文字、注释、示例、markdown code fence(禁止出现 ``` 或 ```python 等)。运行环境已提供 pandas DataFrame 变量 `df`、numpy(np)、train_test_split、StandardScaler、以及用户在 Requirement 中可能提到的任意模型类(例如 RandomForestRegressor、GradientBoostingRegressor、LinearRegression、XGBRegressor、LogisticRegression、SVC 等)。 | |
| 要求: | |
| 1) 使用 80/20 切分(random_state=42),根据用户需求决定是否对数值特征标准化(StandardScaler),如果标准化,务必只应用于数值列并在训练/测试集上分别执行 fit_transform/transform。 | |
| 2) **对 Requirement 中列出的所有模型都依次训练和评估**,不得只选随机森林;如果用户在 Requirement 中指定了多个模型名称,脚本必须循环遍历这些模型并分别训练、预测、计算指标。 | |
| 3) 不要导入任何评价库(如 sklearn.metrics),如需评价请用 numpy 手写实现常见指标(回归:MAE、MSE、R2;分类:accuracy、precision、recall、f1)。 | |
| 4) **脚本最后必须只输出并赋值一个变量 `result_dict`,且它是一个可以 JSON 序列化的 Python dict。** | |
| 推荐 schema(必须包含以下键): | |
| {{ | |
| "dataset": "<可选描述字符串>", | |
| "models": [ | |
| {{ | |
| "name": "<模型类名>", | |
| "type": "<regression 或 classification>", | |
| "metrics": {{ "<指标名>": <float>, ... }} | |
| }}, | |
| ... | |
| ], | |
| "best_model": {{ | |
| "name": "<得分最优的模型类名>", | |
| "score": <float> | |
| }}, | |
| "artifacts": {{ | |
| "best_model_b64": "<base64 字符串>", | |
| "best_model_format": "pickle+gzip" | |
| }}, | |
| // 如模型过大,可选 "artifact_warning": <int 字节大小> | |
| // 以及用户在 Requirement 中提出的其他字段 | |
| // 除最终评估指标外,脚本必须在 result_dict 中加入 intermediate 字段,用于存放模型中间结果(如线性回归系数、预测值、残差、特征重要性、交叉验证细节等)。 | |
| }} | |
| 5) 确保所有数值均为 Python 原生类型(float、int),字段名严格为 models、best_model、artifacts;如果用户有额外需求,如记录训练时间、特征重要性等,也请加入 result_dict。 | |
| 6) 模型导出:训练完毕后,将选定的 best_model 用 pickle 序列化并 gzip 压缩,再 base64 编码;把编码字符串和格式信息填入 result_dict["artifacts"],并确保最终 result_dict 可 JSON 序列化。 | |
| 7) 脚本末尾仅包含一行 `result_dict = {{...}}`,不要 print、不创建其他全局变量、不读写文件。 | |
| 8) 如果模型序列化后的字节数超过合理大小,请在 result_dict 中添加 `"artifact_warning": <字节数>`。 | |
| 9) 不要使用任何外部 IO 或文件操作。 | |
| 10) 请准确实现Requirement中要求的模型,不许添加Requirement之外的模型,若先提供的库中无法直接调用对应模型,请手动实现! | |
| 示例数据头部: | |
| {df_head} | |
| Requirement(请根据以下建模任务指令,对所有列出的模型依次执行训练与评估。若某模型在当前环境不可用,请手动实现对应算法或类,使结果完整可复现): | |
| {suggestion} | |
| Allowed libraries: {allowed}。 | |
| 返回:完整 Python 代码(纯代码块)。""" | |
| ) | |
| if self.error is not None: | |
| if self.debug_num < 5 : | |
| self.debug_num += 1 | |
| prompt += f""" | |
| 上次生成的代码运行失败。 | |
| 【错误信息】: | |
| {self.error} | |
| 【原始代码】: | |
| {self.code} | |
| 请在不输出任何解释性文字的情况下,推理并理解导致错误的根本原因, | |
| 要求: | |
| 1. 不输出任何分析、解释或说明(包括文字、列表或注释段落); | |
| 2. 可在代码内部使用简短注释说明关键修改; | |
| 3. 若错误源于逻辑、数据结构或函数使用不当,请自行调整; | |
| 4. 若依赖库方法不适用,可自行实现替代函数; | |
| 5. 生成的代码必须可独立运行,无语法错误; | |
| 6. 保持整体逻辑与原代码意图一致,仅做必要修正。 | |
| """ | |
| else: | |
| self.debug_num = 0 | |
| if st.session_state.preference_select: | |
| prompt += f"以下是用户的分析偏好设置:{st.session_state.preference_select}”。\n\n" | |
| if st.session_state.additional_preference: | |
| prompt += f"用户提供了以下建模目的与特殊需求:{st.session_state.additional_preference}”。\n\n" | |
| raw = self.call(prompt) | |
| return raw | |
| def result_format_prompt(self, result_json: str) -> str: | |
| prompt = f""" | |
| 下面给出一个 JSON 对象(包含模型评估结果结构)。请将其转换为一份对人类友好的 Markdown 报告,输出要求如下: | |
| === 输出要求 === | |
| 1. 报告开头需有一行简短的“数据集说明”。 | |
| 2. 对每个模型,展示以下内容: | |
| - 模型名称; | |
| - 模型类型(分类 / 回归); | |
| - 主要性能指标(如准确率、R²、MAE、MSE 等),每个指标保留 4 位小数; | |
| - 建议使用表格或分点列表清晰呈现。 | |
| - intermediate 用于存放模型中间结果(如线性回归系数、预测值、残差、特征重要性、交叉验证细节),请深入讲解 | |
| 3. 明确标出 **best_model**(以粗体高亮显示其名称和最优指标)。 | |
| 4. 若 JSON 中包含特征工程相关信息,请在“特征工程说明”部分详细描述其具体方法与作用。 | |
| 5. 输出格式: | |
| - 只返回 Markdown 文本; | |
| - 不得使用任何代码块标记(如 ```、```markdown 等); | |
| - 不输出解释性文字,仅输出最终报告内容(便于直接渲染于 Streamlit)。 | |
| === 输入 JSON === | |
| {result_json} | |
| """.strip() | |
| if self.code is not None: | |
| prompt += f"用户使用了以下code进行建模:{self.code}”。\n\n" | |
| if st.session_state.preference_select: | |
| prompt += f"以下是用户的分析偏好设置:{st.session_state.preference_select}”。\n\n" | |
| if st.session_state.additional_preference: | |
| prompt += f"用户提供了以下建模目的与特殊需求:{st.session_state.additional_preference}”。\n\n" | |
| raw = self.call(prompt) | |
| return raw | |
| def get_model_suggestion( | |
| self, | |
| user_input=None, | |
| memory_limit: int = 6, | |
| ) -> str: | |
| df = self.load_df() | |
| df_head = df.head().to_string(index=False) | |
| columns = df.columns.tolist() | |
| data_info = f"数据列名: {columns}\n\n数据前5行:\n{df_head}" | |
| recent_memory = self.memory[-memory_limit:] if getattr(self, "memory", None) else [] | |
| if recent_memory: | |
| formatted_memory = "\n".join( | |
| f"{m['role']}: {m['content']}" for m in recent_memory | |
| ) | |
| memory_block = f"\n=== 历史上下文(仅供参考) ===\n{formatted_memory}\n" | |
| else: | |
| memory_block = "" | |
| prompt = f""" | |
| 你是一位资深的机器学习建模专家,请基于以下信息进行分析与推理,输出针对性建模建议或改进方案。 | |
| === 数据信息 === | |
| {data_info} | |
| === 历史上下文(仅供参考) === | |
| {memory_block} | |
| """.strip() | |
| if getattr(self, "target", None): | |
| prompt += f""" | |
| === 建模目标 === | |
| {self.target} | |
| (请务必满足该目标,并在回答中明确复述建模意图。) | |
| """ | |
| if user_input: | |
| prompt += f""" | |
| === 用户当前需求 === | |
| {user_input} | |
| (请严格满足该需求。若为局部修改,请保留原逻辑,仅更新指定部分。) | |
| """ | |
| train_code = self.load_code() | |
| if train_code: | |
| prompt += f""" | |
| === 历史训练代码 === | |
| {train_code} | |
| 请在充分理解上述代码的基础上,提出 **1–2 条高质量的模型改进建议**。 | |
| 可从以下角度思考,但不限于此: | |
| - 模型结构优化(如增加层数、调整激活函数、替换模型类型等); | |
| - 特征工程改进(如变量选择、特征交互、归一化策略等); | |
| - 训练流程优化(如正则化、学习率调度、损失函数调整等); | |
| - 超参数调整(如树深度、学习率、batch size 等)。 | |
| 在给出建议时,请简要说明“为什么”与“预期改进效果”。 | |
| """ | |
| else: | |
| prompt += """ | |
| === 建模建议任务 === | |
| 请根据数据特征和上下文,推荐 2–3 个适合的模型方案。 | |
| 要求: | |
| 1. 每个模型需包含模型名称、主要原理、适用场景; | |
| 2. 指出其在当前任务中的优势与潜在局限; | |
| 3. 保持语言专业、简洁,不输出代码。 | |
| """ | |
| if st.session_state.preference_select: | |
| prompt += f"以下是用户的分析偏好设置:{st.session_state.preference_select}”。\n\n" | |
| if st.session_state.additional_preference: | |
| prompt += f"用户提供了以下建模目的与特殊需求:{st.session_state.additional_preference}”。\n\n" | |
| raw = self.call(prompt) | |
| return raw | |
| def summary_html(self) -> str: | |
| if self.code is None: | |
| summary = None | |
| return summary | |
| else: | |
| prompt = f""" | |
| 你正在撰写数据分析报告的**第四章:数据建模**。 | |
| 请根据以下输入内容,综合分析并生成完整的章节正文。 | |
| 内容需逻辑严谨、表达自然,体现专业的分析与总结能力。 | |
| === 输出结构 === | |
| 请严格按照以下五个小节组织内容: | |
| 1. 概述 | |
| - 说明本次建模的目标、研究背景及数据来源的上下文。 | |
| 2. 方法说明 | |
| - 介绍所采用的模型或算法的核心思想与实现流程; | |
| - 若涉及特征工程、超参数选择或数据预处理,请一并说明; | |
| - 可适当涉及模型的数学原理或优化机制,以体现技术深度。 | |
| 3. 关键代码解读 | |
| - 聚焦核心函数与模块,说明其在建模流程中的作用; | |
| - 可提及模型结构定义、训练循环、损失函数与评估逻辑; | |
| - 语言应清晰简练,避免逐行解释。 | |
| 4. 结果与评估 | |
| - 概述主要性能指标(如 Accuracy、AUC、MSE 等)及结果表现; | |
| - 分析模型效果是否符合预期,并指出主要优劣与瓶颈。 | |
| 5. 改进建议 | |
| - 针对模型性能与实验发现,提出具体可行的优化方向; | |
| - 可从模型结构、特征选择、训练策略或正则化等角度给出建议。 | |
| === 写作要求 === | |
| 1. 使用自然流畅、正式的书面表达; | |
| 2. 避免使用模糊或主观词汇(如“可能”“似乎”“微妙”等); | |
| 3. 注重逻辑连贯与专业性; | |
| 4. 不输出标题、列表标记或额外说明,只生成正文内容。 | |
| """.strip() | |
| if self.code is not None: | |
| prompt += f"=== 数据建模代码 ===\n\n{self.code}" | |
| if self.target is not None: | |
| prompt += f"=== 用户建模目标 ===\n\n{self.target}" | |
| if self.load_memory is not None: | |
| prompt += f"=== 数据建模聊天对话 ===\n\n{self.load_memory}" | |
| if self.result is not None: | |
| prompt += f"=== 建模运行结果 ===\n\n{self.result}" | |
| desc = self.call(prompt) | |
| summary = { | |
| "title": "建模分析", | |
| "code": self.code, | |
| "desc": desc, | |
| "result": self.result, | |
| } | |
| return summary | |
| def summary_word(self) -> str: | |
| return self.summary_html() | |
| def code_generation_for_inference(self, code, inference_df_head) -> str: | |
| """生成 LLM prompt:要求 LLM 输出推断分析代码。""" | |
| prompt = ( | |
| f"""请生成完整的 Python 推断分析脚本(仅返回代码,不要任何解释文字)。运行环境已提供 pandas DataFrame 变量 `inference_df`、已经 train 好的模型 `model_obj`、numpy(np)、StandardScaler 库、align_features 辅助函数,其余未提及的库请手写实现。要求: | |
| 示例数据信息: | |
| {code}, inference_df 前五行: {inference_df_head}(请勿引入不存在 inference_df 中的变量) | |
| 1) **可用变量说明:** | |
| - `inference_df`:推断数据集(Pandas DataFrame) | |
| - `model_obj`:已训练好的模型对象(从best_model.joblib加载) | |
| - `np`:NumPy库 | |
| - `pd`:Pandas库 | |
| - `StandardScaler`:用于数据标准化的sklearn工具 | |
| 2) **脚本必须实现的功能:** | |
| a) 对推断数据进行与训练时完全一致的预处理(例如,缺失值处理、编码转换、标准化等) | |
| b) **关键步骤:在预测前,必须使用align_features函数处理特征数据,确保特征数量和顺序与训练时一致** | |
| c) 使用model_obj对预处理并对齐后的特征数据进行预测 | |
| d) 生成详细的推断报告,包含预处理步骤、预测结果分析等 | |
| 3) **预测结果处理要求:** | |
| - 将模型输出转换为人类可理解的形式(如概率值、类别标签、数值结果等) | |
| - **必须生成带预测结果的DataFrame**:将原始或处理后的`inference_df`与预测结果合并,命名为`inference_df_with_predictions` | |
| - 合并后的DataFrame必须包含原始特征列和一列名为`'prediction'`的预测结果列(模型输出多维时扩展为`prediction_0`, `prediction_1`, ...) | |
| 4) **序列化要求(用于前端下载):** | |
| - 将`inference_df_with_predictions`转换为无索引的CSV格式 | |
| - 对CSV数据进行gzip压缩,然后编码为base64字符串 | |
| - 创建包含以下键的`result_dict['artifacts']`字典: | |
| * `'predictions_df_b64'`:base64编码的压缩数据 | |
| * `'predictions_df_format'`:固定值'csv+gzip' | |
| * `'predictions_df_size_bytes'`:压缩后的字节大小(整数) | |
| - 在`result_dict`中添加`'predictions_df_records'`键,值为`inference_df_with_predictions.to_dict(orient='records')` | |
| - 确保所有numpy/pandas类型转换为原生Python类型(int/float/str)以保证JSON可序列化 | |
| 5) **代码结构与输出约束:** | |
| - 脚本最后**仅**包含一行`result_dict = {...}`语句 | |
| - `result_dict`必须是完全JSON可序列化的Python字典 | |
| - 禁止任何外部IO操作(不读写文件) | |
| - 禁止使用print语句或创建额外的全局变量 | |
| 8) **生成代码质量要求:** | |
| - 确保所有变量名称与上述规范严格一致 | |
| - 逻辑清晰,步骤完整,严格按照用户提供的数据和最佳模型文件生成代码 | |
| - 处理可能出现的各种异常情况,提高代码的稳定性和可靠性 | |
| 返回:完整的Python代码(仅包含代码本身,不要任何解释性文字)。""" | |
| ) | |
| raw = self.call(prompt) | |
| return raw | |
| def check_abstract(self): | |
| if self.abstract is None: | |
| if self.code is None: | |
| self.abstract = None | |
| else: | |
| prompt = f""" | |
| 这是数据分析流程中的“建模阶段”。 | |
| 请基于以下信息,在保留所有关键信息的前提下,将内容整理成一段简洁、连贯的文字摘要,用于报告撰写中的建模小节预览。 | |
| === 输入信息 === | |
| - 用户初始需求:{self.target} | |
| - 建模代码:{self.code} | |
| - 建模阶段的交互记录:{self.load_memory} | |
| - 建模运行结果:{self.result} | |
| === 输出要求 === | |
| 1. 以自然流畅的语言撰写一段总结,全面涵盖上述内容中的核心信息; | |
| 2. 重点说明建模目标、所用方法、主要实现逻辑与结果特征; | |
| 3. 避免逐行描述代码,仅提炼核心思路; | |
| 4. 语言应专业、客观,不使用“可能”“似乎”“也许”等模糊表达; | |
| 5. 输出仅为一段完整文字(不要标题、编号或列表); | |
| 6. 摘要应能让人据此判断该部分是否需要纳入最终报告。 | |
| """.strip() | |
| desc = self.call(prompt) | |
| self.abstract = desc | |
| return self.abstract | |
| def check_full(self): | |
| if self.full is None: | |
| if self.code is None: | |
| self.full = None | |
| else: | |
| self.full = f""" | |
| 【阶段说明】这是数据分析流程中的数据建模阶段。 | |
| 【用户初始需求】{self.target} | |
| 【数据建模代码】{self.code} | |
| 【建模聊天对话】{self.load_memory} | |
| 【建模运行结果】{self.result} | |
| """.strip() | |
| return self.full |