阿里云 Milvus AI Function | 低成本和高稳定的双重加强

作者:周弘懿(锦琛)

业务背景

AI 应用上线后,团队很快会撞上三件事:相同的文本被反复向量化、在线推理动辄几秒的延迟、以及突发流量把模型额度瞬间打满。

商品标题会反复同步,客服 FAQ 会多次发布,知识库段落也会因为重试或增量导入再次进入 Embedding 模型------文本没变,向量通常也不变,模型调用和等待时间却实实在在地又发生了一次。

与此同时,很多 AI 任务从一开始就没有「实时返回」的要求。商品描述补全、历史客服会话摘要、内容翻译、知识库初始化,今晚处理、明早交付,完全不影响用户体验。这类任务如果仍然走实时接口,既要按实时价格付费,又会挤占在线搜索与问答的模型额度。

于是 AI 应用的降本诉求可以拆成两条清晰的路径:

  • 相同内容只做一次向量化,把重复的模型调用省下来;

  • 可以等待的任务离线批量处理,用更低的单价完成大批量加工。

同时降本不能以牺牲稳定性为代价。AI 调用一旦进入生产链路,还要直面突发流量、账号限流和局部故障。真正可用的方案,既要压低每次任务的成本,也要在异常发生时保证核心写入与查询不中断。

阿里云 Milvus AI Function 通过 Embedding CacheAI Batch 解决降本,通过内网直连、多账号负载均衡与故障切换、缓存降级 守住稳定,再用 Data Inspection 补上内容安全的最后一道门。

技术挑战

把「降本」和「稳定」同时做好,传统方案会遇到几条绕不开的难点:

  1. 相同内容重复向量化,Token 白花:商品资料、FAQ、热门查询反复进入 Embedding 模型,向量结果完全一样,Token 与等待时间却重复消耗。

  2. 可离线的任务仍走实时接口:历史工单归档、会话摘要、描述补全本可以按天集中处理,却因为没有离线通道而按实时价格计费,还占用在线额度。

  3. AI 调用进入生产链路后连续性难保障:突发流量、单账号限流、API Key 异常都可能让一次写入或查询失败,而 AI 调用往往就挂在核心链路上。

  4. 缓存本身可能成为硬依赖:为降本引入的缓存,一旦 Redis 抖动或读取超时,反而会拖慢甚至拖垮主链路。

  5. 内容安全是上线门槛:用户输入可能含风险内容,模型输出也可能不适合直接交付,缺了护栏产品过不了合规评审。

解决方案

阿里云 Milvus AI Function 把上面几件事收敛进同一套能力里:

能力 作用 解决的问题
Embedding Cache 按文本与调用上下文精确匹配已有向量,命中即复用 相同内容只做一次向量化,省下重复 Token
AI Batch 请求写入 JSONL 异步提交,按实时推理价格 50% 计费 可等待的大批量任务离线低价完成,不挤占在线额度
内网直连 AI Function 与百炼通过内网打通 缩短调用路径,减少公网波动与性能损耗
多账号负载均衡 / 故障切换 多账号分摊调用、异常 Key 自动切换 降低单账号限流、单 Key 异常对连续性的影响
缓存降级 连续慢读 / 错误达阈值即进入降级窗口、回源模型 缓存不拖累主链路,宁可多调模型也不中断
Data Inspection input / output / both 三态内容检查 风险内容进模型前、出模型前各加一道门

具体 AI Function 用法参考官方文档

整体能力示意如下:

降本维度一:让相同内容只做一次向量化

Embedding Cache 会先按文本和调用上下文查找已有向量,命中后直接复用,只有首次出现的新内容才真正调用模型。缓存采用精确匹配------两段文字只要写法不同,就会分别计算。它的收益完全来自重复率:重复越多,省下的模型请求越多。

以日均 10 万次向量化、平均每次 50 Token、缓存命中率 50%、按 30 天计算,命中的那一半请求不再产生任何模型调用与等待,整体 Token 消耗大约下降一半。

Embedding Cache 更适合在线、高频、重复明显的内容:

  • 客服 FAQ 和标准答案

  • 热门商品标题与商品属性

  • 知识库重复导入的段落

  • 高频搜索词和固定查询模板

降本维度二:把可以等待的任务交给 Batch

