软件开发与数据分析智能体

121 阅读55分钟

本章探讨了自然语言——即我们日常使用的英语或任何你喜欢用来与大型语言模型(LLM)交互的语言——如何崛起为一种强大的编程接口。这是一场范式的变革,极致表现被称为“vibe coding”(氛围编码)。开发者无需再去学习新的编程语言或框架,而是可以用自然语言表达自己的意图,由先进的LLM和如LangChain这样的框架,将这些想法转化为健壮、可用于生产环境的代码。此外,虽然传统编程语言依然是生产系统的基石,LLM正在创造出新的工作流程,既补充现有做法,也有潜力提升技术的普及度。这一演进标志着代码生成和自动化早期尝试的重大转变。

我们将具体讨论LLM在软件开发中的地位,及其性能、模型和应用的最新进展。内容涵盖如何使用LLM链和智能体辅助代码生成与数据分析、训练机器学习模型以及提取预测结果。示例涵盖多种模型,包括谷歌的生成式AI服务、Hugging Face和Anthropic等。随后,我们将探讨更高级的方法,如结合智能体和检索增强生成(RAG)应用于文档或代码库管理。

我们还会将LLM智能体应用于数据科学:首先在数据集上训练模型,随后对数据集进行分析和可视化。不论你是开发者、数据科学家还是技术决策者,本章都将帮助你清晰理解LLM如何重塑软件开发和数据分析,同时保持传统编程语言的重要地位。

本章将涵盖的主题包括:

  • LLM在软件开发中的应用
  • 使用LLM编写代码
  • 将LLM智能体应用于数据科学

LLM 在软件开发中的应用

自然语言与编程之间的关系正经历着重大变革。传统编程语言依然是软件开发中不可或缺的工具——C++ 和 Rust 适用于性能关键型应用,Java 和 C# 多用于企业系统,而 Python 则在快速开发、数据分析和机器学习流程中扮演重要角色。然而,自然语言,特别是英语,现在成为一种强大的接口,能够简化软件开发和数据科学任务,作为对这些专业编程工具的有力补充,而非替代。

先进的 AI 助手让你只需保持对想法的“氛围感”,即可构建软件,无需编写或甚至想象任何代码行。这种开发风格被称为“vibe coding”(氛围编码),由 Andrej Karpathy 在2025年初推广。你无需用编程术语来描述任务,也无需纠结语法,而是通过自然对话描述期望的行为、用户流程或结果。模型随后在幕后协调数据结构、逻辑和集成。使用 vibe coding,你不调试代码,而是“重新调整氛围”——通过用自然语言重新陈述或细化需求,助手会重新塑造系统。最终形成的是一种纯粹、直观的以设计为先的工作流程,彻底抽象掉所有编码细节。

支持这种开发方式的工具不断涌现,包括 Cursor、Windsurf(前身为 Codeium)、OpenHands 以及 Amazon Q Developer 等,它们各自提供不同的 AI 辅助编码能力。实际上,这些界面正在使软件开发更具民主化,同时解放经验丰富的工程师免于重复性劳动。然而,尤其是在生产系统中,如何在速度、代码质量和安全之间取得平衡仍然至关重要。

软件开发领域长期以来一直在通过各种抽象层使编程变得更易接近。早期尝试包括第四代语言,旨在简化语法,让开发者用更少代码表达逻辑。随后,现代低代码平台兴起,提供可视化编程和预构建组件,旨在让非传统编码专家也能开发应用。最新且或许是最具变革性的演进是通过大型语言模型实现的自然语言编程,它能够解读用简单语言表达的人类意图,并将其转化为可用的代码。

这次演进特别与众不同的地方在于,它根本性地突破了以往的方法。我们不再创造让人类学习的新型人工语言,而是让智能工具理解自然的人类交流,大幅降低了入门门槛。不同于传统低代码平台往往导致的厂商锁定,自然语言编程生成标准代码,保持开发者的自由和对现有生态系统的兼容性。更重要的是,这种方式提供了前所未有的灵活性,既适合寻求快速解决方案的新手,也能满足希望加速工作流程的经验丰富开发者,从简单任务到复杂应用皆可覆盖。

开发的未来

国际数据公司(IDC)的分析师预测,到2028年,自然语言将被用于创造70%的新数字解决方案(IDC FutureScape,全球开发者和DevOps 2025预测)。然而,这并不意味着传统编程语言会消失;相反,它将演变为一种两层体系结构——自然语言作为高级接口,而传统编程语言则负责精确的实现细节。

尽管自然语言能够简化设计阶段并加快原型开发,但像Python这样具有精确性和确定性的语言依然是构建可靠、可投产系统的关键。换句话说,自然语言(无论是英语、中文,还是其他适合我们认知习惯的语言)并不是完全取代代码,而是作为一种高层抽象层,桥接人类意图和可执行逻辑。

对于软件开发者、数据科学家和技术决策者来说,这一转变意味着拥抱混合工作流:自然语言指令由大型语言模型(LLM)及LangChain等框架驱动,与传统代码共存。这种集成方法为更快速的创新、个性化软件解决方案铺平道路,并最终实现更易用的开发流程。

实施考量

在生产环境中,这一演进通过多种方式体现,正在改变开发团队的运作方式。自然语言界面使原型设计更快速,减少了编写样板代码的时间,而传统编程仍是复杂功能优化和实现的基石。然而,近期独立研究揭示了当前AI编码能力的显著局限性。

2025年OpenAI SWE-Lancer基准研究发现,即使是表现最好的模型,也仅完成了约26.2%的真实自由职业工程任务。研究指出的具体挑战包括表面级问题解决、跨多文件的上下文理解不足、测试不充分以及对边缘案例处理不佳。

尽管如此,许多组织报告称,针对特定场景使用AI编码助手确实提高了生产力。最有效的方法似乎是协作:利用AI加速常规任务,同时依靠人工专家处理AI仍然薄弱的领域,如架构决策、全面测试及业务需求理解。随着技术成熟,自然语言和传统编程的成功融合,很可能取决于清晰划分双方优势,而非假设AI能自主完成复杂软件工程挑战。

代码维护已经通过AI辅助的方法得到了改进,开发者可通过自然语言理解和修改代码库。GitHub报告称,Copilot用户在受控实验中完成特定编码任务的速度提高了55%;但独立实地研究表明,生产环境中的生产力提升更为温和,介于4%至22%之间,具体依赖于场景和测量方法。同样,Salesforce报告其内部工具CodeGenie提升了生产效率,包括自动化代码审查和安全扫描。除了速度上的提升,研究一致表明,AI编码助手减少了开发者的认知负担并提升了满意度,特别是在重复性任务上。然而,研究也指出重要的局限:生成代码通常需要大量人工验证和返工,一些独立研究报告AI辅助代码的缺陷率更高。证据显示,这些工具是宝贵的助理,简化开发流程,但仍需人工保证质量和安全。

代码调试领域也获得增强,自然语言查询帮助开发者更快定位和解决问题,解释错误信息,提供潜在修复建议,以及分析异常行为背景。AXA部署的“AXA Secure GPT”,基于内部政策和代码库训练,显著缩短了常规任务的处理时间,使开发团队能专注于更具战略性的工作。

