当前位置: 首页 > news >正文

观澜:一个 AI 驱动的不良资产尽职调查系统实践

文章目录

    • 一、背景:一个被低估的 AI 落地场景
    • 二、技术架构:不追新,只求稳
      • 容器化部署
    • 三、核心流程:两阶段流水线
      • 数据采集:优雅降级策略
      • AI 分析:三条并行的 LLM 推理线
    • 四、几个值得一说的技术细节
      • 4.1 后端异步任务的双模式调度
      • 4.2 Token 黑名单的自动过期
      • 4.3 GSXT 反爬对策
      • 4.4 LLM 输出的鲁棒性处理
    • 五、为什么选 DeepSeek-V3 而不是 GPT-4
    • 六、教训与反思
      • 6.1 过早抽象 = 过度设计
      • 6.2 WeasyPrint 的 Windows 兼容性
      • 6.3 Redis 多 DB 的隔离策略
      • 6.4 对国内网络环境的适应
    • 七、写在最后

一、背景:一个被低估的 AI 落地场景

不良资产处置是一个万亿级的市场。这个行业的起点和核心是尽职调查——你需要搞清楚每个债务人是否还有偿还能力、有没有隐匿资产、实际控制人是谁、有没有关联企业转移资产。

传统的尽调流程是这样的:

  1. 手工打开 GSXT(国家企业信用信息公示系统),逐个搜索债务人名称
  2. 逐页截图、记录股东、对外投资、行政处罚等信息
  3. 打开裁判文书网,查询涉诉记录
  4. 把以上信息粘贴到 Word 里,人工写分析结论
  5. 一个批次的 30-50 个债务人,一个项目经理要干 2-3 周

这个流程有两个核心痛点:

  • 信息搜集极其繁琐:每个债务人有 5-10 个数据维度,每个维度都需要在不同网站上查询、记录
  • 分析依赖经验:判断关联关系、识别风险信号、评估回收价值,高度依赖资深项目经理的个人经验

"观澜"这个项目的目标很明确:用 AI 自动化掉这个流程


二、技术架构:不追新,只求稳

![系统架构]

┌─────────────────────────────────────────────────────────────┐ │ Vue 3 + TypeScript SPA │ │ Element Plus + ECharts │ └──────────────────────┬──────────────────────────────────────┘ │ REST API ┌──────────────────────▼──────────────────────────────────────┐ │ FastAPI (Python) │ │ JWT Auth / SQLAlchemy / Pydantic v2 │ └──────┬───────────────────────────────────┬──────────────────┘ │ │ ┌──────▼──────┐ ┌───────▼──────────────────┐ │ MySQL 8 │ │ Celery Worker │ │ (业务数据) │ │ (异步任务流水线) │ └─────────────┘ └───────┬──────────────────┘ │ ┌──────────────┐ ┌───────▼──────────────────┐ │ Redis 7 │◄─────────────────┤ Dify LLM Orchestration │ │ (缓存/MQ) │ │ DeepSeek-V3 / Qwen-Max │ └──────────────┘ └──────────────────────────┘

技术上我没有选择最时髦的方案,而是做了务实的权衡:

选型为什么
前端Vue 3 + Element Plus团队最熟,表单/表格/对话框开箱即用
后端FastAPIPython 异步生态最成熟的选择,自动生成 OpenAPI 文档
数据库MySQL 8.0业务数据 ACID 要求,关系复杂,没必要上 PG
缓存/MQRedis 7同时做缓存和 Celery Broker,减少运维组件
异步任务Celery尽调是长时间运行的批处理任务,必须异步
LLM 编排Dify可视化编排 3 个并行的 LLM 节点,降低 prompt 维护成本
LLMDeepSeek-V3中文语义理解强,性价比极高

容器化部署

全项目用 Docker Compose 编排了 6 个服务:

services:mysql# 业务数据库redis# 缓存 + Celery Broker + Session 存储backend# FastAPI 应用服务器backend-worker# Celery 异步工作节点frontend# Nginx 托管 Vue SPA

