使用DeepSeek、MCP和AKShare实现智能金融问答系统技术方案

文将详细介绍如何利用DeepSeek大模型、MCP协议和AKShare金融数据接口,构建一个能够理解用户金融问题并自动获取相关数据回答的智能系统。

提纲

  1. 技术栈概述与准备工作

    • DeepSeek大模型介绍与API申请

    • MCP协议原理与架构解析

    • AKShare金融数据接口简介

    • Python 3.11环境配置

  2. 系统架构设计

    • 整体架构图与数据流

    • MCP Server与Client的角色分配

    • DeepSeek作为核心推理引擎的集成方式

  3. AKShare MCP Server实现

    • 金融数据接口封装设计

    • 使用FastMCP创建MCP服务

    • 支持的主要金融数据功能

  4. DeepSeek与MCP集成

    • 配置DeepSeek支持Function Calling

    • 构建MCP Client调用链

    • 使用LangChain实现智能路由

  5. 完整问答流程实现

    • 用户问题解析与意图识别

    • MCP工具选择与参数生成

    • 数据获取与结果格式化

    • 对话历史管理

  6. 部署与优化

    • 本地开发环境部署

    • 性能优化技巧

    • 错误处理与日志记录

  7. 实际应用案例展示

    • 股票数据查询示例

    • 宏观经济指标分析

    • 投资组合建议生成

1. 技术栈概述与准备工作

DeepSeek大模型介绍与API申请

DeepSeek是由深度求索公司开发的大规模语言模型,最新版本DeepSeek-V3采用混合专家(MoE)架构,拥有671B参数,激活37B参数,在多项基准测试中表现优异3。要使用DeepSeek的API服务:

  1. 访问DeepSeek平台注册账号

  2. 获取API Key(形如sk-e508ba616396*****2c1ee7b17)

MCP协议原理与架构解析

MCP(Model Context Protocol)是由Anthropic提出的标准化协议,它如同AI领域的"USB-C接口",统一了不同大模型与外部工具的交互方式14。MCP的核心组件包括:

  • MCP Server:提供具体功能的节点,如我们的AKShare金融数据服务

  • MCP Client:负责连接大模型与MCP Server的中间件

  • 传输层:支持Stdio(本地进程通信)和SSE(网络通信)等方式5

MCP解决了传统Function Calling的平台依赖问题,使开发者可以"一次开发,多模型通用"1。

AKShare金融数据接口简介

AKShare是基于Python的金融数据接口库,提供股票、基金、期货、宏观经济等领域的海量数据。主要特点包括:

  • 免费开源,数据源丰富

  • 支持多种数据格式(DataFrame/JSON)

  • 持续更新维护

安装命令

python 复制代码
pip install akshare

pip install langchain langgraph langchain-mcp-adapters langchain-deepseek akshare fastapi uvicorn

2. 流程设计

  1. 用户提出问题,如"腾讯控股最近一周股价走势如何?"

  2. DeepSeek分析问题,判断需要调用金融数据接口

  3. MCP Client根据DeepSeek的指示调用对应的AKShare MCP Server

  4. AKShare获取数据后返回给MCP Client

  5. DeepSeek整合数据生成最终回答

3. AKShare MCP Server实现

我们将创建一个专门处理金融数据的MCP Server,核心代码如下:

python 复制代码
from mcp.server.fastmcp import FastMCP
import akshare as ak
import logging
from typing import List, Dict, Any
from datetime import datetime, timedelta
import pandas as pd

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

mcp = FastMCP("AKShareFinance")

@mcp.tool()
def get_stock_hist(symbol: str, start_date: str, end_date: str) -> List[Dict[str, Any]]:
    """
    获取股票历史数据
    :param symbol: 股票代码,如'00700'(腾讯控股)
    :param start_date: 开始日期,格式'YYYY-MM-DD'
    :param end_date: 结束日期,格式'YYYY-MM-DD'
    :return: 包含历史数据的字典列表
    """
    logger.info(f"Fetching stock history for {symbol} from {start_date} to {end_date}")
    df = ak.stock_hk_hist(symbol=symbol, start_date=start_date, end_date=end_date)
    return df.to_dict('records')

