基于MCP协议构建欧盟金融数据AI Agent:从协议原理到实战开发
1. 项目概述与核心价值最近在折腾AI Agent和工具调用的时候发现了一个挺有意思的仓库nexusforge-tools/mcp-eu-finance。这名字一看就很有指向性nexusforge-tools像是一个工具集的组织而mcp大概率指的是Model Context Protocol一个由 Anthropic 提出的、旨在让大模型能更安全、更标准化地使用外部工具和数据的协议。eu-finance则直接点明了领域——欧盟金融。简单来说这个项目就是一个专门为处理欧盟金融数据、法规和业务流程而设计的 MCP 服务器。它的核心价值在于为开发者尤其是那些在构建面向欧洲市场的金融科技应用、合规分析工具或智能助手的开发者提供了一个开箱即用的“桥梁”。这个桥梁能让你的 AI 应用比如基于 Claude、GPT 或其他兼容 MCP 的模型直接、安全地查询欧盟的金融法规、获取市场数据、进行合规性检查等而无需你从零开始去爬取网站、解析复杂的 PDF 法规文件、或者对接各种晦涩难懂的官方 API。我自己在尝试为一家跨境电商构建一个财务合规顾问原型时就深刻体会过手动处理欧盟 VAT增值税法规的痛。不同成员国的税率、申报门槛、豁免条款千差万别而且法规文本动辄上百页更新还频繁。如果当时有这样一个现成的 MCP 服务器我就能让 AI 直接去问“德国对B2C数字服务的增值税率是多少有哪些豁免条件” 而不是自己先去欧盟官方公报OJ网站大海捞针。所以mcp-eu-finance瞄准的正是这个痛点降低AI应用接入欧盟金融信息与服务的门槛将复杂的、非结构化的金融合规与数据查询封装成模型可以理解和调用的标准化工具。它适合金融科技开发者、企业内部的合规与风控团队、以及任何需要将欧盟金融智能集成到自动化流程中的场景。2. 核心架构与设计思路拆解要理解这个项目怎么用我们得先拆开看看它的设计思路。一个标准的 MCP 服务器其核心任务是将外部资源数据、API、功能包装成一系列标准的“工具Tools”和“资源Resources”并通过 MCP 协议暴露给客户端比如 Claude Desktop、Cursor 或你自己写的 AI 应用。2.1 MCP 协议的角色为什么是它首先为什么是 MCP而不是直接调用 API这里的关键在于标准化和安全性。标准化接口不同的 AI 模型Claude, GPT, Gemini等对工具调用的格式、描述方式各有不同。MCP 定义了一套统一的 JSON-RPC 协议用于描述工具名称、描述、参数模式和资源内容、元数据。这意味着你编写的mcp-eu-finance服务器理论上可以被任何兼容 MCP 的客户端使用实现了“一次编写多处运行”。安全边界MCP 强调客户端与服务器的分离。AI 模型运行在客户端不直接接触原始数据或执行危险操作。它只是向 MCP 服务器“发出指令”调用工具。服务器负责执行具体的、可能有风险的网络请求、数据库查询或计算然后将结构化的结果返回。这为访问敏感金融数据或执行关键操作提供了一个可控的沙箱。动态发现MCP 服务器启动时会向客户端宣告自己提供了哪些工具和资源。客户端AI可以根据需要动态地了解并使用它们无需硬编码。对于eu-finance这个领域使用 MCP 的优势非常明显金融数据源多样欧洲央行、各国监管机构、交易所、格式不统一HTML, PDF, XML, JSON、且涉及敏感信息。MCP 服务器可以作为统一的、安全的适配层处理所有这些脏活累活。2.2mcp-eu-finance的预期功能模块虽然我手头没有该仓库的详细源码但根据其命名和领域我们可以合理推断它至少会包含以下几大功能模块这也是设计此类服务器时的通用思路法规查询工具这应该是核心中的核心。工具可能命名为search_eu_financial_regulation或get_regulation_text。参数可能包括法规名称如 “MiFID II”, “GDPR”, “PSD2”、关键词、颁布机构EC, EBA, ESMA、或时效性最新修订版。服务器内部需要维护一个法规元数据索引并可能集成对 EUR-Lex欧盟法律数据库或其他官方源的访问。市场数据工具例如get_interest_rate获取欧洲央行或某国央行的基准利率、get_exchange_rate获取欧元兑其他货币汇率可能对接 ECB 的 API、get_stock_index获取欧洲斯托克指数数据。这些工具的参数会包括数据标识符、时间范围、频率等。合规检查工具这是更高级的应用。例如check_vat_compliance根据交易金额、商品类型、买卖双方所在地判断适用的增值税规则和税率、assess_mifid_ii_suitability基于投资者信息和产品特性进行初步的适用性评估。这类工具需要内置一定的业务逻辑和规则引擎。实体信息查询get_company_financials查询欧盟境内公司的基本财务信息或注册信息可能链接到商业登记簿、lookup_le查询法人实体标识符。这有助于KYC了解你的客户流程。资源Resources除了工具MCP 还支持“资源”。例如服务器可以将“欧洲央行最新货币政策声明”或“ESMA 最新风险报告”作为一个可读的资源resource://eu-finance/ecb-statement-latest提供给客户端。AI 可以将其作为上下文直接读取而无需调用工具。2.3 技术栈选型考量构建这样一个服务器技术栈的选择至关重要语言Python 是 MCP 生态中最主流的选择这得益于其丰富的库生态用于网络爬虫、PDF解析、数据处理和 Anthropic 官方提供的mcpSDK 的良好支持。TypeScript/Node.js 也是一个强劲的候选适合擅长前端或全栈的团队。nexusforge-tools这个组织名暗示它可能是一系列工具选用 Python 能保证更广泛的受众和更快的原型开发。数据获取层官方 API优先使用欧洲央行ECB、欧盟统计局Eurostat、欧洲银行管理局EBA、欧洲证券和市场管理局ESMA等机构提供的开放 API。这些数据最权威、结构化程度高。例如ECB 的 SDWStatistical Data Warehouse和 Exchange Rates API。网络爬虫对于没有开放 API 的信息如某些国家监管机构的详细指南需要编写稳健的爬虫。这里必须严格遵守robots.txt并考虑使用requests-html或playwright处理动态页面。重要提示爬取公开信息需注意频率控制避免对目标服务器造成压力并仔细阅读网站的服务条款。文档解析欧盟法规通常是 PDF 格式。需要集成像pypdf、pdfplumber或langchain的文档加载器来提取文本。更高级的还需要解析 PDF 中的表格。数据处理与缓存金融数据更新频率不同汇率可能实时法规数月一更新。需要设计缓存策略如使用redis或sqlite对静态或低频变化的数据进行缓存减少对上游源的请求提升响应速度。对于法规文本可能还需要建立本地向量数据库如chromadb,qdrant以实现语义搜索。MCP 服务器框架直接使用mcpPython 库是最快捷的方式。它提供了构建服务器、注册工具、处理请求的脚手架。你需要定义工具函数并用tool装饰器来描述它们。注意合规与数据许可这是金融类项目的高压线。在设计和实现时必须逐一确认所使用的每一个数据源的许可协议License。公开数据不等于可以任意商用。确保你的使用方式符合其条款必要时在项目中明确标注数据来源。对于个人学习和小规模原型风险较低但产品化时必须进行法律审查。3. 核心工具实现与实操要点假设我们现在要用 Python 和官方mcp库来实现一个简化版的mcp-eu-finance服务器重点实现一个法规查询工具和一个汇率查询工具。我们来一步步拆解。3.1 环境准备与项目初始化首先创建一个干净的虚拟环境并安装核心依赖。# 创建项目目录 mkdir mcp-eu-finance-demo cd mcp-eu-finance-demo python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (MacOS/Linux) source venv/bin/activate # 安装核心依赖 pip install mcp httpx pydantic # 安装可能用到的数据处理库 pip install pandas pdfplumber beautifulsoup4httpx是一个现代HTTP客户端比requests在某些场景下性能更好且原生支持异步。pydantic用于数据验证和设置管理这对定义工具的参数模式非常友好。项目结构可以这样组织mcp_eu_finance/ ├── __init__.py ├── server.py # MCP服务器主入口 ├── tools/ │ ├── __init__.py │ ├── regulations.py # 法规查询工具 │ └── market_data.py # 市场数据工具 ├── clients/ │ ├── __init__.py │ └── ecb_client.py # 欧洲央行API客户端 ├── models/ │ └── schemas.py # 数据模型和Pydantic Schema └── config.py # 配置文件3.2 实现汇率查询工具我们从简单的开始实现一个从欧洲央行获取欧元汇率的工具。欧洲央行提供了一个非常友好的免费API。首先在clients/ecb_client.py中创建一个专用的API客户端import httpx from typing import Dict, Any, Optional import asyncio class ECBClient: BASE_URL https://api.exchangeratesapi.io/v1 # 注意这是一个示例ECB官方API端点可能不同 # 实际上ECB的公开汇率数据可以通过SDW或特定数据集获取这里为演示使用一个常见端点。 def __init__(self, access_key: Optional[str] None): # 某些API需要密钥可以从环境变量读取 self.access_key access_key self.client httpx.AsyncClient(timeout30.0) async def get_latest_rate(self, base_currency: str EUR, target_currency: str USD) - Dict[str, Any]: 获取最新汇率 # 实际调用时需要根据ECB API的真实文档构建URL和参数 # 这里是一个模拟示例 url f{self.BASE_URL}/latest params {base: base_currency, symbols: target_currency} if self.access_key: params[access_key] self.access_key try: response await self.client.get(url, paramsparams) response.raise_for_status() data response.json() # 假设返回格式为 {rates: {USD: 1.08}, base:EUR, date:2023-10-27} return { rate: data[rates].get(target_currency), date: data.get(date), base: data.get(base) } except httpx.HTTPStatusError as e: return {error: fAPI请求失败: {e.response.status_code}} except Exception as e: return {error: f发生未知错误: {str(e)}} async def close(self): await self.client.aclose() # 示例模拟数据函数用于演示和测试 async def get_mock_exchange_rate(base: str, target: str) - Dict[str, Any]: await asyncio.sleep(0.1) # 模拟网络延迟 mock_rates { (EUR, USD): 1.0825, (EUR, GBP): 0.8560, (EUR, JPY): 161.50, } rate mock_rates.get((base, target)) if rate: return {rate: rate, date: 2023-10-27, base: base, target: target, source: mock} else: return {error: f未找到 {base} 兑 {target} 的模拟汇率数据}接下来在tools/market_data.py中定义MCP工具import mcp from pydantic import BaseModel, Field from typing import Optional from ..clients.ecb_client import get_mock_exchange_rate # 演示用模拟函数 # 使用Pydantic定义工具的参数模式这会被MCP自动转换为JSON Schema class GetExchangeRateParams(BaseModel): base_currency: str Field( defaultEUR, description基础货币的三位字母代码例如 EUR, USD。默认为EUR。 ) target_currency: str Field( ..., description目标货币的三位字母代码例如 USD, GBP, JPY。 ) mcp.tool() async def get_exchange_rate(params: GetExchangeRateParams) - str: 获取指定货币对的最新汇率。默认以欧元(EUR)为基础货币。 数据来源模拟实际应用需接入欧洲央行(ECB)等权威API。 result await get_mock_exchange_rate(params.base_currency, params.target_currency) if error in result: return f查询失败{result[error]} else: return ( f汇率查询结果\n f- 1 {result[base]} {result[rate]} {params.target_currency}\n f- 数据日期{result.get(date, N/A)}\n f- 数据来源{result.get(source, 模拟数据)}\n f注此为演示数据实际交易请以权威金融机构实时报价为准。 )关键点解析mcp.tool()装饰器将这个异步函数注册为一个MCP工具。函数的参数必须是一个PydanticBaseModel的实例。MCP客户端会看到这个模型定义的JSON Schema并据此生成调用界面例如在Claude Desktop中会显示为带标签的输入框。工具返回一个字符串。这个字符串就是AI模型将看到的“答案”。为了可读性我们将其格式化为清晰的多行文本。在实际项目中get_mock_exchange_rate应替换为真正的ECBClient().get_latest_rate()调用并处理错误和边缘情况如无效货币代码。3.3 实现法规查询工具法规查询更复杂因为它涉及文本检索。我们实现一个基于本地向量数据库的简单语义搜索。首先假设我们有一些欧盟法规的文本片段例如从EUR-Lex下载的PDF中提取的并已经将它们嵌入并存储到了向量数据库中。这里我们使用chromadb作为示例。pip install chromadb sentence-transformers在tools/regulations.py中import mcp from pydantic import BaseModel, Field import chromadb from sentence_transformers import SentenceTransformer from typing import List import asyncio # 初始化模型和客户端在实际应用中这些应该作为单例在服务器启动时初始化 # 注意首次运行会下载模型可能需要一些时间 embedding_model SentenceTransformer(all-MiniLM-L6-v2) # 一个轻量且效果不错的句子嵌入模型 chroma_client chromadb.PersistentClient(path./chroma_db) collection chroma_client.get_or_create_collection(nameeu_regulations) # 假设我们已经预先向collection中添加了数据例如 # collection.add( # documents[MiFID II要求投资公司评估金融产品对客户的适用性..., GDPR第32条规定了数据安全措施..., ...], # metadatas[{title: MiFID II, article: Article 25}, {title: GDPR, article: Article 32}, ...], # ids[mifid2_art25, gdpr_art32, ...] # ) class SearchRegulationParams(BaseModel): query: str Field(..., description搜索法规内容的查询语句例如 客户适用性评估要求 或 数据保护安全措施。) n_results: int Field(default3, description返回的最相关结果数量默认为3。) mcp.tool() async def search_eu_regulation(params: SearchRegulationParams) - str: 在欧盟金融法规库中进行语义搜索返回与查询最相关的法规条文片段。 # 将查询文本转换为向量 query_embedding embedding_model.encode(params.query).tolist() # 查询向量数据库 results collection.query( query_embeddings[query_embedding], n_resultsparams.n_results ) if not results[documents]: return 未找到相关法规条文。 # 格式化输出 output_lines [以下是搜索到的最相关法规条文] for i, (doc, meta) in enumerate(zip(results[documents][0], results[metadatas][0])): output_lines.append(f\n{i1}. 【{meta.get(title, 未知法规)} - {meta.get(article, 未知条款)}】) output_lines.append(f 内容摘要{doc[:300]}...) # 截取前300字符作为预览 output_lines.append(f\n---\n*注此搜索基于本地法规摘要库仅供参考。具体法律应用请以官方正式文本和法律意见为准。*) return \n.join(output_lines)实操心得与避坑指南嵌入模型选择all-MiniLM-L6-v2是一个很好的起点它在速度和效果间取得了平衡。对于专业领域可以考虑用欧盟法规文本微调一个模型或者使用更大的模型如all-mpnet-base-v2但这会增加计算开销和响应延迟。数据预处理是关键向量的质量直接取决于输入文本的质量。从PDF提取的文本通常包含页眉、页脚、换行符混乱等问题。需要仔细的清洗、分块chunking。一个常见的策略是按章节或条款进行分块并将章节标题作为元数据存储。元数据利用在collection.add时尽可能丰富地添加元数据如title法规名、article条款号、issuer发布机构、date生效日期。这样在查询时不仅可以做语义搜索未来还可以扩展出按法规名过滤、按时间筛选等工具。异步处理embedding_model.encode和collection.query在某些配置下可能是CPU密集型操作。如果预期有高并发查询需要考虑将这些操作放到线程池中执行避免阻塞服务器的事件循环。可以使用asyncio.to_thread。结果可信度务必在工具返回结果中加入免责声明。AI生成的答案不能作为法律依据。输出应明确提示用户核对官方来源。3.4 整合并启动MCP服务器最后在server.py中我们将所有工具整合起来并启动服务器。#!/usr/bin/env python3 import mcp import asyncio from tools import market_data, regulations async def main(): # 1. 创建服务器实例 server mcp.Server(eu-finance-demo) # 2. 注册所有工具 # 我们可以直接从模块导入工具函数并用 add_tool 注册 server.add_tool(market_data.get_exchange_rate) server.add_tool(regulations.search_eu_regulation) # 3. 也可以在这里直接使用 server.tool() 装饰器定义简单工具 server.tool() async def get_server_info() - str: 获取此EU Finance MCP服务器的信息。 return 这是一个演示用的欧盟金融信息MCP服务器当前版本0.1.0。提供汇率查询和法规语义搜索功能。 # 4. 运行服务器使用标准输入/输出这是与MCP客户端通信的典型方式 async with server.run_stdio() as transport: print(EU Finance MCP 服务器已启动正在等待连接..., filetransport.stderr) await transport.wait_closed() if __name__ __main__: asyncio.run(main())现在你可以通过标准输入输出运行这个服务器。通常MCP客户端如 Claude Desktop会通过命令行参数或配置文件来启动并连接这个服务器。4. 客户端配置与使用示例以 Claude Desktop 为例你需要编辑其配置文件通常在~/Library/Application Support/Claude/claude_desktop_config.json或类似位置来添加我们的服务器。{ mcpServers: { eu-finance-demo: { command: /path/to/your/venv/bin/python, args: [ /absolute/path/to/your/mcp-eu-finance-demo/server.py ], env: { PYTHONPATH: /absolute/path/to/your/mcp-eu-finance-demo } } } }配置完成后重启 Claude Desktop。在聊天界面Claude 应该会主动告知你它发现了新的工具。你可以直接问它“请帮我查一下欧元兑美元的当前汇率。”“搜索一下关于‘客户风险评估’的欧盟金融法规。”Claude 会自动调用相应的get_exchange_rate或search_eu_regulation工具并将工具返回的结果融入它的回答中。5. 性能优化、安全与扩展方向一个基础的演示服务器跑起来后要投入实际使用还需要考虑很多工程化问题。5.1 性能优化策略缓存无处不在汇率数据ECB的汇率数据每天更新一次。完全可以将结果缓存24小时。可以在工具函数内使用functools.lru_cache注意异步函数需要async_lru或自定义或者使用 Redis 做分布式缓存。向量搜索对于相同的查询语句其嵌入向量是固定的。可以缓存(query, n_results)到最终结果的映射有效期可以设得长一些比如1小时因为法规库不会频繁更新。工具响应MCP协议本身支持资源缓存提示可以在返回结果时附带cacheControl信息指导客户端缓存。异步与并发确保所有I/O操作网络请求、数据库查询都是异步的使用httpx.AsyncClient和支持异步的数据库驱动。避免在工具函数中执行长时间的同步CPU操作如大量文本处理必要时使用asyncio.to_thread将其卸载到线程池。向量数据库优化ChromaDB的PersistentClient对于小规模数据是够用的。如果法规文本量巨大数十万条需要考虑使用更专业的向量数据库如Qdrant,Weaviate或Milvus它们支持更高效的索引和过滤。对文本进行更智能的分块避免块太大信息冗余或太小语义不完整。使用HNSW或IVF索引来加速近似最近邻搜索。5.2 安全加固要点输入验证与净化Pydantic 已经提供了强大的类型验证。但还需要防范更复杂的攻击如通过查询字符串进行向量数据库的注入攻击虽然不常见。确保所有用于构建查询的参数都经过严格的检查和转义。访问控制进阶基础的MCP服务器本身没有用户认证。如果你的工具涉及敏感操作如模拟交易、查询用户特定数据需要在服务器层面实现认证。一种模式是让客户端在连接时传递一个令牌服务器在初始化时验证该令牌并将其与后续的工具调用关联。这通常需要自定义MCP服务器的传输层。输出审查确保工具返回的信息不包含未经处理的原始错误堆栈、内部路径或敏感数据。所有给AI的回复都应该是用户友好的、经过净化的。依赖安全定期更新requirements.txt中的依赖使用safety或pip-audit检查已知漏洞。5.3 功能扩展思路mcp-eu-finance的潜力远不止两个工具。以下是一些可以扩展的方向更多数据源集成公司信息接入欧盟商业登记簿网络BRIS的API查询公司基本信息。制裁名单集成欧盟官方制裁名单的查询用于反洗钱检查。公开招标接入TED欧盟招标电子日报的数据寻找商机。复杂计算工具VAT计算器根据交易双方所在地、商品类型、金额计算应缴增值税。贷款合规检查根据欧盟《消费者信贷指令》计算APR年化百分率并检查披露要求。工作流工具法规更新监控提供一个工具让AI可以“订阅”特定法规的更新当服务器检测到变化时通过资源Resource的方式通知客户端。报告生成器根据查询到的市场数据和法规条文辅助AI起草简单的合规报告摘要。资源Resources提供将“欧洲央行最新新闻发布会纪要”或“ESMA年度风险报告”作为可读资源 (resource://eu-finance/reports/esma-risk-2023) 提供。AI可以直接将其内容加载到上下文中进行总结或问答。6. 常见问题与排查技巧实录在实际开发和运行中你肯定会遇到各种问题。这里记录几个我踩过的坑和解决方法。6.1 MCP连接与通信问题问题配置好Claude Desktop后Claude没有显示新工具。排查检查服务器日志在终端直接运行python server.py看是否有错误输出。确保所有依赖已安装特别是mcp库版本兼容。检查配置文件路径确保command和args中的路径是绝对路径。Python解释器路径和脚本路径都不能有误。检查环境变量如果服务器脚本依赖环境变量如API密钥确保在MCP服务器配置的env字段中正确设置。验证服务器独立性可以写一个简单的测试脚本来验证MCP服务器本身是否工作正常。这需要模拟MCP的stdio通信比较复杂。一个更简单的方法是使用mcp库自带的inspect功能如果提供或者用nc或socat进行原始数据流测试。问题工具调用失败返回“Tool not found”或类似错误。排查工具名匹配确保在server.py中注册的工具名函数名与客户端调用时使用的名称一致。MCP默认使用函数名作为工具名。参数模式检查工具函数的参数是否严格遵循(params: YourPydanticModel)的格式。参数模型必须继承自pydantic.BaseModel。服务器重启修改服务器代码后必须重启Claude Desktop或你的MCP客户端因为服务器进程是在客户端启动时被拉起的。6.2 数据获取与处理问题问题从ECB API获取数据时超时或返回错误。排查网络问题首先用curl或httpx在命令行手动测试API端点确认网络可达且API密钥有效。频率限制免费API通常有调用频率限制。在代码中加入延迟和重试逻辑并使用缓存避免不必要的重复调用。API变更第三方API可能随时变更。将API URL和响应解析逻辑封装在单独的客户端类中并做好错误处理当API变化时只需修改一处。问题向量搜索返回的结果不相关。排查嵌入模型不匹配用于查询的嵌入模型必须与构建向量库时使用的模型完全相同。检查模型名称和版本。文本预处理不一致构建库时的文本清洗流程如去除停用词、词干提取应与处理查询文本的流程一致。最好将预处理函数封装起来共用。分块策略不佳如果法规文本块太大可能包含多个不相关的主题导致搜索精度下降。尝试调整分块大小例如按句子或段落和重叠区域。元数据过滤如果你的查询隐含了过滤条件如“MiFID II中关于…”可以先通过元数据where条件过滤到“MiFID II”相关的文档集合再进行语义搜索这样可以大幅提升准确率。ChromaDB和大多数向量数据库都支持元数据过滤。6.3 性能与稳定性问题问题工具调用响应缓慢尤其是法规搜索。排查嵌入计算瓶颈句子转换模型在CPU上编码一段文本可能需要几十到几百毫秒。考虑缓存嵌入结果对相同的查询文本缓存其向量。使用GPU如果服务器有GPU确保sentence-transformers使用了CUDA。使用更轻量模型权衡精度和速度例如all-MiniLM-L6-v2已经比all-mpnet-base-v2快很多。向量搜索瓶颈随着向量数量增加搜索会变慢。创建索引在ChromaDB中确保在添加大量数据后调用了collection.create_index()。限制搜索范围如前所述利用元数据过滤减少待搜索的向量集合。异步阻塞检查代码中是否有在异步函数中调用了耗时的同步操作如大量的json.loads、复杂的字符串处理。使用asyncio.to_thread将其移出事件循环。问题服务器运行一段时间后内存占用过高。排查内存泄漏在长时间运行的异步应用中确保正确管理客户端会话如httpx.AsyncClient的aclose和数据库连接。向量模型sentence-transformers模型加载后常驻内存。如果同时加载多个大模型内存消耗会很大。按需加载或使用共享模型实例。缓存失控如果使用了内存缓存如lru_cache且没有设置大小限制或过期时间缓存可能会无限增长。为缓存设置合理的maxsize和ttl。构建mcp-eu-finance这样的项目最大的挑战往往不在MCP协议本身而在于如何可靠、高效、合规地获取和处理领域特定的数据。它更像是一个精心设计的数据管道和业务逻辑的封装器。从简单的API调用到复杂的语义搜索每一步都需要考虑错误处理、性能优化和用户体验。当你看到AI通过你构建的工具流畅地回答出专业的金融合规问题时那种成就感是非常直接的。这个项目为AI在垂直领域落地提供了一个非常清晰且强大的范式——不是让AI去学习一切而是让我们为AI构建专业、可靠的“手”和“眼”。