Batch 会把请求写入 JSONL 文件后异步提交。百炼 Batch 对支持该能力的模型按实时推理价格的 50% 计费------相同模型、相同 Token 规模下,一笔按实时估算为 100 元的任务,Batch 对应的模型调用费用约为 50 元。

沿用同一口径试算,日均处理 10 万条离线任务、平均输入 50 Token、输出 500 Token,使用 AI Batch 后月调用成本可从约 3,636 元降至 1,818 元

试算场景 口径 优化前 优化后
Embedding Cache 日均 10 万次、平均 50 Token、命中率 50%、30 天 全量调用模型 重复内容 0 Token,整体约降一半
AI Batch 日均 10 万条、输入 50 / 输出 500 Token、30 天 约 3,636 元 / 月 约 1,818 元 / 月

以上数字用于说明计费比例,实际账单以模型、地域与当期价格为准。

Batch 更适合离线、大批量、允许等待结果的任务:

  • 历史文档生成摘要或标签

  • 存量商品资料批量加工

  • 图片、视频、音频素材生成描述

  • 模型评测与数据标注

  • 周期性重建和夜间任务

聊天回复、搜索补全等即时交互仍应使用实时调用------只要用户正在等待结果,就不该为了折扣把请求放进 Batch。

实战操作

一、用两次相同写入验证 Embedding Cache

下面是一段自包含的可运行代码:第一次写入生成向量并写入缓存,第二次写入相同内容时直接复用。是否命中可观察 Redis keyspace_hits 或 Embedding Provider 的调用次数(两次向量相同只能说明模型输出一致,不能单独证明命中缓存)。

代码参考:

python 复制代码
import json
import time

from pymilvus import DataType, Function, FunctionType, MilvusClient

# ==================== 连接配置 ====================
MILVUS_URI = "http://c-xxx.milvus.aliyuncs.com:19530"  # 端口必须写 19530
MILVUS_TOKEN = "root:xxx"
MODEL_NAME = "text-embedding-v4"
VECTOR_DIM = 1024

client = MilvusClient(uri=MILVUS_URI, token=MILVUS_TOKEN)
collection_name = "ai_embedding_cache_demo"

if client.has_collection(collection_name):
    client.drop_collection(collection_name)

schema = MilvusClient.create_schema(auto_id=True, enable_dynamic_field=False)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("content", DataType.VARCHAR, max_length=4096)
schema.add_field("embedding", DataType.FLOAT_VECTOR, dim=VECTOR_DIM)
schema.add_function(
    Function(
        name="embed_content_with_cache",
        function_type=FunctionType.TEXTEMBEDDING,
        input_field_names=["content"],
        output_field_names=["embedding"],
        params={
            "provider": "aliyun_milvus",
            "model_name": MODEL_NAME,
            "dim": VECTOR_DIM,
            "cache": json.dumps({
                "enabled": True,
                "exact_cache": {
                    "enabled": True,
                    "backend": "redis",
                    "ttl_hours": 24,
                },
            }),
        },
    )
)

index_params = client.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",
    metric_type="COSINE",
)
client.create_collection(
    collection_name=collection_name,
    schema=schema,
    index_params=index_params,
)

row = {"content": "Milvus is an open-source vector database."}

print("=" * 64)
print("Embedding Cache 验证:两次写入完全相同的内容")
print("=" * 64)
print(f"写入内容 : {row['content']}")
print(f"缓存配置 : exact_cache backend=redis, ttl=24h")
print("-" * 64)

# 第 1 次写入:缓存未命中,调用模型生成向量并写入缓存
t0 = time.perf_counter()
client.insert(collection_name, [row])
client.flush(collection_name)
t_first = (time.perf_counter() - t0) * 1000
print(f"[第 1 次写入] 缓存未命中 → 调模型生成 embedding   耗时 {t_first:8.1f} ms")

# 第 2 次写入相同内容:缓存命中,复用已有向量,无需再调模型
t0 = time.perf_counter()
client.insert(collection_name, [row])
client.flush(collection_name)
t_second = (time.perf_counter() - t0) * 1000
print(f"[第 2 次写入] 相同内容   → 命中缓存复用向量      耗时 {t_second:8.1f} ms")
if t_second < t_first:
    print(f"           ↳ 第 2 次快 {t_first - t_second:.1f} ms(约 {t_first / t_second:.1f}x)")
