本地大模型 + 自建 MCP Server + SAP OData:让 LLM 代理 SAP 业务操作

本地大模型 + 自建 MCP Server + SAP OData:让 LLM 代理 SAP 业务操作

本文记录从零搭建「本地 LLM → MCP Server → SAP OData」完整链路的全过程,附带解决方案与完整代码;核心思路:把 SAP OData 的查/改/增/删能力封装成 MCP Tool,LLM 通过 function_call 自然语言调用。


1. 整体架构

本地 LLM → MCP → SAP OData 分层架构

关键链路用品牌色标出:stdio 通信(蓝色)、HTTP/SAP OData(橙色)、SAP OData 写路径自动处理 CSRF + ETag(绿色)

2. 三层代码实现

第 1 层:SAPODataClient(HTTP 认证 + CSRF + ETag)

SAP OData v2 的写操作有两道安全门:

机制 触发条件 失败状态码
X-CSRF-Token POST/PUT/PATCH/DELETE 403
If-Match: ETag PUT/PATCH/DELETE 单实体 428 Precondition Required
python 复制代码
class SAPODataClient:
    def call(self, method, service_path, entity_set=None,
             key=None, params=None, payload=None, etag=None):
        """通用 OData 调用,自动处理 CSRF + ETag + 403 重试"""
        # ... URL 拼接 ...

        # ① 写操作 → 自动拿 CSRF Token
        needs_write = method in ('POST', 'PUT', 'PATCH', 'MERGE', 'DELETE')
        if needs_write and not self._csrf_token:
            self._fetch_csrf_token(service_path)    # 从目标服务的 $metadata 取

        # ② PATCH/PUT/DELETE 单实体 → 自动拿 ETag
        if method in ('PUT', 'PATCH', 'MERGE', 'DELETE') and key:
            if not etag:
                probe = self.session.get(url, params={'sap-client':'600','$format':'json'})
                if probe.status_code == 200:
                    etag = probe.json()['d']['__metadata']['etag']
                elif probe.status_code == 400 and method == 'DELETE':
                    etag = '*'   # DELETE 不存在实体时用通配符绕过 Gateway 检查
            if etag:
                headers['If-Match'] = etag

        # ③ 403 自动刷新 CSRF 重试一次
        resp = self.session.request(method, url, ...)
        if resp.status_code == 403 and needs_write:
            self._fetch_csrf_token(service_path)
            headers['X-CSRF-Token'] = self._csrf_token
            resp = self.session.request(method, url, ...)

第 2 层:业务封装(发现 + 通用调用)

LLM 不能瞎调 SAP,它必须先"看菜单",再"看菜谱",最后"做菜"。两步发现 + 一步执行:

python 复制代码
def get_entity_sets(service_url: str) -> list:
    """解析 $metadata XML → EntitySet + 字段 + 主键 + 导航属性"""
    resp = session.get(
        service_url + '/$metadata',
        params={'sap-client': '600'},
        headers={'Accept': 'application/xml'},   # JSON 客户端拿 XML 必须显式指定
    )
    root = ET.fromstring(resp.text)
    # 遍历 EntityType 收集 keys/properties/navigation_properties
    # 遍历 EntitySet 映射到 EntityType
    return [{'name': ..., 'keys': [...], 'properties': [...]}, ...]

def call_odata(service_url, entity_set, method='GET',
               options=None, key=None, payload=None, etag=None):
    """通用调用:统一封装返回 + 清洗 SAP 内部字段"""
    result = client.call(method_up, service_path, entity_set=entity_set,
                         key=key, params=options, payload=payload, etag=etag)

    # SAP 业务错误(400)不抛异常,透传给 LLM 让它自己读
    if isinstance(result, dict) and result.get('__sap_error__'):
        return result

    if method == 'GET':
        return {"count": N, "results": [清洗后的记录]}  # 剥掉 __metadata
    if method == 'POST':
        return {"status": "created", "created": {...}}
    if method in ('PUT', 'PATCH'):
        return {"status": "updated"}
    if method == 'DELETE':
        return {"status": "deleted"}

第 3 层:FastMCP Tool 注册

python 复制代码
# ⚠️ 关键:导入用别名!避免同名遮蔽导致无限递归
from SAP_Service import (
    fetch_all_services,
    get_entity_sets as _get_entity_sets,
    call_odata as _call_odata,
)