在理解复杂系统方面,开发者可借助LLM生成复杂架构、遗留代码库或第三方依赖的解释与可视化,加速新成员入职和系统理解。例如,Salesforce的系统架构图展示了其LLM集成平台如何跨多个服务连接,尽管最新财报显示这些AI项目尚未对财务结果产生显著影响。

系统架构本身也在演变,随着应用日益需要考虑自然语言接口,无论是开发阶段还是未来用户交互。宝马报告称,已实现基于生成式AI的实时聊天界面平台,将数据采集到可执行建议的时间从数天缩短到数分钟。然而,这一架构转型也反映出行业趋势——咨询公司成为生成式AI繁荣的最大受益者。近期行业分析显示,埃森哲等咨询巨头的生成式AI服务年收入达36亿美元,超过大多数生成式AI初创公司的总和,这对组织在规划AI架构策略时提出了有关价值交付和实施效果的重要问题。

对于软件开发者、数据科学家和决策者而言,这种集成意味着更快的迭代、更低的成本,以及从构想到部署的更顺畅流程。虽然LLM有助于生成样板代码和自动化常规任务,但系统架构、安全性和性能仍然需要人工监督。案例研究表明,将自然语言接口集成进开发和运营流程的公司,已实现切实的业务价值,同时保持必要的人为引导。

代码专用大型语言模型(LLM)的演进

自代码专用大型语言模型问世以来,其发展经历了快速且显著的三个阶段,彻底改变了软件开发实践。

第一个是基础阶段(2021年至2022年初),这一阶段推出了首批可行的代码生成模型,验证了概念的可行性。随后进入扩展阶段(2022年底至2023年初),该阶段模型在推理能力和上下文理解方面实现了显著提升。最近的多样化阶段(2023年中至2024年)则见证了先进商业产品的涌现以及开源替代方案的不断壮大和增强能力。

这一演进过程呈现出专有和开源生态系统并行发展的态势。起初,商业模型占据主导地位,但近年来开源方案获得了极大的动力和关注。在此进程中,多个关键里程碑标志着能力的重大跃迁,为跨编程语言和不同任务的AI辅助开发打开了新的可能性。

理解这段演变的历史背景,对于掌握如何利用LangChain进行实现具有重要的启示作用。

image.png

图7.1展示了代码专用语言模型在商业生态(上轨)和开源生态(下轨)中的发展历程。图中突出标示了关键的里程碑,显示了从早期概念验证模型到越来越专业化解决方案的转变。时间线涵盖了早期商业模型如Codex,到2025年3月发布的谷歌Gemini 2.5 Pro,以及Mistral AI的Codestral系列等专业代码模型的最新进展。

近年来,针对编程特别微调的大型语言模型(通常称为代码LLM)呈爆发式增长。这些模型迅速发展,各有优势与局限,正在重塑软件开发格局。它们承诺能加速广泛的软件工程任务,包括:

  • 代码生成:将自然语言需求转化为代码片段或完整函数,例如根据项目规范生成样板代码或模块。
  • 测试生成:根据预期行为描述自动生成单元测试,提高代码可靠性。
  • 代码文档:自动从现有代码或规格中生成docstrings、注释和技术文档,大幅减轻在快节奏开发环境中常被忽视的文档负担。
  • 代码编辑与重构:自动建议改进、修复漏洞、重构代码以增强可维护性。
  • 代码翻译:在不同编程语言或框架间转换代码。
  • 调试和自动修复程序:识别大型代码库中的缺陷并生成补丁,如SWE-agent、AutoCodeRover和RepoUnderstander等工具通过遍历代码仓库、分析抽象语法树并应用针对性修改实现代码迭代优化。

代码专用LLM生态日益多元且复杂,这为生产环境中的开发者带来了关键挑战:

  • 哪种模型最适合特定编程任务?
  • 不同模型在代码质量、准确性和推理能力方面的差异如何?
  • 开源与商业方案之间的权衡是什么?

因此,基准测试成为评估和选择模型的重要工具。

代码LLM的基准测试

客观基准提供了标准化的方法,能跨越多种编程任务、语言和复杂度层级比较模型表现,有助于量化能力,避免主观臆断,从而实现数据驱动的实现决策。

对LangChain开发者而言,理解基准结果有以下优势:

  • 明智的模型选择:基于量化指标而非市场宣传或不完整测试选出最适合的模型。
  • 适当的工具链设计:根据模型优势与限制设计平衡能力和增强技术的LangChain流水线。
  • 成本效益分析:评估高端商业模型是否值得其成本,尤其与免费或自托管方案相比。
  • 性能预期设定:明确不同模型集成到大型系统时的实际表现范围。

代码生成LLM在既定基准上展现出多样化能力,这些性能特征直接影响其在LangChain中的应用效果。最近对领先模型(包括OpenAI的GPT-4o(2024)、Anthropic的Claude 3.5 Sonnet(2025)以及开源模型Llama 3)的评测显示了显著进步。例如,OpenAI的o1在HumanEval基准中达到92.4%的pass@1(《大型语言模型代码生成调查》,2025),而Claude 3 Opus在同一基准上达到84.9%(《Claude 3模型家族》,2024)。但性能指标也揭示了控制性基准环境与生产LangChain应用复杂需求之间的重要差异。

标准基准对LangChain实现的启示与局限

  • HumanEval:通过164个Python编程问题评估功能正确性,主要测试独立函数级生成,不涉及LangChain应用中典型的复杂多组件系统。
  • MBPP(基本编程问题集) :包含约974个入门级Python任务,缺乏生产环境中的依赖和上下文复杂性。
  • ClassEval:较新的基准测试类级代码生成,弥补了函数级测试的不足。刘等人(《类级代码生成中大型语言模型评估》,2024)研究表明其表现相比函数级任务下降15-30%,强调了跨方法维护上下文依赖的挑战,这对管理状态的LangChain组件尤为关键。
  • SWE-bench:更贴近实际开发场景,评估模型在GitHub真实仓库中的漏洞修复任务表现。Jimenez等人(《SWE-bench:语言模型能解决真实GitHub问题吗?》,2023)发现即便是表现最优的模型成功率也仅为40-65%,凸显了合成基准与真实编码挑战间的显著差距。

基于LLM的软件工程方法

在LangChain框架中实现代码生成型大型语言模型(LLM)时,会面临若干关键挑战。

仓库级别的问题:需要理解多个文件、依赖关系和上下文,这对LLM来说是重大挑战。使用ClassEval基准的研究(杜学颖等人,2024年《类级代码生成中大型语言模型评估》)显示,LLM在类级代码生成方面“显著比生成独立函数更具挑战性”,在管理方法间依赖关系时的表现持续低于函数级基准(如HumanEval)。

尽管存在挑战,LLM依然可以被用来理解仓库级别的代码上下文。以下实现示范了用LangChain分析多文件Python代码库的实用方法,将仓库文件作为上下文加载,供模型在实现新功能时参考。该模式通过直接向LLM提供仓库结构,缓解了上下文限制:

from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_community.document_loaders import GitLoader

# 加载仓库上下文
repo_loader = GitLoader(
    clone_url="https://github.com/example/repo.git",
    branch="main",
    file_filter=lambda file_path: file_path.endswith(".py")
)
documents = repo_loader.load()

# 创建具备上下文感知的提示
system_template = """你是一名资深Python开发者。请分析以下仓库文件并实现所需功能。仓库结构:{repo_context}"""
human_template = """请实现以下功能:{feature_request}"""