没有上 k8s——项目初期,一台机器 + Docker Compose 足够。Elasticsearch 和 MinIO 排在了 P1,等用户量上来再加。


三、核心流程:两阶段流水线

整个尽调流程分为两个阶段,由 Celery 编排:

上传债务人列表 (Excel/CSV) │ ▼ ┌───────────────────┐ │ 第一阶段:数据采集 │ ← 串行执行,带限速和反爬 │ GSXT 公开数据 │ │ + Mock 降级 │ └─────────┬─────────┘ ▼ ┌───────────────────┐ │ 第二阶段:AI 分析 │ ← Dify 工作流,3 个 LLM 并 │ 关联关系穿透 │ 行执行 │ 风险识别与打标 │ │ 还款能力评估 │ └─────────┬─────────┘ ▼ ┌───────────────────┐ │ 报告生成 │ │ PDF (WeasyPrint) │ │ + ZIP 批量打包 │ └───────────────────┘

数据采集:优雅降级策略

GSXT 是核心数据源,但它有严格的反爬机制。我们的策略不是"死磕",而是优雅降级

流程: 1. 查 Redis 缓存 (24h 过期) 2. 未命中 → 请求 GSXT 3. 请求成功 → 解析 HTML (三层降级解析) 4. 请求失败 (验证码/403/超时) → Mock 数据生成

Mock 数据生成是一个有意思的设计——它不是随机造假,而是确定性生成

importhashlibdefgenerate_mock_data(company_name:str):seed=hashlib.md5(company_name.encode()).hexdigest()random.seed(seed)# ... 用种子生成注册资本、涉诉数量、股东结构等# 同样的公司名始终得到同样的 Mock 数据

这样保证了:

  • AI 分析管道永远有输入数据,不会因为数据源不可用而中断
  • 测试时可复现,按公司名就能得到确定的结果
  • 每个债权人都"看起来像个真实企业"

AI 分析:三条并行的 LLM 推理线

在 Dify 工作流中,三个 LLM 节点并行执行:

┌──────────────────────────┐ │ Hidden Relation │ │ Penetration │ │ DeepSeek-V3 temp=0.3 │ │ 隐藏关联关系穿透 │ └──────────┬───────────────┘ │ 输入数据 ────────────────────┼─────────┬─────────┐ │ │ │ ┌──────────▼──┐ ┌──▼────────┐ │ │ Risk Tagging │ │ Repayment │ │ │ DeepSeek-V3 │ │ Capacity │ │ │ temp=0.2 │ │ DeepSeek │ │ │ 风险标签 │ │ 还款能力 │ │ └──────────┬──┘ └──┬────────┘ │ │ │ │ └─────────┴─────────┘ │ ▼ ┌──────────────────┐ │ Jinja2 Merger │ │ 统一报告格式 │ └──────────────────┘

1. 关联关系穿透(温度 0.3):分析股东树、人员交叉任职、投资链条,识别隐形控制人、代持、循环担保。这是法律尽调中最耗脑力的部分。

2. 风险语义理解与打标(温度 0.2):将企业经营状态、司法涉诉等数据转化为标准化的风险标签,三级风险等级(红/黄/绿),覆盖 16+ 种风险类型。

3. 还款能力评估(温度 0.2):多维度评估——经营状况、资产质量、负债水平、现金流、关联方支持、逃废债倾向,输出回收等级(高/中/低)和预估回收金额区间。


四、几个值得一说的技术细节

4.1 后端异步任务的双模式调度

Celery 在 Windows 开发环境下有个老问题——和 FastAPI 的事件循环冲突。我采取的策略是一个核心函数,两种调度方式

# 核心业务逻辑(纯异步)asyncdefrun_due_diligence_task(...):# ... 与事件循环无关的业务代码# Celery 包装(生产环境)@celery_app.task(bind=True,max_retries=3)defprocess_due_diligence_task(self,...):asyncio.run(run_due_diligence_task(...))# 开发环境直接调用ifconfig.CELERY_TASK_ALWAYS_EAGER:asyncio.create_task(run_due_diligence_task(...))else:process_due_diligence_task.delay(...)