@mcp.tool()
def call_odata(service_url: str, entity_set: str, method: str = "GET",
               options: dict = None, key: str = None,
               payload: dict = None, etag: str = None) -> dict:
    """通用 SAP OData 调用,支持 GET/POST/PATCH/DELETE..."""
    try:
        return _call_odata(service_url, entity_set, method=method,
                          options=options, key=key, payload=payload, etag=etag)
    except Exception as e:
        logger.error(f"OData 调用失败: {e}")
        return {"error": str(e)}

3. LLM 自然语言调用流程

LLM Agent 自然语言操作 SAP 的调用链路

用户问:"帮我查一下客户组 0001 的描述,然后改成'测试组'"。LLM 自动触发三轮工具调用:

复制代码
┌─ Step 1: get_all_services() ─────────────────────────────┐
│  LLM: "我需要找到客户组相关的服务"                        │
│  → 返回 13 个 ZAPI_CU 服务, 选中 ZAPI_CUSTOMERGROUP_SRV  │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─ Step 2: get_entity_sets(service_url) ──────────────────┐
│  LLM: "这个服务下有哪些实体集?字段是什么?"                │
│  → A_CustomerGroup(keys=[CustomerGroup])                 │
│    A_CustomerGroupText(keys=[Language,CustomerGroup])     │
│      properties: [CustomerGroupName, ...]                 │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─ Step 3: call_odata(..., method='PATCH', payload={...}) ─┐
│  LLM: "描述在 Text 表里,用复合键定位,然后 PATCH"           │
│  ① GET $metadata → X-CSRF-Token                           │
│  ② probe GET 实体 → ETag: W/"..."                         │
│  ③ PATCH + CSRF + If-Match → 204 No Content               │
│  → {"status": "updated"}                                  │
└─────────────────────────────────────────────────────────┘

4. 测试验证结果

操作 管道状态 典型响应
GET 列表 {"count":2,"results":[...]} 支持 filter/filter/filter/select/top/top/top/orderby/$expand
GET 单实体 支持复合键 (Language='EN',Industry='0001')
PATCH 更新 自动 ETag + If-Match,返回 {"status":"updated"}
POST 创建 CSRF 自动管理,业务校验错误完整透传
DELETE 删除 DELETE 不存在实体时用 If-Match:* 绕过 Gateway

5. 结语

这条链路打通后,本地 LLM 就能像业务用户一样操作 SAP 了:读数据、改状态、建单据------全部自然语言。本次实践只关注技术实现可行性,具体应用还需要对各个服务进行封装测试并确保准确无误及安全可靠,比如:

  1. 权限隔离:MCP Server 的 SAP 账号应只给业务必需的最小权限,不要拿超级用户
  2. 写操作确认:对 POST/PATCH/DELETE 加二次确认机制(比如 LLM 先给用户预览 payload)
  3. 审计日志:所有写操作落盘,便于追溯

MCP 协议的价值不只是"让 LLM 调 API",而是提供了一套结构化的能力暴露机制------SAP 的查/改/增/删被拆解成三个 MCP Tool,LLM 可以自主发现、自主组合、自主修正,真正实现 Agent 代理业务操作。


在AI+时代,一切工程都值得用AI来重构一遍!

相关推荐
Flynt1 小时前
Shopify 弃 React Native 上了 HN 1272 分,我复现了它给 Agent 用的那套无头架构
react native·前端框架·agent
leoZ2312 小时前
第 10 篇 数据飞轮与冷启动:AI 产品的数据从哪来
人工智能·大模型·agent
坐吃山猪3 小时前
Harness Engineering知识总结
llm·agent·harness
李燚4 小时前
Agent 要用 API key,但明文一次都不能进模型——Secret Runtime 落地实录(第110篇)
ai·agent·credential·secret·eino·deepflux·secret runtime
invicinble4 小时前
agent操作Windows的原理,以及操作其他的操作系统里面的数据
agent
wangruofeng4 小时前
把AI判断做成「智能if语句」!Jev实测:一次调用3个判断,置信度直接路由
aigc·agent·ai编程
海带紫菜菠萝汤5 小时前
本周 AI 观察:嘴上喊着降速,手上全踩油门
人工智能·深度学习·ai·开源·大模型
桃西西呀5 小时前
别被"秒回"骗了:推理模型背后那只"吞金兽",吃的是你看不见的预算
人工智能·llm·ai编程
coderMax5 小时前
MCP 架构概览
agent