@mcp.tool()
def get_stock_info(symbol: str) -> Dict[str, Any]:
    """
    获取股票基本信息
    :param symbol: 股票代码
    :return: 包含股票基本信息的字典
    """
    logger.info(f"Fetching stock info for {symbol}")
    df = ak.stock_hk_spot()
    stock_info = df[df['代码'] == symbol].iloc[0].to_dict()
    return stock_info

@mcp.tool()
def get_macro_economic_data(indicator: str) -> List[Dict[str, Any]]:
    """
    获取宏观经济数据
    :param indicator: 指标名称,如'GDP'、'CPI'等
    :return: 包含宏观经济数据的字典列表
    """
    indicator_map = {
        'GDP': ak.macro_china_gdp,
        'CPI': ak.macro_china_cpi,
        'PMI': ak.macro_china_pmi
    }
    if indicator not in indicator_map:
        raise ValueError(f"Unsupported indicator: {indicator}")
    
    logger.info(f"Fetching macroeconomic data for {indicator}")
    df = indicator_map[indicator]()
    return df.to_dict('records')

if __name__ == "__main__":
    logger.info("Starting AKShare Finance MCP Server")
    mcp.run(transport="sse", port=8000)

此MCP Server提供了三个核心功能:

  1. 获取港股历史数据

  2. 获取股票实时信息

  3. 获取宏观经济指标数据

4. DeepSeek与MCP集成

配置DeepSeek支持Function Calling

虽然DeepSeek原生不完全支持Function Calling,但我们可以通过以下方式实现:

  1. 使用LangChain的DeepSeek封装

  2. 配置MCP适配器作为Function Calling的桥梁

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent

# 初始化DeepSeek
llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key="your_api_key_here",
    temperature=0.3
)

# 配置MCP Client
mcp_client = MultiServerMCPClient(
    servers={
        "finance": "http://localhost:8000"  # AKShare MCP Server地址
    }
)

# 创建React Agent
agent = create_react_agent(llm, mcp_client)

构建MCP工具描述

为了让DeepSeek知道何时以及如何调用我们的金融数据工具,需要提供清晰的工具描述:

python 复制代码
{
  "name": "get_stock_hist",
  "description": "获取指定股票在特定时间范围内的历史交易数据,包括开盘价、收盘价、最高价、最低价和成交量等信息。",
  "parameters": {
    "type": "object",
    "properties": {
      "symbol": {
        "type": "string",
        "description": "股票代码,如腾讯控股是'00700'"
      },
      "start_date": {
        "type": "string",
        "description": "开始日期,格式为YYYY-MM-DD"
      },
      "end_date": {
        "type": "string",
        "description": "结束日期,格式为YYYY-MM-DD"
      }
    },
    "required": ["symbol", "start_date", "end_date"]
  }
}

5. 完整问答流程实现

用户问题解析与意图识别

当用户提问"腾讯控股过去一周的股价表现如何?"时,系统处理流程:

  1. DeepSeek分析问题,识别关键信息:

    • 股票名称:腾讯控股 → 代码00700

    • 时间范围:过去一周 → 计算起止日期

  2. 生成函数调用参数:

{

"symbol": "00700",

"start_date": "2025-04-13",

"end_date": "2025-04-20"

}

3.通过MCP Client调用AKShare服务获取数据

数据获取与结果格式化

获取到的原始数据示例:

python 复制代码
[
  {'日期': '2025-04-13', '开盘': 320.0, '收盘': 325.0, '最高': 328.0, '最低': 319.0, '成交量': 1500000},
  {'日期': '2025-04-14', '开盘': 326.0, '收盘': 322.0, '最高': 327.0, '最低': 321.0, '成交量': 1200000},
  ...
]

·

DeepSeek会分析这些数据并生成自然语言回答:

"腾讯控股(00700)在过去一周(4月13日至4月20日)的股价表现如下:周初开盘320港元,最高触及328港元,周末收盘报325港元,整体呈现小幅上涨趋势..."

对话历史管理

使用LangChain的ConversationBufferMemory维护对话上下文:

python 复制代码
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory()
memory.save_context(
    {"input": "腾讯控股过去一周的股价表现如何?"},
    {"output": "腾讯控股(00700)在过去一周..."}
)

# 后续问题可以引用上下文
response = agent.run("那成交量如何?", memory=memory)

7. 实际应用案例展示

案例展示的服务器使用的是腾讯 cloud studio 提供的免费的高性能工作空间,非常好的免费GPU算力平台。

登录后选择模板直接生成一个工作空间。

一、创建mcp的sse服务器端代码

python 复制代码
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
from starlette.applications import Starlette
from mcp.server.sse import SseServerTransport
from starlette.requests import Request
from starlette.routing import Mount, Route
from mcp.server import Server
import uvicorn
import akshare as ak
import json
from datetime import datetime
from dotenv import load_dotenv
import os
load_dotenv()  # load environment variables from .env


# 初始化 MCP 服务器
mcp = FastMCP("StockServer")


@mcp.tool()
def stock_bid_ask_em(stock_code: str) -> str:
    """获取指定股票代码的行情信息,单次返回指定股票的行情报价数据。
    获取 A 股股票的实时行情数据,包括股票名称、最新价格和涨跌幅信息。
    Args:
        stock_code (str): 股票代码,例如 symbol="000001";
    Returns:
        str: 返回包含股票行情信息的字符串, 
    Raises:
        Exception: 当查询股票数据发生异常时抛出
    """
    try:
        stock_bid_ask_em_df = ak.stock_bid_ask_em(symbol=stock_code)
        if stock_bid_ask_em_df.empty:
            return "未找到该股票的行情信息。"
        else: 
            return stock_bid_ask_em_df.to_json(orient='records', force_ascii=False)
    except Exception as e:
        return f"查询股票行情时出现错误: {e}"
 

@mcp.tool()
def stock_zh_a_hist(stock_code: str, 
                    start_date: str =datetime.now().strftime("%Y-%m-%d") , 
                    end_date: str = datetime.now().strftime("%Y-%m-%d")) -> str:
    """  沪深京 A 股日频率数据; 历史数据按日频率更新, 当日收盘价请在收盘后获取
    Args:
        stock_code (str): 股票代码,例如 symbol="000001";
        start_date(str):	开始查询的日期,例如start_date='20210301';  
        end_date(str): 结束查询的日期,例如end_date ='20210616';  
    Returns:
        str: 返回包含股票行情信息的字符串, 
    Raises:
        Exception: 当查询股票数据发生异常时抛出
    """
    try:
        stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol=stock_code, period="daily", start_date=start_date, end_date=end_date, adjust="hfq")
        if stock_zh_a_hist_df.empty:
            return "未找到该股票的行情信息。"
        else: 
            return stock_zh_a_hist_df.to_json(orient='records', force_ascii=False)
    except Exception as e:
        return f"查询股票行情时出现错误: {e}"
 


@mcp.tool()
def stock_news_em(stock_code: str) -> str:
    """  指定个股的新闻资讯数据
    Args:
        stock_code (str): 股票代码,例如 symbol="000001"; 
    Returns:
        str: 指定 symbol 当日最近 100 条新闻资讯数据, 
    Raises:
        Exception: 当查询股票数据发生异常时抛出
    """
    try:
        stock_news_em_df = ak.stock_news_em(symbol=stock_code)
        if stock_news_em_df.empty:
            return "未找到该股票的信息。"
        else: 
            return stock_news_em_df.to_json(orient='records', force_ascii=False)
    except Exception as e:
        return f"查询股票信息时出现错误: {e}"
 
@mcp.tool()
def  stock_news_main_cx():  
    """  财新数据通-股票精选新闻内容
    Returns:
        str: 返回所有历史新闻数据 
    Raises:
        Exception: 当查询股票数据发生异常时抛出
    """
    try:             
        stock_news_main_cx_df = ak.stock_news_main_cx()
        if stock_news_main_cx_df.empty:
            return "未找到该股票的信息。"
        else: 
            return stock_news_main_cx_df.head(20).to_json(orient='records', force_ascii=False)
    except Exception as e:
        return f"查询股票信息时出现错误: {e}"