print("-" * 64)

# 取出两条记录的向量逐元素比对:命中缓存则两份向量应完全一致
rows = client.query(
    collection_name,
    filter="",
    output_fields=["id", "content", "embedding"],
    limit=10,
)
rows = sorted(rows, key=lambda r: r["id"])
print(f"写入记录数 : {len(rows)}")
for r in rows:
    vec = r["embedding"]
    print(f"  · id={r['id']}  dim={len(vec)}  首 3 维={[round(x, 6) for x in vec[:3]]}")

if len(rows) >= 2:
    v1, v2 = rows[0]["embedding"], rows[1]["embedding"]
    max_diff = max(abs(a - b) for a, b in zip(v1, v2))
    print("-" * 64)
    print(f"两条向量逐元素最大差值 : {max_diff:.3e}")
    if max_diff == 0.0:
        print("结论 : ✅ 两次向量完全一致 ------ 第 2 次确实复用了缓存向量,Embedding Cache 命中")
    else:
        print("结论 : ❌ 两次向量存在差异 ------ 未复用缓存,请检查缓存配置")

示例把有效期设为 24 小时,可按内容更新频率调整:商品标题和 FAQ 更新不频繁时适当延长,内容变化快时缩短,让新版本更快重新计算。缓存不可用或超时时,Milvus 会继续调用模型,业务写入和查询不会因此中断。

二、一个夜间知识库加工的 Batch 例子

一家电商白天把模型额度留给正在搜索和咨询的用户,夜里再集中处理当天积累的商品资料和客服会话:批量生成商品描述、补齐标签、把长对话整理成摘要。第二天运营上班时结果已写回业务系统------用户没有多等一秒,企业也不必按实时价格处理这批数据。

输入文件一行对应一条任务,用 custom_id 关联原始数据:

jsonl 复制代码
{"custom_id":"article-001","method":"POST","url":"/v1/chat/completions","body":{"model":"qwen3.7-max","messages":[{"role":"user","content":"请为这篇知识库文章生成一句话摘要......"}],"enable_thinking":false}}
{"custom_id":"article-002","method":"POST","url":"/v1/chat/completions","body":{"model":"qwen3.7-max","messages":[{"role":"user","content":"请为这篇知识库文章生成一句话摘要......"}],"enable_thinking":false}}

参考代码:

python 复制代码
import json
import os
import shutil
import tempfile
import time
import uuid
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

# ==================== 连接配置 ====================
# Milvus AI Batch 走 RESTful 接口,base_url 用实例地址(端口 19530)
MILVUS_BASE_URL = "http://c-xxxx.milvus.aliyuncs.com:19530"
MILVUS_TOKEN = "root:xxx"
MODEL_NAME = "qwen3.7-max"        # 批处理调用的模型,必须与 input.jsonl 里每行 body.model 一致
PROVIDER = "aliyun_milvus"
ENDPOINT = "/v1/chat/completions"


