中小券商用DeepSeek搭建研报自动生成架构实战
2026/9/18 12:29:23 网站建设 项目流程

简介:面向中小券商数字化转型需求,这份PDF完整梳理了基于DeepSeek实现研报自动生成的分层架构方案。内容从传统财务分析与研报生成痛点切入,覆盖DeepSeek技术原理、金融场景应用、数据层采集与预处理、模型选型与训练、服务层接口设计、应用层功能模块,并延伸至部署实施步骤、技术挑战应对与案例效果评估,体系完整、逻辑清晰。资源共1个PDF文件,压缩包约2.08MB,正文31页,文字、目录、图表均显示正常,方便直接查阅。读者既可借鉴其层次化架构设计思路与模型部署方法,也可参考效率、质量、业务三类评估指标,为规划AI+金融落地提供实操性参考。已有71人浏览学习。

1. 研报从三天到三小时:中小券商部署DeepSeek的架构分水岭

在中小券商里,研报生产长期是一条人工流水线:分析师从Wind和Choice手动导数据,在Excel里算指标,再对着模板写文字。一份深度报告从选题到发布,三到五天是常态,遇到市场热点,大券商两小时出点评,中小券商只能看着流量流失。DeepSeek这类大语言模型出现后,很多人第一时间做了demo:把财报丢进去,几秒钟就能生成一段像模像样的分析。但demo能跑和生产线能跑是两回事——研报要引用准确数字、要过合规审核、要对接内部数据源,背后是数据层、模型层、服务层、应用层的完整架构设计。这篇架构设计拆解的正是这件事:在算力和人力都有限的前提下,把DeepSeek从“能聊天”推到“能出报告”,覆盖部署选型、数据管线、服务封装到上线验证。适合正在做LLM落地的架构师、量化团队和券商IT负责人,也值得任何在强合规行业里做文档自动生成的团队参考。

2. 部署选型先于架构设计:DeepSeek的API、私有化与本地化边界

2.1 三种部署路径与数据合规约束

在券商环境里,部署方式首先不是技术偏好问题,而是数据合规问题。研报草稿里往往包含未公开的客户持仓、内部评级、调研纪要,这些数据一旦离开内网就是事故。所以第一步要按数据敏感度划分部署边界。

部署方式数据是否出境单token成本首token延迟维护成本适用场景
官方API脱敏数据测试、低敏感辅助写作
专有云私有实例生产环境,数据不出域
本地GPU部署低(电费+折旧)高频调用、深度定制微调

我见过不少团队一上来就接官方API,demo跑得很顺,等要接真实交易数据时被合规拦下,整个项目返工。正确顺序是:先划分数据等级,再决定哪些环节走哪条部署路径。常见做法是“内外分流”,公开的新闻资讯走云端API做快速摘要,内部财务数据走本地或专有云实例做生成主链路。

2.2 本地部署的硬件基线:Ollama与vLLM的显存测算

本地部署大语言模型,工具链首选Ollama和vLLM。Ollama胜在零配置,适合原型验证;vLLM做了PagedAttention显存管理,吞吐高,生产环境更推荐。

# 方式一:Ollama 快速起服务,装完就能跑 ollama pull deepseek-r1:7b ollama run deepseek-r1:7b # 方式二:vLLM 起生产级 OpenAI 兼容服务 pip install vllm vllm serve deepseek-ai/DeepSeek-R1-Distill-7B \ --quantization awq \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000

--quantization awq启用4比特量化,显存占用大约是FP16的四分之一;--max-model-len 8192控制上下文窗口上限,设得越大KV Cache占显存越多;--gpu-memory-utilization 0.9表示允许模型吃掉90%显存,留出余量给推理计算。生产环境我会用Docker封装vLLM,配合Supervisor或Kubernetes做进程守护,避免OOM后服务直接挂掉。

模型规模量化方式预估显存最低硬件建议
7Bq4_k_m约5GB单张8GB卡
14Bq4_k_m约9GB单张16GB卡
32Bawq约20GB单张24GB卡或双卡
70Bawq约40GBA100/H100或双卡并行