def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette:
    sse = SseServerTransport("/messages/")

    async def handle_sse(request: Request) -> None:
        async with sse.connect_sse(
                request.scope,
                request.receive,
                request._send,  # noqa: SLF001
        ) as (read_stream, write_stream):
            await mcp_server.run(
                read_stream,
                write_stream,
                mcp_server.create_initialization_options(),
            )

    return Starlette(
        debug=debug,
        routes=[
            Route("/sse", endpoint=handle_sse),
            Mount("/messages/", app=sse.handle_post_message),
        ],
    )

 

if __name__ == "__main__":
 
    mcp_server = mcp._mcp_server  # noqa: WPS437

    import argparse
    
    parser = argparse.ArgumentParser(description='Run MCP SSE-based server')
    parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
    parser.add_argument('--port', type=int, default=9000, help='Port to listen on')
    args = parser.parse_args()

    # Bind SSE request handling to MCP server
    starlette_app = create_starlette_app(mcp_server, debug=True)

    uvicorn.run(starlette_app, host=args.host, port=args.port)

二、创建mcp的sse客户端代码

python 复制代码
import asyncio
from typing import Optional
from contextlib import AsyncExitStack

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

from mcp.client.sse import sse_client



from dotenv import load_dotenv
import os
load_dotenv()  # load environment variables from .env

from openai import OpenAI
import json

# api_key = os.environ["DEEPSEEK_API_KEY"]
# base_url = os.environ["DEEPSEEK_API_BASE"]

# model_type=os.environ["DEEPSEEK_MODEL"]

api_key = "sk-661***03f" # os.environ["DEEPSEEK_API_KEY"]
base_url ="https://api.deepseek.com/v1"# os.environ["DEEPSEEK_API_BASE"]

model_type= "deepseek-chat" #os.environ["DEEPSEEK_MODEL"]

# print(api_key)
print(base_url)


class MCPClient:
    def __init__(self):
        # Initialize session and client objects
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.anthropic = OpenAI(api_key=api_key, base_url=base_url)

        # api_key=os.environ.get("ANTHROPIC_API_KEY")
        # api_base=os.environ.get("ANTHROPIC_API_KEY")
    # methods will go here


    async def connect_to_server(self, server_script_path: str):
        """Connect to an MCP server

        Args:
            server_script_path: Path to the server script (.py or .js)
        """
        is_python = server_script_path.endswith('.py')
        is_js = server_script_path.endswith('.js')
        if not (is_python or is_js):
            raise ValueError("Server script must be a .py or .js file")

        command = "python" if is_python else "node"
        server_params = StdioServerParameters(
            command=command,
            args=[server_script_path],
            env=None
        )

        stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
        self.stdio, self.write = stdio_transport
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))

        await self.session.initialize()

        # List available tools
        response = await self.session.list_tools()
        tools = response.tools
        print("\nConnected to server with tools:", [tool.name for tool in tools])



    async def connect_to_sse_server(self, server_url: str):
        """Connect to an MCP server running with SSE transport"""
        # 创建 SSE 客户端连接上下文管理器
        self._streams_context = sse_client(url=server_url)
        # 异步初始化 SSE 连接,获取数据流对象
        streams = await self._streams_context.__aenter__()

        # 使用数据流创建 MCP 客户端会话上下文
        self._session_context = ClientSession(*streams)
        # 初始化客户端会话对象
        self.session: ClientSession = await self._session_context.__aenter__()

        # 执行 MCP 协议初始化握手
        await self.session.initialize()


    async def process_query(self, query: str) -> str:
        """Process a query using Claude and available tools"""
        messages = [
            {
                "role": "user",
                "content": query
            }
        ]

        response = await self.session.list_tools()


        available_tools = []

        for tool in response.tools:
            tool_schema = getattr(
                tool,
                "inputSchema",
                {"type": "object", "properties": {}, "required": []},
            )

            openai_tool = {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "parameters": tool_schema,
                },
            }
            available_tools.append(openai_tool)

        # Initial Claude API call
        model_response = self.anthropic.chat.completions.create(
            model=model_type,
            max_tokens=1000,
            messages=messages,
            tools=available_tools,
        )

        # Process response and handle tool calls
        final_text = []
        tool_results = []

        messages.append(model_response.choices[0].message.model_dump())
        print(messages[-1])
        if model_response.choices[0].message.tool_calls:
            tool_call = model_response.choices[0].message.tool_calls[0]
            tool_args = json.loads(tool_call.function.arguments)

            tool_name = tool_call.function.name
            result = await self.session.call_tool(tool_name, tool_args)
            tool_results.append({"call": tool_name, "result": result})
            final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")

            messages.append(
                {
                    "role": "tool",
                    "content": f"{result}",
                    "tool_call_id": tool_call.id,
                }
            )

            # Get next response from Claude
            response = self.anthropic.chat.completions.create(
                model=model_type,
                max_tokens=1000,
                messages=messages,
            )

            messages.append(response.choices[0].message.model_dump())
            print(messages[-1])

        return messages[-1]["content"]



    async def chat_loop(self):
        """Run an interactive chat loop"""
        print("\nMCP Client Started!")
        print("Type your queries or 'quit' to exit.")
        print("\n🤖 MCP 客户端已启动!输入 'quit' 退出")

        while True:
            try:
                query = input("\nQuery: ").strip()

                if query.lower() == 'quit':
                    break

                response = await self.process_query(query)
                print("\n" + response)

            except Exception as e:
                print(f"\nError: {str(e)}")

    async def cleanup(self):
        """Clean up resources"""
        await self.exit_stack.aclose()