prompt = ChatPromptTemplate.from_messages([
    ("system", system_template),
    ("human", human_template)
])

# 使用扩展上下文窗口的模型
model = ChatOpenAI(model="gpt-4o", temperature=0.2)

该实现使用GPT-4o生成代码,同时考虑整个仓库的上下文,加载相关Python文件以理解依赖关系。此方法有效解决上下文限制,但对于大型代码库需要仔细设计文档切分与检索策略。

生成的代码表面上看似正确,但往往包含细微的缺陷或安全漏洞,难以初步检测。Uplevel Data Labs的研究(《生成式AI是否真正提升开发者生产力?》)分析了近800名开发者,发现使用AI编码助手的开发者生成代码的错误率明显更高。2024年BlueOptima对218,000多名开发者的综合分析(《驳斥GitHub Copilot研究的结论》)也显示,88%的专业人员需大幅重构AI生成的代码才能达到生产标准,常因“异常编码模式”未被及时发现。

安全研究人员发现,AI模型可能无意间复制训练数据中的不安全模式,带来持续的安全风险,这些漏洞常常逃避语法和编译检查(见2024年《基于角色引导与自我反思的大型语言模型评估》和《HalluLens:LLM幻觉基准》)。这凸显了在生产部署前,必须进行严格人工审查与测试的重要性。

以下示例展示了如何创建专门的验证链,系统地分析生成代码的常见问题,作为防范细微缺陷和漏洞的第一道防线:

from langchain.prompts import PromptTemplate

validation_template = """请分析以下Python代码,检查:
1. 潜在安全漏洞
2. 逻辑错误
3. 性能问题
4. 边缘情况处理
代码如下:
```python
{generated_code}
请给出详细分析,指出具体问题并提出修复建议。
"""

validation_prompt = PromptTemplate(
    input_variables=["generated_code"],
    template=validation_template
)

validation_chain = validation_prompt | llm

该验证方法在工作流中创建了基于LLM的专门代码审查步骤,关注安全与质量关键点。

多数成功实现方案包含执行反馈机制,允许模型基于编译错误和运行时行为迭代改进输出。Boyan Li等人(2024年《自然语言到SQL的曙光:我们准备好了吗?》)研究表明,纳入反馈机制显著提升查询生成准确度,使用执行结果优化输出的系统持续超越无反馈系统。

在生产环境部署代码生成LLM时,应关注以下因素:

  • 模型选择权衡:闭源模型如GPT-4和Claude在代码基准表现优异,但开源替代品如Llama 3(HumanEval上70.3%)在成本、延迟和数据隐私方面更具优势。具体选择依赖于准确度需求、部署限制和预算。
  • 上下文窗口管理:有效处理有限上下文窗口至关重要。近期技术如递归切分和分层摘要(Li等,2024)可提升大代码库任务表现高达25%。
  • 框架集成:通过专用工具(如LangChain)扩展基础LLM能力,实现工作流管理。采用定制安全策略及反馈循环,确保模型输出持续改进。该集成方式使团队在享受基础模型进步的同时,保持对部署细节的掌控。
  • 人机协作:明确开发者与AI系统的职责分工。该模式保持人工对关键决策的监督,同时将常规任务交由AI助手处理。系统化文档与知识管理确保AI生成方案对整个团队可理解且易维护。成功应用该模式的公司报告生产力提升及团队知识传递效果改善。

安全与风险缓解

在使用LangChain构建基于大型语言模型(LLM)的应用时,实施健全的安全措施和风险缓解策略至关重要。本节聚焦于通过LangChain特定实现,实用地解决安全漏洞、防止幻觉问题以及保障代码质量的方法。

LLM生成代码中的安全漏洞带来重大风险,尤其涉及用户输入、数据库交互或API集成时。LangChain允许开发者创建系统化的验证流程,以识别和降低这些风险。以下验证链可集成至任何涉及代码生成的LangChain工作流,在部署前提供结构化的安全分析:

from typing import List
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

# 定义用于结构化输出的Pydantic模型
class SecurityAnalysis(BaseModel):
    """生成代码的安全分析结果。"""
    vulnerabilities: List[str] = Field(description="识别出的安全漏洞列表")
    mitigation_suggestions: List[str] = Field(description="针对每个漏洞的修复建议")
    risk_level: str = Field(description="整体风险评估:低、中、高、严重")

# 使用Pydantic模型初始化输出解析器
parser = PydanticOutputParser(pydantic_object=SecurityAnalysis)

