【DeepSeek实战】30、LangGraph+AKShare实战:自然语言股票查询系统从入门到精通

在这里插入图片描述

引言:为什么自然语言股票查询是量化分析的“刚需”?

在金融投资中,“快速获取股票信息”是最基础的需求。无论是资深分析师还是普通投资者,都曾遇到过这样的场景:

  • 听到“宁德时代”想查询其代码,却要在行情软件中手动输入拼音首字母搜索;
  • 看到代码“600519”想知道对应公司名称,却要记忆“沪市6开头、深市0/3开头”的规则;
  • 想了解“新能源板块的龙头股票有哪些”,却要逐个筛选几十支股票的行业标签。

这些问题的核心在于:传统查询方式需要用户适应机器的“语法规则”(如代码格式、精确名称),而非机器理解人类的自然语言

本文将系统讲解如何用LangGraph Agent(自然语言理解框架)+ AKShare(金融数据工具)构建一套自然语言股票查询系统,实现“说句话就能查股票”的体验。从数据获取到Agent决策,从代码实战到部署优化,全方位覆盖技术细节与最佳实践。

一、核心技术栈解析:从“数据”到“理解”的全链路

构建自然语言股票查询系统需要四大技术模块协同工作,形成完整的“输入-处理-输出”链路:

graph TD
    A[用户自然语言输入] --> B[LangGraph Agent(理解与决策)]
    B --> C[工具函数(查询逻辑)]
    C --> D[AKShare(数据来源)]
    D --> C
    C --> B
    B --> E[自然语言输出结果]

1. 数据层:AKShare——免费实时的金融数据源

AKShare是Python生态中最成熟的免费金融数据接口库,支持沪深A股、港股、美股等全球市场的实时行情数据。其核心优势在于:

  • 实时性:能获取当日最新股价、涨跌幅等动态数据;
  • 结构化:返回pandas DataFrame格式,便于筛选和处理;
  • 覆盖广:包含股票代码、名称、行业分类等元数据,满足查询需求。

基础代码示例:获取创业板实时数据

import akshare as ak

# 获取创业板所有股票的实时数据(代码、名称、最新价等)
df = ak.stock_cy_a_spot_em()  # em代表东方财富数据源
print(df[["代码", "名称", "最新价", "涨跌幅"]].head())

输出结果示例:

代码 名称 最新价 涨跌幅
300750 宁德时代 210.50 +1.20%
300059 东方财富 14.80 -0.54%
300124 汇川技术 65.30 +2.15%

2. 工具层:函数封装——将查询逻辑转化为“可调用工具”

需要将股票查询逻辑封装为标准化函数,供Agent调用。工具函数需满足:

  • 明确的输入参数(如codename);
  • 结构化的输出(如字典或列表,便于LLM解析);
  • 完善的异常处理(如未找到股票时返回友好提示)。

工具函数封装示例

from langchain_core.tools import tool
import pandas as pd

# 全局存储股票数据(避免重复调用AKShare,提升效率)
stock_data = ak.stock_cy_a_spot_em()  # 初始化时加载一次

@tool
def get_stock_info(code: str = None, name: str = None) -> str:
    """
    根据股票代码或名称查询详细信息(支持模糊搜索)
    
    参数:
        code: 股票代码(如"300750")
        name: 股票名称(如"宁德时代")
    
    返回:
        股票信息字典列表,包含代码、名称、最新价等
    """
    global stock_data
    result = None
    
    try:
        # 优先按代码查询(精确匹配)
        if code:
            result = stock_data[stock_data["代码"].str.contains(code.strip())]
        # 按名称查询(模糊匹配)
        elif name:
            result = stock_data[stock_data["名称"].str.contains(name.strip())]
        # 处理无结果的情况
        if result.empty:
            return f"未找到代码为{
     code}或名称含{
     name}的股票"
        # 转换为字典列表,便于LLM解析
        return result[["代码", "名称", "最新价", "涨跌幅", "所属行业"]].to_dict(orient="records")
    except Exception as e:
        return f"查询出错:{
     str(e)}"

关键设计点

  • 使用@tool装饰器:这是LangChain/LangGraph的标准写法,能自动生成工具描述,供LLM理解其功能;
  • 支持模糊搜索:通过str.contains()实现“输入部分名称/代码即可查询”(如输入“宁德”可匹配“宁德时代”);
  • 全局数据缓存:初始化时加载一次数据,避免重复调用AKShare导致的延迟和限频。

3. 理解层:LLM大模型——实现自然语言到指令的转换

大模型是系统的“大脑”,负责将用户的自然语言(如“宁德时代的代码是多少”)解析为工具函数的调用参数(如name="宁德时代")。核心要求是:支持Function Calling功能(即能识别何时需要调用工具,并生成规范的调用格式)。

推荐模型

  • DeepSeek-Chat(V3):国产模型中Function Calling支持较好,对中文处理更精准;
  • GPT-3.5-turbo/GPT-4:生态成熟,但中文金融术语理解略逊于国产模型;
  • 禁止使用:DeepSeek-R1、LLaMA等纯推理模型(无Function Calling能力,无法调用工具)。

4. 决策层:LangGraph——控制工具调用的工作流