async def main():
    if len(sys.argv) < 2:
        print("Usage: python client.py <path_to_server_script>")
        sys.exit(1)

    print("Connecting to server...")
    print(sys.argv)
    client = MCPClient()
    try:
        await client.connect_to_sse_server(sys.argv[1])
        await client.chat_loop()
    finally:
        await client.cleanup()

if __name__ == "__main__":
    import sys
    asyncio.run(main())

三、股票数据查询示例

总结

本文详细介绍了如何使用DeepSeek、MCP协议和AKShare构建智能金融问答系统。通过MCP协议,我们成功将DeepSeek大模型的推理能力与AKShare的金融数据获取能力相结合,实现了真正的Function Calling功能。这种架构具有以下优势:

  1. 标准化:MCP提供了统一的接口标准,便于扩展其他数据源1

  2. 灵活性:可以轻松切换不同的大模型或数据源

  3. 安全性:敏感数据可以保留在本地环境中1

  4. 高效性:利用DeepSeek-V3的高性能推理能力3

未来可以进一步扩展的方向包括:

  • 增加更多金融数据源

  • 实现更复杂的投资分析功能

  • 加入可视化图表生成能力

  • 部署为Web服务供更多人使用

声明:本文仅做技术研究,投资有风险,如实需谨慎。

相关推荐
-曾牛1 分钟前
Spring AI聊天模型API:轻松构建智能聊天交互
java·人工智能·后端·spring·交互·api·springai
码码哈哈爱分享7 分钟前
可商用,可离线运行,可API接口调用的开源AI数字人项目Heygem,喂饭级安装教程
人工智能·开源
江安的猪猪9 分钟前
大连理工大学选修课——机器学习笔记(2):机器学习的一般原理
人工智能·笔记·机器学习
IT古董16 分钟前
【漫话机器学习系列】238.训练误差与测试误差(Training Error And Test Error)
人工智能·深度学习·机器学习
今晚去打老虎25 分钟前
快速掌握大语言模型+向量数据库_RAG实现
人工智能·python·语言模型·milvus
wang_yb42 分钟前
从“朴素”到“半朴素”:贝叶斯分类器的进阶之路
ai·databook
weixin_307779131 小时前
使用Python和Pandas实现的Amazon Redshift权限检查与SQL生成用于IT审计
数据仓库·金融·云计算·etl·aws
ARM2NCWU1 小时前
边缘计算服务器
服务器·人工智能·边缘计算
meisongqing1 小时前
【人工智能】边缘计算技术及应用概述
人工智能·边缘计算
__Benco1 小时前
OpenHarmony平台驱动开发(一),ADC
人工智能·驱动开发·harmonyos