def post_json(path, body, timeout=120):
    request = Request(
        f"{MILVUS_BASE_URL.rstrip('/')}{path}",
        data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {MILVUS_TOKEN}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    try:
        with urlopen(request, timeout=timeout) as response:
            return response.status, json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        return exc.code, json.loads(exc.read().decode("utf-8"))


def upload_input_file(input_file):
    """multipart 上传 input.jsonl,等价于 OpenAI 的 files.create(purpose='batch')"""
    boundary = f"----milvus-ai-batch-{uuid.uuid4().hex}"
    fields = {
        "provider": PROVIDER,
        "model_name": MODEL_NAME,
        "endpoint": ENDPOINT,
        "purpose": "batch",
    }
    with tempfile.TemporaryFile(mode="w+b") as payload:
        for name, value in fields.items():
            payload.write(f"--{boundary}\r\n".encode())
            payload.write(f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode())
            payload.write(value.encode())
            payload.write(b"\r\n")

        payload.write(f"--{boundary}\r\n".encode())
        payload.write(b'Content-Disposition: form-data; name="file"; filename="input.jsonl"\r\n')
        payload.write(b"Content-Type: application/jsonl\r\n\r\n")
        with open(input_file, "rb") as fh:
            shutil.copyfileobj(fh, payload)
        payload.write(b"\r\n")
        payload.write(f"--{boundary}--\r\n".encode())

        length = payload.tell()
        payload.seek(0)
        request = Request(
            f"{MILVUS_BASE_URL.rstrip('/')}/v2/vectordb/ai/batch/files/upload",
            data=payload,
            headers={
                "Authorization": f"Bearer {MILVUS_TOKEN}",
                "Content-Type": f"multipart/form-data; boundary={boundary}",
                "Content-Length": str(length),
            },
            method="POST",
        )
        try:
            with urlopen(request, timeout=600) as response:
                return response.status, json.loads(response.read().decode("utf-8"))
        except HTTPError as exc:
            return exc.code, json.loads(exc.read().decode("utf-8"))


def download_batch_file(batch_id, file_type, output_file):
    """下载结果文件,等价于 OpenAI 的 files.content(output_file_id)"""
    request = Request(
        f"{MILVUS_BASE_URL.rstrip('/')}/v2/vectordb/ai/batch/files/content",
        data=json.dumps(
            {"provider": PROVIDER, "batch_id": batch_id, "file_type": file_type},
            ensure_ascii=False,
        ).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {MILVUS_TOKEN}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    with urlopen(request, timeout=600) as response, output_file.open("wb") as out:
        shutil.copyfileobj(response, out)


# 1) 上传 JSONL 输入文件
status, data = upload_input_file("input.jsonl")
print(json.dumps(data, ensure_ascii=False, indent=2))
input_file_id = data.get("data", {}).get("id")
if status != 200 or not input_file_id:
    raise SystemExit(f"上传输入文件失败:HTTP={status} code={data.get('code')} message={data.get('message')}")

# 2) 创建 Batch 任务,声明完成窗口
status, data = post_json(
    "/v2/vectordb/ai/batch/jobs/create",
    {
        "provider": PROVIDER,
        "input_file_id": input_file_id,
        "endpoint": ENDPOINT,
        "completion_window": "24h",
    },
)
print(json.dumps(data, ensure_ascii=False, indent=2))
batch_id = data.get("data", {}).get("id")
if status != 200 or not batch_id:
    raise SystemExit(f"创建 Batch 任务失败:HTTP={status} code={data.get('code')} message={data.get('message')}")

# 3) 轮询任务状态
while True:
    status, data = post_json(
        "/v2/vectordb/ai/batch/jobs/describe",
        {"provider": PROVIDER, "batch_id": batch_id},
    )
    batch = data.get("data", {})
    batch_status = batch.get("status")
    print(batch_status)  # validating / in_progress / completed / failed ...
    if batch_status in {"completed", "failed", "expired", "cancelled"}:
        break
    time.sleep(5)

# 4) 完成后下载结果(失败行在 error_file_id 中,可单独重试)
if batch_status == "completed":
    download_batch_file(batch_id, "output", Path("output.jsonl"))
    if batch.get("error_file_id"):
        download_batch_file(batch_id, "error", Path("error.jsonl"))

任务完成后结果文件仍带回原来的 custom_id,业务可以把摘要准确写回对应文章。部分数据失败时,只需下载错误文件并重试失败行,已完成的结果直接保留。

降本之后,在线调用还要稳

成本只是第一关。AI Function 进入生产环境后,网络抖动、账号限流、API Key 异常和缓存故障,都可能影响一次写入或查询。阿里云 Milvus 在性能与稳定性上做了几项强化:

  • 内网直连:AI Function 服务与百炼通过内网打通,调用路径更短,减少公网调用带来的性能损耗与网络波动。

  • 多账号负载均衡与故障切换:支持多账号分摊调用、异常 API Key 自动切换,降低单账号限流或单 Key 异常对业务连续性的影响。

  • 缓存延迟降级:Redis 短暂不可用或读取超时时,系统绕过缓存继续调用模型;开启延迟降级后,连续慢读或错误达到设定次数,缓存默认进入 60 秒降级窗口,触发降级的结果会被丢弃、请求直接回源模型,窗口结束后系统自动探测缓存是否恢复。

这条策略很明确:宁可短时间多调用模型,也要避免缓存拖慢请求或把异常结果带进主链路。Batch 对局部失败同样保留了单独处理的空间------成功结果与错误结果分开下载,只重试失败部分,已完成的数据无需整批重做。

内容进入模型前后,都可以多一道检查

客服对话、工单处理和自动内容发布还会遇到另一类问题:用户输入可能含风险内容,模型输出也可能不适合直接交付。Data Inspection 可以检查模型调用前的输入、返回前的输出,或两端同时开启:

  • 输入检查适合客服、工单和用户提问,在风险内容进入模型前完成判断;

  • 输出检查适合摘要、营销文案和自动答复,在内容进入发布流程前再检查一次;

  • 双向检查适合风险要求更高、结果会直接触达用户的业务。

命中安全策略的内容会被拦截或返回安全拒答,正常内容继续走原有流程。Data Inspection 负责的是 AI Function 文本处理过程中的内容安全;账号权限、网络隔离和数据加密仍需按企业安全规范分别配置。

实战效果

把降本与稳定两条路径放进同一套业务,各类数据各走合适的路径,在线体验和成本不用互相让步。两种降本能力面向的是两种浪费:

业务特征 Embedding Cache AI Batch
数据特点 相同文本反复出现 首次处理的大批量数据
返回要求 在线返回 允许延后完成
节省来源 减少重复模型调用 按实时推理价格 50% 计费
常见场景 FAQ、热门商品、重复导入 历史加工、夜间任务、模型评测

落到电商场景:新商品上架继续实时向量化,热门商品重复同步由 Embedding Cache 复用向量,几百万条历史商品的摘要和标签交给 Batch;在线调用通过内网直连、多账号负载均衡与异常 Key 切换保持连续;涉及内容生成的流程再按风险等级开启输入 / 输出 / 双向检查。与传统做法相比,收益很直观:

维度 优化前 接入阿里云 Milvus AI Function 后
重复内容向量化 每次都调用模型 Embedding Cache 命中即复用,重复内容 0 Token
离线大批量任务 按实时价格、挤占在线额度 Batch 离线执行,50% 计费、错峰运行
突发流量 / 单账号限流 单账号打满即失败 多账号负载均衡 + 异常 Key 故障切换
缓存故障 可能拖垮主链路 自动降级回源,主链路不中断
内容安全 事后人工、被动处置 Data Inspection 前置拦截,发不出去

总结

每项能力都有清晰的职责:Cache 减少重复计算,Batch 降低离线任务单价,稳定性机制守住在线调用,Data Inspection 控制内容风险。真正用于生产的 AI Function,很少只依赖某一个开关------先按时效拆数据(在线走实时、存量与周期任务走 Batch),再按重复率让 Embedding Cache 接住反复出现的内容,在线调用靠内网直连与多账号切换保持连续,最后按风险等级挂上内容检查。成本、稳定和安全,也就从口号落到了每一次请求里。

让 AI 落地,不必再从复杂链路开始

阿里云 Milvus 是目前唯一支持 AI Native 的 Milvus 云服务。它不仅提供高性能、全托管的向量检索能力,更将 AI Function 与 AI-Gateway 原生嵌入数据链路,让模型能力真正成为数据库的一部分。

从数据进入阿里云 Milvus 的那一刻起,理解、加工、检索和生成便可以在一条原生链路中完成。阿里云 Milvus 正在把「接入 AI」变成「原生拥有 AI」。

相关推荐
小刘快学习1 小时前
印刷包装企业的工艺问答与报价,为什么从直连模型改成了聚合网关
大数据·人工智能
菜冻鱼1 小时前
Python-pytorch-数据加载
开发语言·人工智能·pytorch·python·深度学习·机器学习
40岁资深老架构师尼恩1 小时前
RAGFlow 三大引擎 详解:DeepDoc、RAPTOR、GraphRAG
人工智能
水境传感 李兆栋1 小时前
林地农田生态观测,多光谱植被监测仪发挥哪些作用
人工智能
_風箏1 小时前
TRAE WORK 复杂任务放心交给ta
人工智能
MobotStone1 小时前
AI 评测:别瞎猜,用评估模型说话
人工智能
阿弱1 小时前
pi 扩展机制:加载、执行与能力
后端·llm·agent
大模型探索者1 小时前
2026零售与电子商务大模型训推平台选型指南:大促高并发与极致算力降本破局
大数据·人工智能·零售