LangGraph是构建Agent的框架,负责协调“大模型理解→工具调用→结果整理”的全流程。其核心价值在于:

  • 支持循环工作流:复杂查询可多轮调用工具(如“先查宁德时代代码,再查其最新价”);
  • 状态管理:保存对话历史,支持上下文关联查询(如“它的涨跌幅是多少”中的“它”指代前文的股票);
  • 条件分支:根据工具返回结果决定下一步操作(继续调用工具或直接回答)。

二、LangGraph Agent实战:两种实现方式对比

LangGraph提供了两种构建Agent的模式,分别适合不同场景:手动实现(灵活度高)和预构建Agent(快速开发)。

1. 方式一:手动实现Function Calling(适合深度定制)

手动构建工作流,需定义节点函数和条件分支,步骤更繁琐但可精确控制每一步逻辑。

步骤1:定义状态结构

状态用于存储对话历史和中间结果,是Agent决策的依据:

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from pydantic import BaseModel, Field
from typing import List, Annotated

# 定义状态结构:存储消息列表
class AgentState(BaseModel):
    messages: Annotated[List, add_messages] = Field(default_factory=list)
步骤2:绑定工具到LLM

将大模型与工具函数关联,使LLM知道“可以调用哪些工具”:

from langchain_community.chat_models import ChatDeepSeek
import os

# 初始化DeepSeek模型(需设置API密钥)
os.environ["DEEPSEEK_API_KEY"] = "你的API密钥"
llm = ChatDeepSeek(model="deepseek-chat", temperature=0)  # temperature=0确保输出稳定

# 将工具绑定到LLM(大模型将知道如何调用这些工具)
llm_with_tools = llm.bind_tools([get_stock_info])
步骤3:定义节点函数

节点是工作流的“步骤”,包括“LLM决策节点”和“工具调用节点”:

# 节点1:LLM决策节点(判断是否需要调用工具)
def llm_decide_node(state: AgentState):
    # 调用绑定工具的LLM,传入对话历史
    response = llm_with_tools.invoke(state["messages"])
    # 将LLM的响应添加到状态中
    return {
   "messages": [response]}

# 节点2:工具调用节点(执行工具并获取结果)
def tool_call_node(state: AgentState):
    # 获取LLM最新的响应(包含工具调用指令)
    last_message = state["messages"][-1]
    tool_results = []
    
    # 遍历所有工具调用指令
    for tool_call in last_message.tool_calls:
        # 调用对应的工具函数(此处仅get_stock_info一个工具)
        if tool_call["name"] == "get_stock_info":
            result = get_stock_info.invoke(tool_call["args"])
            # 将工具结果包装为ToolMessage
            tool_results.append(ToolMessage(
                content=str(result),
                tool_call_id=tool_call["id"]
            ))
    
    # 将工具结果添加到状态中
    return {
   "messages": tool_results}
步骤4:构建工作流

通过状态图定义节点之间的连接关系,实现“LLM决策→工具调用→结果整理”的循环:

# 创建状态图
graph = StateGraph(AgentState)

# 添加节点
graph.add_node("llm_decide", llm_decide_node)  # LLM决策节点
graph.add_node("tool_call", tool_call_node)    # 工具调用节点

# 定义流程:从START开始,先进入LLM决策节点
graph.add_edge(START, "llm_decide")

# 条件分支:LLM决策后,若需要调用工具则进入tool_call节点,否则直接结束
def should_call_tool(state: AgentState):
    last_message = state["messages"][-1]
    # 检查LLM的响应中是否包含工具调用指令
    if last_message.tool_calls:
        return "tool_call"  # 需要调用工具
    else:
        return END          # 无需调用工具,流程结束

graph.add_conditional_edges(
    "llm_decide",       # 源节点
    should_call_tool,   # 条件判断函数
    {
   "tool_call": "tool_call", END: END}  # 分支目标
)

# 工具调用后,返回LLM决策节点继续处理结果
graph.add_edge("tool_call", "llm_decide")

# 编译为可运行的Agent
agent = graph.compile()
步骤5:测试手动构建的Agent
# 测试查询:"宁德时代的股票代码是什么?"
messages = [HumanMessage(content="宁德时代的股票代码是什么?")]
result = agent.invoke({
   "messages": messages})

# 输出最终回复(从状态的消息列表中获取)
for msg in result["messages"]:
    if isinstance(msg, AIMessage):
        print("Agent回复:", msg.content)

输出结果

Agent回复:宁德时代的股票代码是300750。其最新价为210.50元,涨跌幅为+1.20%,所属行业为电力设备。

2. 方式二:预构建Agent(适合快速开发)

LangGraph提供create_react_agent函数,封装了上述工作流,一行代码即可创建Agent,适合追求效率的场景。

代码示例

from langgraph.prebuilt import create_react_agent

# 一行代码创建Agent(自动包含LLM决策、工具调用、结果整理逻辑)
agent = create_react_agent(llm, tools=[get_stock_info])

# 测试查询:"代码300750对应的股票名称是什么?"
response = agent.invoke({
   
    "messages": [HumanMessage(content="代码300750对应的股票名称是什么?")]
})

# 提取并打印回复
print("Agent回复:", response["messages"][-1].content)

输出结果

Agent回复:代码300750对应的股票名称是宁德时代,其最新价为210.50元,涨跌幅为+1.20%,所属行业为电力设备。

3. 两种实现方式的对比

实现方式 优点 缺点 适用场景
手动构建 可定制工作流细节&#
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无心水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值