# 创建带有格式说明的提示模板
security_prompt = PromptTemplate.from_template(
    template="""请分析以下代码中的安全漏洞:{code}
考虑因素:
- SQL注入漏洞
- 跨站脚本攻击(XSS)风险
- 不安全的直接对象引用
- 认证与授权弱点
- 敏感数据泄露
- 缺失输入验证
- 命令注入风险
- 不安全的依赖使用
{format_instructions}""",
    input_variables=["code"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 初始化语言模型
llm = ChatOpenAI(model="gpt-4", temperature=0)

# 通过LCEL构建验证链
security_chain = security_prompt | llm | parser

Pydantic输出解析器保证结果结构规范,便于程序化处理和自动化把关。LLM生成的代码切勿未经验证直接在生产环境中执行。LangChain提供了工具,可创建安全的执行环境用于测试生成代码。

构建处理代码的LangChain应用时,保障安全需要多层防护,结合基于LLM的验证和传统安全工具以实现稳健防御。使用Pydantic模型和LangChain输出解析器来结构化安全发现,确保输出一致且可操作。始终在资源受限的沙箱环境中隔离执行LLM生成的代码,切勿直接运行于生产环境。显式管理依赖关系,验证导入是否匹配可用包,避免幻觉误导。通过反馈循环持续优化代码生成,结合执行结果和验证发现。完整记录代码生成步骤、安全检测和修改操作,方便审计。遵循最小权限原则,生成遵守安全最佳实践的代码,如权限最小化和适当的输入验证。最后,利用版本控制存储生成代码,并对关键组件实施人工审核。

LLM生成代码的验证框架

组织在将LLM生成的代码和分析投入生产前,应实施结构化的验证流程。以下框架为采纳LLM于数据科学工作流的团队提供实用指导:

  • 功能验证 是任何评估流程的基础。首先使用代表性测试数据执行生成的代码,仔细核对输出是否符合预期结果。确保所有依赖项正确导入且与生产环境兼容——LLM偶尔会引用过时或不兼容的库。最重要的是确认代码确实满足原始业务需求,因为LLM有时会生成看似出色却偏离核心业务目标的代码。
  • 性能评估 不仅关注功能。对比LLM生成代码与现有方案的执行时间,以发现潜在的效率问题。使用逐渐增大的数据集测试常揭示出样本数据未显现的扩展性瓶颈。系统性分析内存使用情况,因LLM默认可能不优化资源限制。此性能数据对部署决策至关重要,同时有助发现优化空间。
  • 安全筛查 绝不可忽视。扫描不安全函数、潜在注入漏洞和不安全的API调用——尽管LLM训练中涵盖安全编码,仍可能引入问题。验证认证凭证和敏感数据的正确处理,尤其当模型被指示包含API访问时。仔细检查硬编码的秘密信息或无意间的数据泄露,这些都可能在生产环境引发安全风险。
  • 健壮性测试 超越理想路径,验证代码在边界条件和意外输入下的表现。确保错误处理机制完善,能提供有意义的反馈,而非神秘失败。评估代码对格式错误或缺失数据的容忍度,因为生产环境中数据往往不如开发时理想。
  • 业务逻辑验证 聚焦领域特定要求,LLM可能无法完全理解。确认行业特定限制和业务规则的正确执行,尤其是各行业差异显著的监管要求。核对关键流程的计算和转换与人工计算结果一致,细微数学差异会显著影响业务。确保所有相关法规和政策得到满足,因LLM可能缺乏领域合规知识。
  • 文档与可解释性 补全验证过程,保证代码可持续使用。要求LLM提供或另外生成内嵌注释,解释复杂代码和算法选择。记录模型的假设,避免影响未来维护或升级。编制验证报告,将代码功能与业务需求直接关联,支持技术和业务双方追踪。

该验证框架应集成于开发工作流中,并尽可能自动化以减少人工工作。采纳LLM的组织应从明确对齐业务目标的用例出发,系统性实施验证流程,投入全面的员工培训(涵盖LLM能力和限制),并建立与技术同步演进的治理框架。

LangChain集成

众所周知,LangChain支持构建多功能且健壮的AI代理。例如,集成LangChain的代理能通过专用解释器安全执行代码,连接SQL数据库实现动态数据检索,进行实时财务分析,并始终保持严格的质量和安全标准。

集成涵盖代码执行与隔离、数据库查询、金融分析、代码仓库管理等多个领域。这套丰富的工具集促进了与现实世界数据和系统的深度结合,确保AI解决方案既强大又实用。以下是部分集成示例:

  • 代码执行与隔离:Python REPL、Azure Container Apps动态会话、Riza代码解释器和Bearly代码解释器等工具提供安全的执行环境。它们使LLM能将复杂计算或数据处理任务委派给专用解释器,提高准确性和可靠性的同时保障安全。
  • 数据库与数据处理:Cassandra、SQL及Spark SQL工具包允许代理直接与多种数据库交互。JSON工具包和pandas DataFrame集成支持高效处理结构化数据。这些能力对需动态检索、转换和分析数据的应用至关重要。
  • 金融数据与分析:FMP Data、Google Finance和FinancialDatasets工具包让开发者构建具备复杂金融分析与市场调研能力的AI代理。Dappier扩展功能,连接到精选的实时数据流。
  • 代码仓库与版本控制集成:GitHub和GitLab工具包使代理能与代码仓库交互,简化问题管理、代码审查和部署流程,对于现代DevOps环境的开发者来说尤为重要。
  • 用户输入与可视化:Google Trends和PowerBI工具包体现了生态系统在引入外部数据(如市场趋势)并有效可视化方面的关注。“人作为工具”集成则提醒我们,在模糊场景中,人类判断依然不可或缺。

探索完理论框架和LLM辅助软件开发的潜在优势后,接下来我们将转向实际实现。在下一节,我们将演示如何利用LLM生成功能性软件代码,并直接在LangChain框架中执行。这种实践操作将具体展示前述概念,并提供可适用于您项目的实用示例。

用LLM编写代码

本节演示如何使用集成在LangChain中的各种模型进行代码生成。我们选择了不同的模型来展示:

  • LangChain与多种AI工具的多样集成能力
  • 不同许可和可用性的模型
  • 支持本地部署的选项,包括体积较小的模型

这些示例展示了LangChain在处理各种代码生成模型时的灵活性,涵盖了从云端服务到开源替代方案的多种选择。这样的方式让你能够了解可用方案的广度,并选择最适合你具体需求和限制的解决方案。

请确保你已安装本书第2章中说明的所有依赖,否则可能会遇到运行问题。

鉴于该领域的发展速度及LangChain库的持续更新,我们正努力保持GitHub仓库的最新状态,地址为:github.com/benman1/gen…

如有任何疑问或运行问题,请在GitHub上提交issue,或加入Discord讨论:packt.link/lang

Google生成式AI

Google生成式AI平台提供一系列专为指令跟随、转换以及代码生成/辅助设计的模型。这些模型具有不同的输入/输出限制和训练数据,且经常更新。下面我们看看Gemini Pro模型能否解决FizzBuzz问题,这是面向初级软件开发者面试中常见的题目。

我们将用LangChain调用Gemini Pro,提供FizzBuzz问题描述,测试其代码生成能力:

from langchain_google_genai import ChatGoogleGenerativeAI

question = """
给定一个整数 n,返回一个字符串数组 answer(从1开始索引),
满足以下条件:
如果 i 能同时被3和5整除,则 answer[i] == "FizzBuzz"。
如果 i 能被3整除,则 answer[i] == "Fizz"。
如果 i 能被5整除,则 answer[i] == "Buzz"。
如果上述条件都不满足,则 answer[i] == i 的字符串形式。
"""

llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro")
print(llm.invoke(question).content)

Gemini Pro立刻返回了一个干净且正确的Python解决方案,完美实现了FizzBuzz的所有要求:

answer = []
for i in range(1, n+1):
    if i % 3 == 0 and i % 5 == 0:
        answer.append("FizzBuzz")
    elif i % 3 == 0:
        answer.append("Fizz")
    elif i % 5 == 0:
        answer.append("Buzz")
    else:
        answer.append(str(i))
return answer

该模型生成的方案高效且结构良好,正确实现了FizzBuzz逻辑,没有错误或多余的复杂度。你会考虑将Gemini Pro纳入你的团队吗?

Hugging Face

Hugging Face 托管了大量开源模型,其中许多专门针对代码进行了训练,有些模型可以在 Playground 中试用,你可以让它们完成(针对旧模型)或编写代码(指令调优模型)。通过 LangChain,你可以选择下载这些模型并在本地运行,也可以通过 Hugging Face API 访问它们。我们先尝试本地运行的方案,示例为计算素数:

from langchain.llms import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

# 选择一个较新的模型
checkpoint = "google/codegemma-2b"

# 加载模型和分词器
model = AutoModelForCausalLM.from_pretrained(checkpoint)
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

# 创建文本生成流水线
pipe = pipeline(
    task="text-generation",
    model=model,
    tokenizer=tokenizer,
    max_new_tokens=500
)

# 将流水线集成到 LangChain
llm = HuggingFacePipeline(pipeline=pipe)

# 定义输入文本
text = """
def calculate_primes(n):
    """创建一个从2到N的连续整数列表。
    例如:
    >>> calculate_primes(20)
    输出: [2, 3, 5, 7, 11, 13, 17, 19]
    """
"""

# 使用 LangChain 的 LLM 生成文本
output = llm(text)
print(output)

运行时,CodeGemma 会通过实现埃拉托斯特尼筛法(Sieve of Eratosthenes)来补全函数,这是查找素数的经典高效算法。模型正确理解了文档字符串,知道函数应返回小于等于 n 的所有素数,而不是仅判断某个数字是否为素数。生成的代码展示了专门针对代码训练的模型如何从简单的说明中产出可用实现。

请注意,下载和加载模型可能需要几分钟时间。

如果你在用 LangChain 访问 URL 时遇到“cannot access a gated repo”的错误,这意味着你尝试访问 Hugging Face 上的私有仓库,该仓库需要通过个人访问令牌(Personal Access Token)进行身份验证才能查看或使用。你需要创建 Hugging Face 访问令牌,并将其设置为名为 "HF_TOKEN" 的环境变量。可以在 Hugging Face 网站 huggingface.co/docs/api-in… 上获取令牌。

当上例代码成功运行 CodeGemma 时,它会生成一个完整的素数计算器实现,输出类似如下:

def calculate_primes(n):
    """创建一个从2到N的连续整数列表。
    例如:
    >>> calculate_primes(20)
    输出: [2, 3, 5, 7, 11, 13, 17, 19]
    """
    primes = []
    for i in range(2, n + 1):
        if is_prime(i):
            primes.append(i)
    return primes

def is_prime(n):
    """如果 n 是素数,返回 True。"""
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def main():
    """获取用户输入并打印素数列表。"""
    n = int(input("Enter a number: "))
    primes = calculate_primes(n)
    print(primes)

if __name__ == "__main__":
    main()

你可以看到,模型不仅实现了请求的 calculate_primes() 函数,还创建了辅助函数 is_prime(),该函数使用更高效的算法仅检测到数字平方根范围内的可整除性。模型甚至添加了完整的 main() 函数来处理用户输入,展示了它对Python编程模式的理解。

与其下载并在本地运行模型(这需要大量计算资源),我们还可以通过 Hugging Face 的推理API直接在他们的基础设施上运行模型。这种方式更易部署,不需要强大的硬件。以下是使用 Hugging Face 托管服务实现同样示例的代码:

from langchain.llms import HuggingFaceHub

# 选择适合代码生成的轻量级模型
repo_id = "bigcode/starcoder"

# 初始化 HuggingFaceHub LLM
llm = HuggingFaceHub(
    repo_id=repo_id,
    task="text-generation",
    model_kwargs={
        "temperature": 0.5,
        "max_length": 1000
    }
)

# 使用 LangChain LLM 生成文本
output = llm.invoke(text)
print(output)

运行时,该代码会连接到 Hugging Face 的服务器,调用专门针对代码训练的大规模模型 StarCoder。输出预计与前例类似——完整实现素数计算器函数,但算法实现可能不同,因为使用了不同的模型。此托管方式牺牲了一部分灵活性和控制权,但带来便利性和减少本地资源需求,适合快速原型开发或硬件受限场景。

Anthropic

Anthropic 的 Claude 系列模型在与编程相关的任务中表现尤为出色。我们来看一下 Claude 处理一个编程任务的效果:

from langchain_anthropic import ChatAnthropic
from langchain_core.prompts.prompt import PromptTemplate

template = """Question: {question}
Let's think step by step.
Answer:
"""
prompt = PromptTemplate(template=template, input_variables=["question"])

llm = ChatAnthropic(model='claude-3-opus-20240229')
llm_chain = prompt | llm

print(llm_chain.invoke(text))

我们得到的算法和非常详细的解释如下:

为计算小于等于给定数字 N 的所有素数,我们可以使用埃拉托斯特尼筛法(Sieve of Eratosthenes)。以下是解决该问题的逐步方法:

def calculate_primes(n):
    """创建一个从2到N的连续整数列表。"""
    # 创建从2到N的连续整数列表
    numbers = list(range(2, n + 1))
    # 初始化空列表存储素数
    primes = []
    while numbers:
        # 取列表中的第一个数作为素数
        prime = numbers[0]
        primes.append(prime)
        # 从列表中移除所有该素数的倍数
        numbers = [num for num in numbers if num % prime != 0]
    return primes

逐步解释:

  1. 使用 range() 函数创建一个名为 numbers 的列表,包含从 2 到 N 的连续整数。
  2. 初始化一个空列表 primes,用于存储计算出的素数。
  3. 开始一个循环,只要 numbers 列表中还有数字,就继续执行。
  4. 在循环内,取出 numbers 列表的第一个数字,视为素数,将其添加到 primes 列表。
  5. 通过列表推导式从 numbers 中移除所有该素数的倍数,生成一个只包含不被当前素数整除的数字的新列表。
  6. 重复第 4 和第 5 步,直到 numbers 列表为空。这样确保了对所有数字进行了筛选,找出了所有素数。
  7. 最后返回 primes 列表,即所有小于等于 N 的素数。

测试函数示例:

print(calculate_primes(20))

输出:

[2, 3, 5, 7, 11, 13, 17, 19]

函数正确返回了小于等于 20 的素数列表。此实现再次采用了埃拉托斯特尼筛法。

代理方法

我们还可以创建一个能够执行 Python 代码以解决问题的 LLM 代理:

from langchain_openai import ChatOpenAI
from langchain.agents import load_tools, initialize_agent, AgentType
from langchain_experimental.tools import PythonREPLTool

tools = [PythonREPLTool()]  # 赋予代理执行 Python 代码的能力
llm = ChatOpenAI()

# 配置代理,指定所需工具和模型
agent = initialize_agent(
    tools,
    llm,  # 用于驱动代理的语言模型
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True  # 显示代理的思考过程
)  # 代理在无示例情况下做决策

result = agent("What are the prime numbers until 20?")
print(result)

代理的工作流程:

  1. 判断需要编写的 Python 代码。
  2. 使用 PythonREPLTool 执行代码。
  3. 返回执行结果。

运行时,它会展示推理步骤和代码执行过程,最终给出答案。预期输出示例如下:

> Entering new AgentExecutor chain...
I can write a Python script to find the prime numbers up to 20.
Action: Python_REPL
Action Input: def is_prime(n):
if n <= 1:
    return False
for i in range(2, int(n**0.5) + 1):
    if n % i == 0:
        return False
return True
primes = [num for num in range(2, 21) if is_prime(num)]
print(primes)
Observation: [2, 3, 5, 7, 11, 13, 17, 19]
I now know the final answer
Final Answer: [2, 3, 5, 7, 11, 13, 17, 19]
> Finished chain.
{'input': 'What are the prime numbers until 20?', 'output': '[2, 3, 5, 7, 11, 13, 17, 19]'}

文档 RAG

另一个非常有趣的用法是利用文档来帮助写代码或查询文档相关问题。下面是一个示例,展示如何使用 DocusaurusLoader 加载 LangChain 官网的所有文档页面:

from langchain_community.document_loaders import DocusaurusLoader
import nest_asyncio

nest_asyncio.apply()
# 加载 LangChain 文档的所有页面
loader = DocusaurusLoader("https://python.langchain.com")
documents = loader.load()
documents[0]

nest_asyncio.apply() 允许在 Jupyter Notebook 中运行异步操作。DocusaurusLoader 会自动爬取并提取 LangChain 文档网站内容,专门针对基于 Docusaurus 构建的网站,能正确提取格式化后的内容。由于 Jupyter Notebook 的 asyncio 事件循环有限制,nest_asyncio.apply() 这行代码允许在 notebook 单元格中执行异步代码,这是许多网页爬取操作所必须的。执行完毕后,documents 变量包含所有文档页面,每个页面都表示为一个 Document 对象,拥有 page_contentmetadata 等属性。

接下来,我们设置带缓存的向量嵌入:

from langchain.embeddings import CacheBackedEmbeddings
from langchain_openai import OpenAIEmbeddings
from langchain.storage import LocalFileStore

# 本地缓存嵌入向量,避免重复调用 API
store = LocalFileStore("./cache/")
underlying_embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
embeddings = CacheBackedEmbeddings.from_bytes_store(
    underlying_embeddings, store, namespace=underlying_embeddings.model
)

在将文档输入向量库之前,我们需要对文档进行拆分,正如第4章所述:

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=20,
    length_function=len,
    is_separator_regex=False,
)
splits = text_splitter.split_documents(documents)

然后,从拆分后的文档创建向量存储:

from langchain_chroma import Chroma

# 为文档生成嵌入并存储以便高效检索
vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)