通过CELERY_TASK_ALWAYS_EAGER这个开关,开发时直接用create_task,生产用 Celery 调度,一套代码两套部署模式。

4.2 Token 黑名单的自动过期

JWT 登出后 token 仍然有效直到过期,这是 JWT 的固有问题。通用的做法是维护一个黑名单,但需要定时清理。

用 Redis 的 TTL 特性,这个问题的解法变得很优雅:

asyncdeflogout(token:str):# 解析 token 获取 jti 和剩余过期时间payload=decode_token(token)jti=payload.get("jti")exp=payload.get("exp")remaining=exp-time.time()# 写入黑名单,自动过期awaitredis.setex(f"token_blacklist:{jti}",remaining,"1")

不需要定时任务,不需要手动清理,Redis 在 TTL 归零后自动删 key。这个模式的寓意是——让基础设施为你做运维

4.3 GSXT 反爬对策

如果说整个系统中有什么地方最像"工程实践"而非"业务开发",就是这个 GSXT 客户端的实现了:

  • 8 个 User-Agent 轮换:覆盖 Chrome/Firefox/Edge/Safari,Windows/macOS 全平台
  • 请求抖动:每次请求间隔0.8s ± 40%随机化
  • Token 桶限速:每分钟最多 20 次请求
  • 验证码检测:响应体中关键词匹配,命中则立即切换策略
  • 三层 HTML 解析回退dl/dt/dd结构 → 表格行解析 → 正则提取

4.4 LLM 输出的鲁棒性处理

LLM 的输出天然不稳定。我必须在生产环境中处理这些情况:

defsanitize_llm_output(raw_text:str)->dict:# 1. 替换 Python 布尔值text=re.sub(r'\bTrue\b','true',raw_text)text=re.sub(r'\bFalse\b','false',text)text=re.sub(r'\bNone\b','null',text)# 2. 剥离 Markdown 代码块标记text=re.sub(r'```(?:json)?','',text)# 3. 尝试 JSON 解析try:returnjson.loads(text)exceptjson.JSONDecodeError:# 4. 实在解析失败,返回原始文本return{"raw_output":raw_text}

核心原则:LLM 会犯错,pipeline 不能崩溃。无论 LLM 输出的质量如何,管道都要能走完。质量问题是 AI 调优的事情,系统可用性是工程的事情。


五、为什么选 DeepSeek-V3 而不是 GPT-4

这是一个经常被问到的问题。理由很简单:

  1. 中文语义理解:在处理企业名称、法律文书、经营异常信息时,DeepSeek 的中文语料覆盖远好于 GPT-4
  2. 性价比:价格大约是 GPT-4 的 1/20,一次尽调任务可能要调用 50-100 次 LLM,成本差距是数量级的
  3. 数据合规:金融行业的数据合规要求严格,通过 Dify 可以灵活切换模型,未来可以无缝迁移到私有化部署的 Qwen-Max
  4. 效果足够:在结构化分析和文本分类场景上,DeepSeek-V3 与 GPT-4 的差距微乎其微

六、教训与反思

6.1 过早抽象 = 过度设计

最初的 collector 模块我设计了一个完整的策略模式(Strategy Pattern),每种数据源一个实现类。最后发现——GSXT 占了 95% 的数据采集量,其他数据源只有 5%。抽象了 6 个接口,实际只用到 1 个。

教训:等你有第二个实现的时候再抽象。

6.2 WeasyPrint 的 Windows 兼容性

WeasyPrint 在 Linux 上运行良好,但在 Windows 上需要 GTK3 运行时。这个坑在开发初期就发现了,解决方式是惰性加载

# 不在导入时加载,在调用时加载defgenerate_pdf(...):fromweasyprintimportHTML# 延迟导入HTML(string=html).write_pdf(target)

