使用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)

  • 持续更新维护

安装命令

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,核心代码如下:

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的桥梁

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知道何时以及如何调用我们的金融数据工具,需要提供清晰的工具描述:

{
  "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服务获取数据

数据获取与结果格式化

获取到的原始数据示例:

[
  {'日期': '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维护对话上下文:

from langchain.memory import ConversationBufferMemory

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

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

7. 实际应用案例展示

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

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

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

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客户端代码

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服务供更多人使用

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


 

### Deepseek MCP 介绍 Deepseek MCP (Model Control Platform) 是一种集成平台,旨在简化大型项目的开发流程并提高效率。该平台集成了多个工具服务来支持整个软件开发生命周期中的不同阶段[^1]。 #### 平台架构概述 MCP 架构主要由以下几个部分组成: - **Cline**: 提供命令行接口,允许开发者与各种服务交互。 - **Deepseek V3 大型语言模型**: 负责处理自然语言输入,并根据上下文提供相应的编程帮助或自动生成代码片段。 - **GitLab 集成**: 支持版本控制管理以及持续集成/部署(CI/CD),确保团队协作顺畅无阻。 - **VSCode Cline 插件**: 增强IDE功能,使用户能够更便捷地访问操作MCP资源。 - **Obsidian 文档系统**: 创建统一的知识管理体系,便于维护技术文档技术债务记录。 ```bash # 安装 VSCode 的 Cline 插件 code --install-extension cline-plugin ``` ### 功能特性 - **自动化代码生成功能**:通过定义好的提示词模板,可以根据具体业务逻辑快速生成高质量的基础代码框架。 - **智能辅助开发环境**:利用 AI 技术理解用户的意图,在编写过程中提供建议支持;还可以根据已有案例学习新的模式应用于未来项目中去。 - **灵活可扩展的服务组合方式**:不仅限于内置模块间配合工作,也鼓励第三方应用接入形成更加丰富的生态系统。 - **全面覆盖的生命周期管理**:从最初的需求分析到最后的产品发布都有一套完整的解决方案保驾护航。 ### 使用方法 为了更好地理解掌握如何使用 Deepseek MCP 进行高效开发,请遵循以下指南: 安装必要的依赖项之后,可以通过 `cline` 工具初始化一个新的项目实例: ```bash # 初始化新项目 cline init my_project_name cd my_project_name/ ``` 接着复制一份通用 Obsidian 模板至当前工程目录内,并依据实际情况调整其中的内容以适应特定应用场景的要求。 对于日常工作中涉及到的任务执行,则可以直接调用相应子命令完成诸如构建、测试等一系列常规动作: ```bash # 执行构建过程 cline build ``` 当遇到复杂问题难以解决时,不妨尝试借助 Deepseek V3 来寻求灵感——只需简单描述所面临挑战即可获得针对性指导建议。 最后但同样重要的是保持良好习惯定期提交更改到远程仓库以便追踪进度并与队友分享成果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值