初始化 LLM 或聊天模型:

from langchain_google_vertexai import VertexAI

llm = VertexAI(model_name="gemini-pro")

设置 RAG 相关组件:

from langchain import hub

retriever = vectorstore.as_retriever()
# 使用社区创建的 RAG 提示模板
prompt = hub.pull("rlm/rag-prompt")

最后,构建 RAG 链:

from langchain_core.runnables import RunnablePassthrough

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 链条结合上下文检索、提示和生成响应
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

我们可以调用链条进行查询:

response = rag_chain.invoke("What is Task Decomposition?")

每个组件环环相扣,最终构建了一个完整的 RAG 系统,可以基于 LangChain 文档回答问题。

代码仓库 RAG

RAG 系统的一个强大应用是分析代码仓库,以支持针对代码库的自然语言查询。这项技术帮助开发者快速理解陌生代码或找到相关的实现示例。下面我们通过索引一个 GitHub 代码仓库,来构建一个以代码为中心的 RAG 系统。

首先,我们克隆仓库并设置环境:

import os
from git import Repo
from langchain_community.document_loaders.generic import GenericLoader
from langchain_community.document_loaders.parsers import LanguageParser
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter

# 从 GitHub 克隆书籍代码仓库
repo_path = os.path.expanduser("~/Downloads/generative_ai_with_langchain")  # 确保该目录尚不存在!
repo = Repo.clone_from("https://github.com/benman1/generative_ai_with_langchain", to_path=repo_path)