这样即使开发机上没有 GTK3,FastAPI 也能正常启动,不影响其他功能开发。

6.3 Redis 多 DB 的隔离策略

一个 Redis 实例承担了三个职责,通过 DB 编号隔离:

DB用途策略
0Celery Broker无需持久化
1业务缓存 (GSXT 数据)24h TTL + LRU
2Token 黑名单按 TTL 自动过期

没有引入额外的消息队列,减少了运维复杂度。Redis 7 的单实例性能在这个量级上完全够用。

6.4 对国内网络环境的适应

Dify Plugin Daemon 的 Docker 镜像在 Docker Hub 上,国内拉取经常超时。解决方案——注释掉,用 Dify Cloud API 替代。这个决定是在第一次docker-compose up失败后 10 分钟做出的。

在国内做开源基础设施开发,先确认所有依赖能不能拉下来。


七、写在最后

“观澜"这个项目目前还处于 MVP 阶段的尾声。从我的角度看,这个项目的价值不在于技术有多"新”,而在于把一个非常传统的业务场景用 AI 重新做了一遍

不良资产行业的信息化程度整体偏低,很多项目经理还在用 Excel + 微信来管理尽调流程。AI 的落地机会不在于取代人,而在于把专业人员的精力从"信息搬运工"中解放出来,让他们专注于真正需要判断力的事情——比如怎么设计处置方案、怎么和债务人谈判。

如果你对这个项目感兴趣,或者你在做类似的 AI + 传统行业落地的尝试,欢迎交流。

项目上线:http://118.178.232.83

http://www.jsqmd.com/news/1142772/

相关文章:

  • TS2007FC音频放大器与PIC32MX675F256L微控制器集成设计
  • Agent的物种分化——豆包千问同日下线智能体背后,一场被误读的产业地震
  • 从模糊到高清:如何用Anime4K让你的动漫观看体验瞬间升级
  • 105-基于51单片机智能衣鞋柜【Proteus仿真+Keil程序+报告+原理图】
  • Termux+Kali打造移动渗透测试环境:从原理到实战
  • 不止看裸车价:全生命周期成本视角下的购车决策逻辑
  • 一键打印6种证件类型,智能排版身份证工具轻松导出图片
  • 3个步骤掌握英雄联盟本地化工具箱的终极实战指南
  • Burp Suite Intruder 4种攻击模式对比:Sniper与Cluster Bomb实战效率分析
  • Havenlon|AI 时代的执行控制(四):AI 真正危险的不是会思考,而是能执行
  • Three.js 罗盘教程
  • 【北京联合大学本科毕业论文】基于SpringBoot的校园二手体育器材交易平台设计与实现
  • STM32智能窗帘系统
  • 界面控件DevExpress WPF v26.1新版亮点 - Data Grid功能全新增强
  • IQ-TREE终极指南:革命性的高效系统发育分析工具
  • 2026物联网公司推荐:选型标准与D-coding落地评估
  • 机缘、收获与憧憬:一个运维开发者的创作自述
  • 从Scaling Law bug学调优:开发者必避的三大陷阱与实操指南
  • Wwise音频容器解析工具:3步掌握游戏音频资源管理与替换
  • 抖音无水印批量下载终极指南:3步搞定视频、音乐、封面全保存
  • 5分钟掌握文档下载黑科技:kill-doc浏览器脚本的终极指南
  • ReAct模式:让AI Agent具备推理与行动能力
  • AI SKILLS应用
  • Scaling Law bug实战启示:从“虚胖“到“精瘦“的算力效率革命
  • 告别C盘爆满!Dism++三步搞定Windows系统优化终极指南
  • 终极微博备份指南:简单免费的微博PDF导出解决方案
  • 3步解锁你的加密音乐:Unlock-Music完全使用指南
  • 韩国出口高增揭示全球AI链贸易上行通道,亚洲供应链持续受益
  • 14部委联合出手!医美行业大整顿,你该知道的全在这里
  • HoRain云--Hermes Agent 安装