显存估算要留buffer,7B模型q4量化虽然只要5GB,但加上KV Cache和推理中间张量,8GB卡跑8192上下文已经接近极限。中小券商采购时,我一般建议直接上两张24GB卡,32B量化模型基本覆盖所有研报场景,还能余出卡做向量检索。

2.3 接口抽象层:用OpenAI兼容协议隔离上游模型

部署方式定了之后,第二件事是接口设计。常见错误是业务代码直接绑定某个厂商的SDK,换个模型就要改业务层。统一走OpenAI兼容协议,是当前成本最低的抽象方式。

from openai import OpenAI client = OpenAI( api_key="EMPTY", # 本地端点不校验,占位即可 base_url="http://localhost:8000/v1" # vLLM 本地端点 ) resp = client.chat.completions.create( model="deepseek", messages=[{"role": "user", "content": "分析这家公司的偿债能力,给出流动比率和速动比率"}], temperature=0.3, # 研报生成用低温,减少发散 max_tokens=2048 ) print(resp.choices[0].message.content)

base_url指向本地vLLM服务,api_key填任意值因为本地不校验。这样设计后,将来切回云端API、或者接入codex风格的编码模型来生成SQL查询语句,都只改base_urlmodel名,业务层零改动。deepseek api如何调用这个问题,答案就是这一套——协议统一了,调用方式就统一了。

3. 数据层与研报生成管线:从财务数据入库到结构化输出

3.1 多源财务数据的采集与统一建模

研报生成的前提,是数据层能把口径统一的财务数据喂给模型。中小券商常见数据源包括Wind/Choice这类商业数据库API、交易所公告、财经新闻。难点不在采集,在口径统一——同一个“净利润”,有合并净利润、归母净利润、扣非净利润三种口径,混用会让研报数字严重失真。

import pandas as pd from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://analyst:pass@10.20.1.8:3306/research") def load_financials(ts_code: str): # 数据服务统一封装,屏蔽上游 Wind/Tushare 差异 df = data_service.query("income", ts_code=ts_code, start="2020Q1") df = df.rename(columns={ "n_income_attr_p": "np_attr_parent", # 归母净利润 "total_revenue": "revenue" }) df.to_sql("income_statement", engine, if_exists="append", index=False) return df

字段重命名是第一步,真正要命的是上游数据库对同一指标的算法差异。所以建表时一定要把口径写进字段注释,让后续调数据的分析师和模型提示词都能看到定义。

CREATE TABLE income_statement ( ts_code VARCHAR(16) NOT NULL, report_date DATE NOT NULL, revenue DECIMAL(20,2), net_profit DECIMAL(20,2), np_attr_parent DECIMAL(20,2) COMMENT '归母净利润', gross_margin DECIMAL(8,4), source VARCHAR(32), updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (ts_code, report_date, source) ) ENGINE=InnoDB;

复合主键(ts_code, report_date, source)防止同一来源的重复数据反复入库,source字段标识数据来自哪家供应商,排查口径差异时能快速定位是哪个源出了问题。

3.2 数据清洗:缺失值、复权处理与公告去重

数据层的脏数据比模型幻觉还可怕,因为模型会一本正经地把错数字写进研报。清洗流程里最常踩的坑有三个:缺失值、除权除息、重复公告。

问题现象处理方案
缺失值部分季度指标为空按时间前向填充并打标记,或丢弃该指标
除权除息股价、每股指标跳变用复权因子统一为后复权口径
口径不一净利润混淆合并/归母/扣非拆分独立字段,提示词中强制声明口径
重复公告同一财报被多个爬虫任务抓取按 ts_code+report_date+title 去重
# 除权除息处理:用后复权价做收益率计算 def adjust_price(df: pd.DataFrame, factor_col: str = "adj_factor") -> pd.DataFrame: df["close_adj"] = df["close"] * df[factor_col] # 后复权价格 df["return"] = df["close_adj"].pct_change() # 对数/算数收益率 return df

后复权处理的关键是adj_factor复权因子的算法必须和上游数据商保持一致,否则算出来的收益率曲线在除权日会出现假跳变。清洗完的数据我习惯落一份parquet快照,方便后面回溯“这个数字在哪个版本的数据里是对的”。

3.3 RAG与提示词工程:让DeepSeek引用数据而不是编造

大模型生成研报最大的风险是幻觉——它会用合理的语气编造不存在的营收数字。解决手段不是靠提示词里写“不要编造”,而是RAG:先把财报和公告切片入库,生成时先检索,再让模型基于检索到的片段作答。

from sentence_transformers import SentenceTransformer import chromadb model = SentenceTransformer("BAAI/bge-large-zh-v1.5") client = chromadb.PersistentClient(path="./report_index") col = client.get_or_create_collection("financial_docs") def index_report(text: str, meta: dict, chunk_size=768, overlap=96): # 按长度切片,overlap 避免切断语义完整句 chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size - overlap)] ids = [f"{meta['doc_id']}_{i}" for i in range(len(chunks))] col.add( ids=ids, embeddings=model.encode(chunks).tolist(), documents=chunks, metadatas=[meta] * len(chunks) )

chunk_size=768对中文财报段落比较合适,太短截断财务逻辑,太长检索噪声大;overlap=96保证被切开的句子在相邻片段里都能完整出现。生成时的提示词模板,我一般固定成以下结构:

你是券商研究员。基于以下检索片段撰写分析,禁止使用片段之外的具体数字。 片段: {context} 任务:分析该公司盈利能力,输出结构化内容: 1. key_metrics:关键财务指标及数值 2. analysis:250字以内的分析段落 3. risk:风险提示,三条以内

生成时temperature=0.2,低温让模型更贴近检索片段,减少自由发挥。输出要求结构化,方便下游落库、审核和比对数字。RAG检索的top_k我建议取5到8,太少覆盖不全,太多会把不相关片段塞进上下文干扰生成。

4. 服务层与应用层落地:接口设计、监控与审核闭环

4.1 服务层三大模块与FastAPI实现

服务层是数据层和模型层之间的桥梁,拆三个模块最干净:数据服务模块负责对外提供统一取数接口,模型调用服务模块负责封装LLM推理和RAG检索,报告生成服务模块负责编排整个生成流程、落库和状态管理。

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() class ReportRequest(BaseModel): ts_code: str template: str = "深度报告" # 模板类型,决定提示词结构 with_risk: bool = True # 是否输出风险提示段落 @app.post("/api/v1/report/generate") async def generate_report(req: ReportRequest): # 1. 数据服务模块取财务数据 fin = data_service.get_financials(req.ts_code) # 2. 检索服务模块取相关公告与新闻片段 ctx = retrieval_service.search(f"{req.ts_code} {req.template}") # 3. 模型服务模块组装提示词并调用 DeepSeek content = llm_service.generate(fin_summary=fin, context=ctx, template=req.template) # 4. 报告服务模块落库,返回草稿 ID 便于后续编辑与审核 draft_id = report_service.save(req.ts_code, content) return {"draft_id": draft_id, "status": "draft"}

接口设计上要保证幂等性:同一个ReportRequest重复提交,生成的draft_id应该相同。做法是以ts_code + 模板 + 日期做业务键,重复请求直接返回已有草稿,避免分析师前端重试时产生多份重复报告。with_risk独立成参数,是因为部分场景(比如盘中快评)不需要完整风险提示,拆出来能减少不必要的token消耗。

4.2 模型服务的性能监控与容错

LLM服务和传统API的监控维度完全不同。除了常规的错误率,还要盯首token延迟和token吞吐,这两个指标直接决定分析师的使用体感。

指标含义阈值建议
p50/p95首token延迟用户从发起到看到首个字的耗时p95小于5秒
token吞吐每秒生成token数,影响成本按预算折算
单次生成成功率完整生成不中断的比例大于99%
RAG上下文命中率检索片段被模型实际引用的比例大于85%
import time import logging logger = logging.getLogger("llm_gateway") async def llm_with_monitor(prompt: str, max_tokens: int = 2048): t0 = time.time() try: resp = await llm_client.chat(prompt, max_tokens=max_tokens, timeout=120) duration = (time.time() - t0) * 1000 logger.info({ "event": "llm_call", "tokens": resp.usage.total_tokens, "latency_ms": duration, "status": "ok" }) return resp except TimeoutError: logger.error({"event": "llm_call", "status": "timeout", "latency_ms": 120000}) raise HTTPException(503, "模型服务超时,请稍后重试")

120秒超时是因为深度报告的一次完整生成可能涉及数千token;超时后前端要能明确感知“任务失败”而不是无限转圈。重试策略用指数退避:第一次等2秒重试,第二次4秒,最多三次;如果vLLM里有加载备用小模型,可以考虑降级到7B模型先出一版简评,保证业务不中断。监控上报建议直接对接Prometheus,把上面的logger结构化字段转成metrics,配合Grafana做可视化。

4.3 应用层三类角色与人工审核闭环

研报自动生成不能全自动,必须有“人在回路”。应用层按角色拆成分析师、管理层、客户三类视图,越界访问在接口层就要拦截。

角色主要操作数据权限
分析师编辑草稿、核对数据、提交审核本人及团队数据源
管理层审核发布、统计工作量全量草稿与报告
客户只读查看已发布研报已发布且已授权报告

报告状态机用五态就够了:draft(AI生成草稿)、editing(分析师修改中)、under_review(提交审核)、published(发布)、archived(归档)。权限用RBAC模型,/api/v1/report/generate只对分析师角色开放,管理层只有/pending_review的读权限,客户端的/published接口按客户订阅关系过滤数据。审核这一步不能省,它既是质量闸门,也是合规要求的落地载体。

5. 上线后的验证与调优:用评测集守住研报质量下限

研报质量评估不能靠“感觉这段写得好”,要落到可量化的评测集上。上线前我建议先构建一个50到100条的评测集,覆盖三类场景:财报解读、行业点评、突发公告点评。每条评测样本包含原始数据、检索片段、人工撰写的基准答案,以及抽取出的关键数字集合。

评分维度我最看重三个:数字正确性——生成文本中的每个具体数字都能在源数据中找到对应;逻辑一致性——结论与论据不矛盾,比如营收下滑就不该得出“成长强劲”的结论;合规表述——不出现承诺性收益、不泄露未公开信息。数字正确性权重最高,因为这是硬伤,一票否决。

import re def verify_numbers(text: str, source_df) -> list: """从生成文本中抽取数字,与源数据比对,返回不一致项""" errors = [] for num in re.findall(r"\d+\.?\d*%?", text): # 数字可能带百分号,需要统一口径解析 if not is_in_source(num, source_df): errors.append(num) return errors

is_in_source的匹配逻辑是:先判断数字是否带百分号,再在源数据对应字段里查是否存在相同数值。为了防止浮点精度问题,比较时用“绝对值误差小于0.01”而不是严格相等。校验不通过的生成结果,直接拦截进人工修改队列,不让它流到审核环节。

评测集真正的作用是挡住提示词回归。每次改模板、调参数,先跑一遍评测集,看数字错误率和逻辑不一致数有没有上升。我经历过一次“温度从0.2调到0.5后研报文采变好但数字错误率翻倍”的回归,没有评测集根本发现不了。RAG参数也按评测集调:chunk_size在512和1024之间对比,top_k从5调到10,哪个组合数字错误率最低就锁哪个。

线上运行阶段,每周抽10篇已发布研报重新跑数字校验,错误率超过1%就要回溯是数据层更新出了问题还是提示词被改过。最后还有一个实用技巧:生成端到端埋一个source_ref字段,让模型在每个关键数字后面输出对应的检索片段ID,这样分析师核数时一键跳转到原始出处,把核数时间从半小时压缩到三分钟。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询