克隆完仓库后,我们需要用 LangChain 的专用加载器解析 Python 文件,保持代码结构的语义信息。LanguageParser 有助于在处理过程中保持代码语义:

loader = GenericLoader.from_filesystem(
    repo_path,
    glob="**/*",
    suffixes=[".py"],
    parser=LanguageParser(language=Language.PYTHON, parser_threshold=500),
)
documents = loader.load()

python_splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=50, chunk_overlap=0
)

# 将文档拆分为小块,便于做嵌入和向量存储
texts = python_splitter.split_documents(documents)

这段代码完成了三项核心操作:克隆我们的书籍 GitHub 仓库,使用语言感知解析器加载所有 Python 文件,并将代码拆分成更小、语义上有意义的片段。语言特定的拆分器确保在可能的情况下保留函数和类的定义,提高检索效果。

接下来,我们通过对这些代码块生成嵌入,并设置检索链,来创建 RAG 系统:

# 创建向量存储和检索器
db = Chroma.from_documents(texts, OpenAIEmbeddings())
retriever = db.as_retriever(
    search_type="mmr",  # 最大边际相关性,提升结果多样性
    search_kwargs={"k": 8}  # 返回 8 个最相关的块
)

# 设置问答链
prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer based on context:\n\n{context}"),
    ("placeholder", "{chat_history}"),
    ("user", "{input}"),
])

# 创建链组件
document_chain = create_stuff_documents_chain(ChatOpenAI(), prompt)
qa = create_retrieval_chain(retriever, document_chain)

这里我们构建了完整的 RAG 流程:将代码嵌入存储到 Chroma 向量数据库,配置使用最大边际相关性的检索器(帮助返回多样化结果),创建一个问答链,将检索到的代码结合提示模板传给 LLM。

现在让我们用一个关于软件开发示例的问题测试这个代码感知的 RAG 系统:

question = "What examples are in the code related to software development?"
result = qa.invoke({"input": question})
print(result["answer"])

得到的回答可能是:

Here are some examples of the code related to software development in the given context:
1. Task planner and executor for software development: This indicates that the code includes functionality for planning and executing tasks related to software development.
2. debug your code: This suggests that there is a recommendation to debug the code if an error occurs during software development.
These examples provide insights into the software development process described in the context.

这个回答有些有限,可能是因为我们设置的块大小较小(50 字符)导致代码示例被拆分得较为零散。尽管系统正确识别了任务规划和调试的相关描述,但没有给出具体代码示例或上下文细节。在生产环境中,您可能希望增加块大小,或实现分层拆分以保留更多上下文。此外,使用针对代码的专用嵌入模型可能进一步提升检索结果的相关性。

下一节,我们将探讨生成式 AI 代理如何自动化并增强数据科学工作流程。LangChain 代理不仅能编写和执行代码,还能分析数据集,甚至在极少人类指导下构建和训练机器学习模型。我们会演示两个强大的应用案例:训练神经网络模型和分析结构化数据集。

应用大语言模型(LLM)代理于数据科学

将大语言模型(LLM)集成到数据科学工作流中,代表了一种重要且细致的演变,改变了分析任务的处理方式。尽管传统的数据科学方法在复杂的数值分析中仍然不可或缺,LLM 主要通过提升可访问性和辅助工作流中的特定环节,提供了有力的补充能力。

独立研究显示,LLM 的实际效果较某些厂商宣称的更为理性。多项研究表明,LLM 在不同数据科学任务中的表现差异较大,且随着任务复杂性的增加,性能往往下降。《PLOS One》期刊的一项研究指出,“随着数据分析任务复杂度的提升,生成代码的可执行性显著降低”,揭示了当前模型处理复杂分析挑战的局限性。

与传统方法相比,LLM 在数据处理上的核心差异体现在关注点上。传统统计技术擅长处理结构化、表格化数据,基于明确的数学关系进行计算;而 LLM 在处理非结构化文本方面表现更优。它们能生成常见数据科学任务的代码,尤其是数据操作、可视化和常规统计分析等模板化操作。GitHub Copilot 等工具的研究显示,这些助手能显著加速开发进程,尽管独立研究中观察到的生产力提升(通常为7%至22%)远低于部分厂商声称的水平。BlueOptima 对超过218,000名开发者的分析表明,真实生产环境中的生产力提升约为4%,远低于实验室控制环境中报告的55%。

文本转 SQL 是最有前景的应用之一,可能通过允许非技术用户以自然语言查询数据库,极大地普及数据访问。但其在更现实的 BIRD 基准测试中性能通常低于 Spider,准确率依赖于查询复杂度、数据库模式和所用基准,仍是关键问题。

LLM 还擅长将技术发现转化为非技术人员易懂的叙述,成为数据驱动组织中的沟通桥梁。尽管 InsightLens 等系统已实现自动化洞察组织,但生成不同类型内容时仍表现出明显优势与局限。特别是在合成数据方面,LLM 能有效生成定性文本样本,却难以处理需要复杂统计关系的结构化数值数据。这种性能界限反映了其核心文本处理能力,也凸显了传统统计方法的优势。发表于 JAMIA 的研究(2024 年)指出,“LLM(尤其是 GPT-4,而非 GPT-3.5)在社交媒体健康文本分类任务中有效用于数据增强,但单独用于监督模型训练数据标注时效果有限。”

综合来看,未来将是 LLM 与传统数据分析工具共存互补的局面。最有效的方案可能是混合系统,结合:

  • LLM 负责自然语言交互、代码辅助、文本处理和初步探索
  • 传统统计和机器学习技术负责对结构化数据的严格分析和关键预测任务

LLM 带来的变革让技术和非技术相关方都能更高效地与数据互动。其主要价值在于减轻重复编码任务带来的认知负担,使数据科学家能保持思路连贯,专注于更高阶的分析挑战。但严格的验证仍不可或缺——独立研究持续指出代码质量、安全性和可维护性的问题。这些问题在 LangChain 革新过的两大关键工作流中尤为重要:机器学习模型训练和数据集分析。

在机器学习模型训练中,LLM 现能生成合成训练数据、辅助特征工程和自动调优超参数,大幅降低模型开发门槛。同时,在数据分析中,LLM 作为智能接口,将自然语言问题转化为代码、可视化和洞察,使领域专家无需深厚编程知识即可提取数据价值。接下来的章节将结合 LangChain 展示这两大领域的应用。

机器学习模型训练

如您所知,LangChain 代理可以编写并执行 Python 代码,完成数据科学任务,包括构建和训练机器学习模型。这种能力尤为宝贵,当您需要执行复杂的数据分析、制作可视化或即时实现定制算法,而无需切换上下文时。

本节将通过两个主要步骤,探讨如何创建和使用具备 Python 能力的代理:搭建 Python 代理环境及配置合适的模型和工具;从零开始实现神经网络,指导代理构建一个完整的工作模型。

搭建支持 Python 的代理

我们先使用 LangChain 的实验性工具创建一个支持 Python 的代理:

from langchain_experimental.agents.agent_toolkits.python.base import create_python_agent
from langchain_experimental.tools.python.tool import PythonREPLTool
from langchain_anthropic import ChatAnthropic
from langchain.agents.agent_types import AgentType

agent_executor = create_python_agent(
    llm=ChatAnthropic(model='claude-3-opus-20240229'),
    tool=PythonREPLTool(),
    verbose=True,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
)

这段代码创建了一个基于 Claude 3 Opus 模型的 Python 代理,该模型在处理复杂编程任务时具有较强的推理能力。PythonREPLTool 为代理提供了一个 Python 执行环境,使其能够编写和运行代码,查看输出,并根据结果进行迭代。设置 verbose=True 允许我们观察代理的思考过程,有助于理解其解决方案和调试。

安全注意事项

PythonREPLTool 以与您的应用程序相同的权限执行任意 Python 代码。虽然它非常适合开发和演示,但在生产环境中存在重大安全风险。对于生产部署,建议考虑:

  • 使用受限执行环境,例如 RestrictedPython 或 Docker 容器
  • 实现具有明确权限边界的自定义工具
  • 在权限有限的独立隔离服务中运行代理
  • 在执行生成的代码之前添加验证和清理步骤

另一方面,AgentExecutor 是 LangChain 的一个组件,负责协调代理的执行循环。它管理代理的决策过程,处理与工具的交互,执行迭代限制,并处理代理的最终输出。可以把它看作代理运行的运行时环境。

让代理构建神经网络

现在我们已经搭建好 Python 代理,接下来用一个实际的机器学习任务测试它的能力。我们将让代理实现一个简单的神经网络,用于学习基本的线性关系。这个示例展示了代理如何处理从数据生成到模型训练和评估的端到端机器学习开发任务。

下面的代码指示代理用 PyTorch 创建一个单神经元神经网络,使用表示函数 y=2x 的合成数据训练 1000 个周期,并每 100 个周期打印一次训练进度,最后对 x=5 进行预测:

result = agent_executor.run(
    """Understand, write a single neuron neural network in PyTorch.
    Take synthetic data for y=2x. Train for 1000 epochs and print every 100 epochs.
    Return prediction for x = 5"""
)
print(result)

这段简洁的提示指导代理实现完整的神经网络流程:生成单神经元模型的 PyTorch 代码,创建符合 y=2x 的合成训练数据,训练模型 1000 个周期并定期输出训练进度,最后对新输入 x=5 进行预测。

代理执行及结果

当我们运行这段代码时,代理开始推理问题并执行 Python 代码。以下是简化后的详细输出,展示了代理的思考过程和代码执行:

进入新的 AgentExecutor 链...

这是一个用 PyTorch 编写的单神经元神经网络,它在表示 y=2x 的合成数据上训练,每100个周期打印一次损失,并返回 x=5 的预测结果:

操作:Python_REPL
操作输入:

import torch
import torch.nn as nn
# 创建合成数据
X = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[2.0], [4.0], [6.0], [8.0]])
# 定义模型
[...]  # 省略模型创建代码

观察结果:

Epoch [100/1000], Loss: 0.0529
[...]  # 省略200-900周期的训练进展
Epoch [1000/1000], Loss: 0.0004
对x=5的预测结果:9.9659

总结如下:

  • 我用 nn.Linear(1, 1) 在 PyTorch 中创建了一个单神经元神经网络模型
  • 生成了符合 y=2x 关系的合成数据用于训练
  • 定义了均方误差(MSE)损失函数和随机梯度下降(SGD)优化器
  • 训练了1000个周期,每100个周期打印一次损失
  • 训练结束后,对 x=5 进行了预测

x=5 的最终预测值是 9.9659,接近预期值 10(因为 y=2x)。

总结来说,我成功训练了一个简单的单神经元 PyTorch 模型,能够很好地拟合合成的 y=2x 数据,并对新的输入 x=5 做出准确的预测。

最终答案:训练好的单神经元 PyTorch 模型预测 x=5 的值为 9.9659。

链结束。

最终输出确认我们的代理成功构建并训练了学习 y=2x 关系的模型。对 x=5 的预测约为 9.97,十分接近预期的 10。

结果表明代理成功完成了神经网络的构建和训练。预测值与预期值非常接近,证明模型有效地从合成数据中学习到了线性关系。

如果代理的结果不理想,可以考虑:增加提示语的具体细节(如指定学习率或模型架构)、添加验证步骤(例如绘制损失曲线)、降低 LLM 的温度以获得更确定性的结果,或将复杂任务拆分成多个顺序提示。

这个示例展示了 LangChain 代理如何在最少人工干预下,成功实现机器学习工作流。代理表现出强大的能力:理解任务需求、无需示例生成正确的 PyTorch 代码、创建合适的合成数据、配置并训练神经网络、以及根据预期结果评估性能。

在真实场景中,你可以将此方法扩展到更复杂的机器学习任务,如分类问题、时间序列预测,甚至定制模型架构。接下来,我们将探讨代理如何辅助数据分析和可视化任务,这些任务建立在上述基础机器学习能力之上。

分析数据集

接下来,我们将演示 LangChain 代理如何通过分析著名的鸢尾花(Iris)数据集来处理结构化数据。鸢尾花数据集由英国统计学家 Ronald Fisher 创建,包含了三种鸢尾花的萼片长度、萼片宽度、花瓣长度和花瓣宽度的测量数据。该数据集在机器学习中的分类任务中被广泛使用。

创建 pandas DataFrame 代理

数据分析是 LLM 代理的理想应用场景。让我们探索如何创建一个专门处理 pandas DataFrame 的代理,使其能够通过自然语言与表格数据交互。

首先,我们加载经典的鸢尾花数据集,并将其保存为 CSV 文件,供代理使用:

from sklearn.datasets import load_iris
df = load_iris(as_frame=True)["data"]
df.to_csv("iris.csv", index=False)

接着,我们创建一个专门用于 pandas DataFrame 的代理:

from langchain_experimental.agents.agent_toolkits.pandas.base import create_pandas_dataframe_agent
from langchain import PromptTemplate

PROMPT = (
    "If you do not know the answer, say you don't know.\n"
    "Think step by step.\n"
    "\n"
    "Below is the query.\n"
    "Query: {query}\n"
)
prompt = PromptTemplate(template=PROMPT, input_variables=["query"])
llm = OpenAI()
agent = create_pandas_dataframe_agent(
    llm, df, verbose=True, allow_dangerous_code=True
)

安全警告

这里使用了 allow_dangerous_code=True,允许代理在你的机器上执行任意 Python 代码。如果代理生成恶意代码,这可能带来安全风险。此选项应仅在开发环境下、且数据源可信的情况下使用,切勿在生产环境中使用,除非有适当的沙箱保护。

上述示例适用于像鸢尾花这样的小型数据集(150 行),但现实中的数据分析往往涉及更大规模的数据,超出 LLM 的上下文窗口容量。生产环境中实现 DataFrame 代理时,可以通过以下策略克服限制。

数据摘要和预处理

发送数据给代理前,建议先提取关键统计信息,比如数据形状、列名、数据类型和摘要统计量(均值、中位数、最大值等)。还可以包括代表性样本,如开头和结尾的几行数据或小批随机样本,以提供上下文信息,同时避免超出 LLM 的令牌限制。这种预处理既保留了关键信息,也大幅减小了输入数据量。

分块策略

对于超出单一上下文窗口的大型数据集,可以将数据拆分成多个可管理的块,分别让代理处理每个块,再汇总结果。汇总逻辑依赖具体分析任务,例如针对优化查询可以汇总块级结果的全局最大值,复杂任务则可结合部分分析结果。这种做法牺牲部分全局上下文,但可处理任意规模数据。

针对查询的预处理

根据问题类型调整预处理策略。统计类问题可先做聚合,再发送给代理。相关性问题可提前计算并提供相关矩阵,帮助 LLM 聚焦于解释而非计算。探索性问题则可仅提供数据集元数据和样本。这种针对性预处理有效利用上下文窗口,仅包含与具体查询相关的信息。

关于数据集提问

现在我们已经搭建好了数据分析代理,让我们通过对数据集提出逐步复杂的问题来探索它的能力。一个设计良好的代理应该能够处理不同类型的分析任务,从基础探索、统计计算到可视化分析。以下示例展示了我们的代理如何处理经典的鸢尾花(Iris)数据集,该数据集包含了花的各种特征测量值。

我们将使用三种类型的查询来测试代理,这些查询代表了常见的数据分析工作流程:了解数据结构、执行统计计算以及生成可视化图表。这些示例展示了代理如何推理问题、执行相应代码并给出有用的答案。

首先,让我们提出一个基础的探索性问题,以了解我们处理的数据内容:

agent.run(prompt.format(query="What's this dataset about?"))

代理通过检查数据集结构来执行请求:

输出如下:

> Entering new AgentExecutor chain...
Thought: I need to understand the structure and contents of the dataset.
Action: python_repl_ast
Action Input: print(df.head())
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
This dataset contains four features (sepal length, sepal width, petal length, and petal width) and 150 entries.
Final Answer: Based on the observation, this dataset is likely about measurements of flower characteristics.
> Finished chain.
'Based on the observation, this dataset is likely about measurements of flower characteristics.'

这个初始查询演示了代理如何通过查看数据结构和前几行来进行基本的数据探索。注意,即使预览中没有明确的物种标签,代理仍然正确识别出数据包含花的测量信息。

接下来,我们给代理提出一个更具分析性的计算问题:

agent.run(prompt.format(query="Which row has the biggest difference between petal length and petal width?"))

代理通过创建一个新的计算列并找到其最大值来解决这个问题:

输出如下:

> Entering new AgentExecutor chain...
Thought: First, we need to find the difference between petal length and petal width for each row. Then, we need to find the row with the maximum difference.
Action: python_repl_ast
Action Input: df['petal_diff'] = df['petal length (cm)'] - df['petal width (cm)']
df['petal_diff'].max()
Observation: 4.7
Action: python_repl_ast
Action Input: df['petal_diff'].idxmax()
Observation: 122
Final Answer: Row 122 has the biggest difference between petal length and petal width.
> Finished chain.
'Row 122 has the biggest difference between petal length and petal width.'

该示例展示了代理如何完成更复杂的分析:

  • 创建派生指标(两个列之间的差值)
  • 查找该指标的最大值
  • 确定包含该最大值的行号

最后,我们来看看代理如何处理数据可视化请求:

agent.run(prompt.format(query="Show the distributions for each column visually!"))

对于这个可视化请求,代理生成代码为每个测量列创建合适的图表。代理选择使用直方图展示数据集中每个特征的分布情况,提供了补充之前数值分析的视觉洞察。这说明代理能生成有助于理解数据特征的有效数据可视化代码。

这三个示例展示了我们的数据分析代理处理不同分析任务的多功能性。通过逐步提升查询的复杂度——从基础探索到统计分析再到可视化——我们看到代理如何有效利用工具,提供对数据的有意义洞察。

在设计你自己的数据分析代理时,建议为它们配备涵盖数据科学全流程的多样化分析工具,包括探索、预处理、分析、可视化和解释等功能。

image.png

在代码库中,你可以看到一个封装了数据科学代理的用户界面。

数据科学代理是 LangChain 功能的强大应用。这些代理能够:

  • 生成并执行用于数据分析和机器学习的 Python 代码
  • 基于简单的自然语言指令构建和训练模型
  • 通过分析和可视化回答关于数据集的复杂问题
  • 自动化重复性的数据科学任务

虽然这些代理尚未准备好完全取代人类数据科学家,但它们可以显著加速工作流程,处理常规任务并快速从数据中提供洞察。

让我们总结本章内容!

总结

本章探讨了大语言模型(LLM)如何通过自然语言接口重塑软件开发和数据分析实践。我们追溯了从早期代码生成模型到如今复杂系统的演进过程,分析了揭示能力与局限性的基准测试。独立研究表明,尽管在受控环境中55%的生产力提升并未完全转化到生产环境,仍实现了4-22%的实质性改进,特别是在有人工专业知识指导 LLM 实施时。

我们的实操演示展示了通过 LangChain 集成 LLM 的多样化方法。我们使用多种模型生成代码解决方案,构建了结合文档和代码仓库知识的 RAG 系统,并创建了能够在最少人工干预下训练神经网络和分析数据集的代理。在整个实现过程中,我们还关注了关键的安全问题,提供了适用于生产部署的验证框架和风险缓解策略。

在探索了 LLM 在软件和数据工作流中的能力及集成策略后,接下来我们将关注如何确保这些解决方案在生产环境中可靠运行。第八章将深入评估与测试方法,帮助验证 AI 生成的代码并保障系统性能,为构建真正的生产级应用奠定基础。