用RAG做了个智能客服,上线第一天就被用户骂了——我的7天实战复盘

用RAG做了个智能客服,上线第一天就被用户骂了------我的7天实战复盘

开场:需求来得比外卖还快

周一早上九点,我刚坐下还没来得及打开工位的加湿器,产品经理老王就搬着笔记本坐到了我旁边。

"badhope,老板要个智能客服系统,两周内上线,目标是替代掉现在3个客服的人力成本。"

我差点把咖啡喷到键盘上。3个客服,每天处理大概800条咨询,涵盖退换货、物流、支付、会员等十几个业务线。现在两周之内要用AI把这事儿干了?

"不就是RAG嘛,检索+生成,我已经看过不少demo了。"我当时心里是这么想的。LangChain拉一把,向量库存一下文档,调一下GPT的接口,一个下午就能跑起来。

事实证明,我太天真了。Demo和生产之间,隔着的不是一道墙,是一片海。

第一版我花了两天就搭完了------FAISS存了200条FAQ文档,ada-002做embedding,GPT-4做生成。自己测了几条觉得还行,兴冲冲部署上去给产品体验。

结果老王问了句"我买的耳机能不能退货",系统返回了一段关于"耳机保修政策"的内容,完全答非所问。他又问"怎么申请退款",系统回了句"您可以联系客服处理"。一个智能客服让用户去找人工客服,这就很尴尬了。

那天下午,我开始了真正的RAG实战。下面是我这一周踩过的所有坑,和每个坑对应的解决方案。

文档预处理:分块策略踩坑

第一个大坑就在文档分块上。

我们的知识库有产品手册、FAQ、退换货政策、物流说明等,格式杂得很------有Word、有PDF、还有飞书文档导出的Markdown。我把所有文档拼成一堆纯文本,然后用RecursiveCharacterTextSplitter按512 token固定切,chunk overlap设了50。

看起来没什么问题对吧?但用户问"你们的退换货政策是什么",系统返回了这样一段话:

...商品签收后7日内,如商品完好无损且包装完整,可申请无理由退换。以下商品不支持退换:定制类商品、贴身衣物、鲜活易腐...

注意到了吗?返回的是半截回答。"以下商品不支持退换"后面直接断了,用户看到这个只会更困惑。

问题就出在固定长度分块上------它不管语义边界,硬生生在一个列表中间把文本切断了。政策文档里"以下商品不支持退换"后面跟着6条具体商品,结果前3条在这个chunk,后3条在下一个chunk,向量检索只召回了前一个。

我开始研究不同的分块策略,对比了三种主流方案:

分块策略 原理 优点 缺点 适用场景
固定长度分块 按token数硬切 实现简单,速度快 可能切断语义,丢失上下文 纯文本预览、快速原型
语义分块 按句子/段落语义相似度切分 保持语义完整 计算开销大,chunk长度不可控 长文档、学术论文
递归分块 按分隔符优先级递归切分 兼顾语义和长度控制 需要调分隔符和参数 企业知识库(推荐)

最后我用了RecursiveCharacterTextSplitter,但自定义了分隔符优先级------先按markdown标题切,再按双换行(段落),再按句号,最后才是字符数。同时把chunk_size从512调到了800,overlap设为200,确保列表和表格不会被切断。

python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 自定义分隔符:按语义层级递归切分
separators = [
    "\n## ",    # markdown二级标题
    "\n### ",   # markdown三级标题
    "\n\n",     # 段落
    "\n",       # 单行换行
    "。",       # 中文句号
    "!",       # 感叹号
    "?",       # 问号
    ";",       # 分号
    " ",        # 空格
    ""          # 兜底:逐字符
]

splitter = RecursiveCharacterTextSplitter(
    separators=separators,
    chunk_size=800,
    chunk_overlap=200,
    length_function=lambda x: len(x)  # 按字符数而非token
)

# 分块并附带元数据
chunks = splitter.split_documents(documents)

print(f"原始文档数: {len(documents)}")
print(f"分块后chunk数: {len(chunks)}")
print(f"平均chunk长度: {sum(len(c.page_content) for c in chunks) / len(chunks):.0f} 字符")

改完之后,"退换货政策"这个问题终于返回了完整的政策说明。但新的问题来了------文档量一大,FAISS开始撑不住了。

向量数据库选型:别选花眼

一开始我用FAISS在本地跑,200条FAQ没问题,响应很快。但当我把完整知识库(大概5000条文档chunk)灌进去之后,内存直接飙到8G,然后OOM了。

而且FAISS有个致命问题------它是纯内存的,每次重启都要重新加载全部向量。我的服务每次启动要等40秒加载索引,这在生产环境根本没法接受。

我又去调研了一圈主流的向量数据库:

向量数据库 是否需运维 支持文档量 查询延迟 成本 适用阶段
FAISS 否(纯内存) <100万 <10ms 免费 原型开发
Chroma 低(嵌入式) <100万 5-20ms 免费 开发/测试
Pinecone 否(SaaS) 10亿+ 10-50ms 按量付费 生产(不想运维)
Milvus 高(需部署) 10亿+ 5-30ms 服务器成本 生产(大规模)

说实话,如果团队没有专职运维,Pinecone是最省心的选择------完全托管,按用量付费。但我们的数据涉及用户隐私,不能上云,所以最终选了Chroma做开发环境,Milvus做生产环境

Chroma的好处是嵌入式运行,不需要单独部署服务,Python直接import就能用,开发调试非常方便。Milvus虽然部署麻烦点,但支持持久化存储、分布式扩展,生产环境扛得住。

python 复制代码
# === 开发环境:Chroma ===
import chromadb
from chromadb.config import Settings

chroma_client = chromadb.PersistentClient(path="./vector_db_dev")
collection = chroma_client.get_or_create_collection(
    name="customer_service_kb",
    metadata={"hnsw:space": "cosine"}  # 使用余弦相似度
)

# 批量写入向量
collection.add(
    ids=[f"chunk_{i}" for i in range(len(chunks))],
    documents=[c.page_content for c in chunks],
    embeddings=embeddings,  # 预计算好的向量
    metadatas=[c.metadata for c in chunks]
)

# === 生产环境:Milvus ===
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType

connections.connect(
    alias="default",
    host="milvus.internal",
    port="19530"
)

# 定义Collection Schema
fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
    FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
    FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=256),
]
schema = CollectionSchema(fields, description="客服知识库")
collection = Collection("customer_service_kb", schema)

# 创建IVF索引,平衡速度和召回率
collection.create_index(
    field_name="embedding",
    index_params={
        "index_type": "IVF_FLAT",
        "metric_type": "COSINE",
        "params": {"nlist": 1024}
    }
)

向量库解决了,但召回率还是上不去。问题出在了Embedding模型上。

Embedding模型:中文场景的大坑

这是整个项目里我踩得最狠的一个坑。

一开始我用的是OpenAI的text-embedding-ada-002,英文场景下效果确实不错。但我们的知识库全是中文,用户query也是中文。ada-002对中文的理解能力很弱------它本质上是把中文token化之后做embedding,对中文语义的捕捉远不如专门的中文模型。

具体表现就是:用户问"怎么退款",系统匹配到的是"退款进度查询",而不是"退款申请流程"。这两个在语义上很近,但ada-002算出来的余弦相似度差了一截。

我测了三个主流的中文Embedding模型:

模型 维度 中文效果 部署方式 召回率(我们的数据集)
text-embedding-ada-002 1536 一般 OpenAI API 62%
text-embedding-3-small 1536 较好 OpenAI API 71%
m3e-base 768 本地部署 78%
bge-large-zh-v1.5 1024 很好 本地部署 89%

换上bge-large-zh-v1.5之后,召回率直接从62%飙到了89%。"怎么退款"终于能正确匹配到"退款申请流程"了。bge模型是智源研究院开源的中文embedding模型,在C-MTEB榜单上表现一直很能打。

python 复制代码
from FlagEmbedding import FlagModel

# 本地部署bge-large-zh
# 模型大小约1.3G,首次加载需要下载
model = FlagModel(
    'BAAI/bge-large-zh-v1.5',
    query_instruction_for_retrieval="为这个句子生成表示用于检索相关文章:",
    use_fp16=True  # 半精度推理,省显存
)

# 生成query向量
query_embedding = model.encode_queries(["怎么申请退款"])

# 生成文档向量(批量)
doc_embeddings = model.encode_corpus([chunk.page_content for chunk in chunks])

# 写入向量库
collection.add(
    embeddings=doc_embeddings.tolist(),
    documents=[chunk.page_content for chunk in chunks],
    ids=[f"chunk_{i}" for i in range(len(chunks))]
)

经验教训:中文场景千万别用英文通用embedding模型。bge-large-zh和m3e-base都是专门针对中文优化的,效果差距不是一点半点。如果在意延迟,m3e-base更轻量(768维);如果在意效果,bge-large-zh是首选。

检索质量优化:召回率从62%到95%

换了好embedding模型,召回率到了89%。但还差一口气------有些query就是匹配不准。

最典型的场景:用户问"怎么退款",知识库里相关文档的标题是"退货流程"。向量检索能算出"退款"和"退货"在语义上接近,但有时候top-1返回的却是一条完全不相关的"账户安全"文档。原因很简单------纯向量检索对关键词的精确匹配能力很弱

用户问"怎么退款"时,他其实是想知道退款的具体步骤。但向量检索可能觉得"退款"和"提现"的语义距离更近(都涉及"把钱拿出来"),反而把"提现规则"排在了前面。

解决方案是混合检索------BM25做关键词检索 + 向量检索做语义检索,两路结果做融合排序。BM25擅长精确关键词匹配,"退款"这个关键词会直接命中包含"退款"的文档;向量检索擅长语义理解,"怎么退款"和"退款申请流程"的语义距离很近。两者互补。

然后再加一层重排序------用cross-encoder模型对召回的top-20候选做精排。向量检索用的是bi-encoder(query和doc分别编码再算相似度),速度快但精度有限;cross-encoder把query和doc拼在一起做编码,精度高但慢,所以只对top-20做重排。

python 复制代码
from rank_bm25 import BM25Okapi
from FlagEmbedding import FlagReranker
import numpy as np

class HybridRetriever:
    def __init__(self, documents, embedding_model):
        # 初始化BM25
        self.bm25 = BM25Okapi([list(doc.page_content) for doc in documents])
        self.documents = documents
        self.embedding_model = embedding_model
        # 初始化重排序模型
        self.reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True)

    def search(self, query, top_k=5):
        # --- 第一路:BM25关键词检索 ---
        bm25_scores = self.bm25.get_scores(list(query))
        bm25_top_indices = np.argsort(bm25_scores)[-20:]  # 取top-20

        # --- 第二路:向量语义检索 ---
        query_vec = self.embedding_model.encode_queries([query])
        vec_scores = self._compute_similarity(query_vec, top_k=20)
        vec_top_indices = np.argsort(vec_scores)[-20:]

        # --- 融合:RRF(Reciprocal Rank Fusion)---
        candidate_indices = set(bm25_top_indices) | set(vec_top_indices)
        rrf_scores = {}
        for idx in candidate_indices:
            bm25_rank = np.where(bm25_top_indices == idx)[0]
            vec_rank = np.where(vec_top_indices == idx)[0]
            # RRF公式:1/(60+rank)
            rrf_scores[idx] = (
                1 / (60 + len(bm25_top_indices) - bm25_rank[0]) if len(bm25_rank) > 0 else 0
            ) + (
                1 / (60 + len(vec_top_indices) - vec_rank[0]) if len(vec_rank) > 0 else 0
            )

        # 取RRF top-20进入重排序
        rrf_top = sorted(rrf_scores, key=rrf_scores.get, reverse=True)[:20]

        # --- 第三层:Cross-Encoder重排序 ---
        pairs = [[query, self.documents[i].page_content] for i in rrf_top]
        rerank_scores = self.reranker.compute_score(pairs, normalize=True)

        # 最终排序
        final_ranking = sorted(zip(rrf_top, rerank_scores), key=lambda x: x[1], reverse=True)
        return [self.documents[idx] for idx, _ in final_ranking[:top_k]]

    def _compute_similarity(self, query_vec, top_k=20):
        # 简化:实际从向量库查询
        all_vecs = np.array(self.doc_embeddings)
        return np.dot(all_vecs, query_vec.T).flatten()

混合检索 + 重排序之后,我在测试集上跑了一遍,召回率从89%提到了95%。尤其是那种"用户用词和文档用词不一样"的case,比如用户说"东西坏了想换",文档写的是"商品质量问题退换货",现在都能正确召回了。

检索优化的核心思路:向量检索管"语义相近",BM25管"关键词命中",RRF做融合,cross-encoder做精排。四层下来,召回率能稳定在90%以上。别指望单一检索方案解决所有问题。

Prompt工程:让AI说话像客服

检索质量上去了,但用户的吐槽并没有停。主要有两个问题:

第一,回答太啰嗦。用户问"退货流程是什么",GPT-4洋洋洒洒写了三段话,从退换货政策背景讲到注意事项再到操作步骤。用户只想知道"点哪个按钮、填什么信息"。

第二,偶尔出现幻觉。知识库里没有"会员等级折扣"的文档,但GPT-4有时候会自己编一个出来------"根据您的会员等级,可享受9折优惠"。用户照着操作发现根本没有这个功能,投诉就来了。

这两个问题的根源都在Prompt上。我开始设计专门的客服Prompt模板,核心思路是:角色设定 + 检索结果注入 + 硬约束条件

python 复制代码
CUSTOMER_SERVICE_PROMPT = """你是一名专业的电商客服助手,请严格按照以下规则回答用户问题。

## 角色设定
- 你是XX商城的智能客服,负责处理售前咨询、售后问题、物流查询等。
- 语气亲切专业,像一位经验丰富的客服小姐姐。
- 回答简洁直接,优先给出操作步骤,不要铺垫太多。

## 检索到的知识库内容
以下是从知识库中检索到的相关信息,请基于这些内容回答用户问题:
---
{context}
---

## 硬性约束(必须遵守)
1. 只能基于上方检索到的内容回答,绝不能编造知识库中不存在的信息。
2. 如果检索到的内容无法回答用户问题,直接说"这个问题我需要转给人工客服处理",不要猜测。
3. 涉及退款、赔偿等敏感操作时,必须提醒用户确认后再操作。
4. 回答控制在150字以内,用编号列表给出操作步骤。

## 用户问题
{question}

## 回答"""

# 构建完整的Prompt
from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_template(CUSTOMER_SERVICE_PROMPT)

# 调用LLM生成回答
chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

response = chain.invoke("怎么申请退款?")

这个Prompt模板里有几个关键设计。首先,context部分注入了检索到的文档内容,明确告诉模型"只能基于这些内容回答"。其次,硬性约束第1条和第2条直接堵死了幻觉的口子------检索不到就说转人工,不许编。最后,回答长度限制在150字以内,逼模型给干货不给废话。

防幻觉三条铁律:①检索结果为空时,必须回答"无法处理"而非自由发挥;②所有事实性信息必须能在context中找到原文出处;③涉及金钱、权限的操作类回答,必须加确认提示。这三条写进Prompt的硬约束里,幻觉率能降到5%以下。

改完Prompt之后,回答风格明显正常了。用户问"怎么退款",系统回复变成:"您可以通过以下步骤申请退款:1. 进入「我的订单」页面;2. 找到需要退款的订单,点击「申请退款」;3. 选择退款原因并提交。退款将在3-5个工作日内原路返回。"简洁、准确、有操作步骤。

上线实战:从Demo到生产

Demo能跑了,但离生产还差十万八千里。最大的问题是稳定性和成本

首先是延迟。用户问一个问题,完整的链路是:query embedding → 混合检索 → 重排序 → LLM生成。这整个链路跑下来,平均要4-6秒。用户等不了这么久,超过3秒就会觉得"卡了"。

其次是LLM的可用性。我用的GPT-4,有一天OpenAI接口抽风,响应时间飙到30秒,大量请求超时。智能客服直接瘫痪了,用户全部涌向人工客服------我们本来就是来减轻人工客服压力的,结果反而给他们添了乱。

最后是成本。GPT-4的API不便宜,每天800条query跑下来,光LLM调用费就不少。而且其中很多问题是重复的------"怎么退款""退货流程""物流查询"这几个问题占了总query的40%以上。

针对这三个问题,我做了以下架构改造:

python 复制代码
import hashlib
import time
from functools import lru_cache

class CustomerServiceRAG:
    def __init__(self):
        self.retriever = HybridRetriever(...)
        self.llm_primary = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
        self.llm_fallback = RuleEngine()  # 规则引擎兜底
        self.cache = RedisCache(ttl=3600)  # 1小时缓存
        self.call_count = 0
        self.fallback_count = 0

    def answer(self, query: str) -> dict:
        start_time = time.time()

        # 第一层:缓存命中检查
        cache_key = hashlib.md5(query.encode()).hexdigest()
        cached = self.cache.get(cache_key)
        if cached:
            return {
                "answer": cached,
                "source": "cache",
                "latency_ms": int((time.time() - start_time) * 1000)
            }

        # 第二层:检索
        docs = self.retriever.search(query, top_k=3)

        # 检索结果为空 → 直接转人工
        if not docs or docs[0].score < 0.5:
            return {
                "answer": "这个问题我需要转给人工客服处理,请稍等。",
                "source": "fallback_empty",
                "latency_ms": int((time.time() - start_time) * 1000)
            }

        # 第三层:LLM生成(带超时和降级)
        try:
            context = "\n".join([d.page_content for d in docs])
            response = self.llm_primary.invoke(
                CUSTOMER_SERVICE_PROMPT.format(context=context, question=query),
                timeout=5  # 5秒超时
            )
            answer = response.content
            self.cache.set(cache_key, answer)  # 写入缓存
            self.call_count += 1
            return {
                "answer": answer,
                "source": "llm",
                "latency_ms": int((time.time() - start_time) * 1000)
            }
        except (TimeoutError, Exception) as e:
            # LLM挂了 → 规则引擎兜底
            self.fallback_count += 1
            answer = self.llm_fallback.match(query, docs)
            return {
                "answer": answer,
                "source": "fallback_rule",
                "latency_ms": int((time.time() - start_time) * 1000)
            }

这套架构里有三层保障。缓存层用Redis存历史query的答案,命中缓存直接返回,延迟降到50ms以内。LLM调用加了5秒超时,超时后自动降级到规则引擎------规则引擎就是一套基于关键词匹配的if-else逻辑,虽然不如LLM灵活,但至少不会挂。检索结果置信度太低时(score < 0.5),直接转人工,不强行回答。

上线前后的关键指标对比如下:

指标 Demo版本 生产版本 提升幅度
平均响应延迟 4.8s 1.2s 75%↓
P99延迟 12s 3.5s 71%↓
缓存命中率 0% 38% ---
LLM调用次数/天 800 496 38%↓
可用性(SLA) 92% 99.5% 7.5%↑
幻觉率 15% 4% 73%↓

加了缓存之后,38%的重复query直接命中缓存返回,LLM调用量下降了三分之一,成本也跟着降了。延迟从4.8秒降到1.2秒,用户体感好了很多。

成本与性能:老板最关心的

系统上线后第一周,老板问了我一个问题:"每天跑这个AI客服要花多少钱?"

这个问题不回答清楚,这个项目就保不住。我算了一笔账。

每个query的完整链路消耗的token大约是:检索到的context(约1500 token)+ Prompt模板(约300 token)+ 用户query(约20 token)+ LLM输出(约150 token)= 总计约2000 token/query。每天800条query,大约消耗160万token。

用GPT-4的话,按input 30/M、output30/M、output 30/M、output60/M算,每天成本大概 50,一个月50,一个月 50,一个月1500。用GPT-3.5的话,成本只有GPT-4的十分之一。但GPT-3.5在复杂问题上的理解能力确实差一截。

最后的方案是分级路由------简单问题用GPT-3.5,复杂问题才升级到GPT-4:

LLM模型 单次成本 回答质量 延迟 适用场景
GPT-4 Turbo ~$0.06 优秀 3-5s 复杂售后、纠纷处理
GPT-3.5 Turbo ~$0.006 良好 1-2s 常见问题、操作指引
规则引擎 $0 一般 <100ms LLM不可用时的兜底
缓存 $0 --- <50ms 重复query
python 复制代码
class LLMRouter:
    """分级路由:根据问题复杂度选择LLM"""

    # 复杂问题关键词(升级到GPT-4)
    COMPLEX_KEYWORDS = [
        "投诉", "纠纷", "差评", "赔偿", "法律",
        "异常", "错误", "被骗", "退款失败", "多次"
    ]

    def __init__(self):
        self.llm_fast = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
        self.llm_smart = ChatOpenAI(model="gpt-4-turbo", temperature=0)

    def route(self, query: str, retrieved_docs: list) -> object:
        # 复杂度判断:关键词匹配 + 检索结果数量
        is_complex = any(kw in query for kw in self.COMPLEX_KEYWORDS)
        is_complex = is_complex or len(retrieved_docs) > 3  # 多个相关文档说明问题复杂

        if is_complex:
            return self.llm_smart  # GPT-4处理
        else:
            return self.llm_fast  # GPT-3.5处理

# 使用
router = LLMRouter()
llm = router.route(query, docs)
response = llm.invoke(prompt.format(context=context, question=query))

分级路由上线后,大约75%的query走GPT-3.5,20%走GPT-4,5%走规则引擎兜底 。月成本从预估的 1500降到了∗∗1500降到了** 1500降到了∗∗420左右**,效果几乎没有下降------因为大多数客服问题本来就是重复的标准化问题,GPT-3.5完全能处理。

结尾复盘:RAG做客服,够用但别神化

上线一周后,我拉了一份数据。

指标 数据
日均处理query 812条
AI自动解决率 72%
转人工率 28%
用户满意度评分 4.1/5.0
人工客服工作量减少 约60%

72%的自动解决率,虽然没达到产品经理最初设想的"替代全部人工",但确实把人工客服的工作量砍掉了60%。剩下28%转人工的,大多是涉及金额纠纷、个性化投诉这种需要人工判断的case------这些本来就不该指望AI处理。

回头看这一周的踩坑历程,RAG客服系统的技术优先级其实很明确:

分块策略 > Embedding模型 > 检索方法 > Prompt工程。前两个决定了你的知识库质量上限,后两个决定了你能逼近这个上限到什么程度。很多人一上来就调Prompt、换LLM,但如果不把分块和Embedding搞好,上层怎么调都是白搭。

还有一点想说------RAG不是万能的。它能处理的是"知识库里有的、可检索的、标准化的问题"。对于需要推理、需要多轮交互、需要理解用户情绪的复杂场景,光靠RAG是不够的。但做客服?足够了。至少足够帮你的客服团队挡掉70%的重复劳动,让他们把精力留给真正需要人工判断的问题。

最后给准备做RAG客服的同学一个建议:别急着写代码,先把你的知识库整理好。文档质量决定了RAG的天花板,技术方案只是在逼近这个天花板。文档乱七八糟,再牛的检索算法也救不回来。

这一周踩的坑比过去半年都多,但系统跑起来的那一刻,看着日志里一条条被自动解决的query,还是有点小爽的。毕竟,用技术解决真实问题,不就是我们写代码的初衷嘛。

相关推荐
骄阳如火1 小时前
论文撰写SKILLS实测三|PaperSpine:每个阶段都是带硬关卡的 gate,审计能直接 BLOCK 你
人工智能
dunge20261 小时前
2026年8月更新:ChatGPT与Codex开发实践——从工具使用到AI工程工作流,开发者如何建立长期生产力体系(GPT-5.6技术分享)
人工智能·gpt·chatgpt
硅基流动1 小时前
OPC 南川:超级个体的疯狂探索|开发者说
人工智能
147API1 小时前
AI 图片编辑接口报 400 怎么排查?先读错误体,再查参数、图片与 mask
网络·人工智能
xiaoxiaoxiaolll1 小时前
AI-有限元融合的复合材料多尺度建模与性能
人工智能
zyplayer-doc2 小时前
同一份制度别复制到多个知识库:用zyplayer-doc引用文档解决重复维护
javascript·人工智能·智能手机·开源·ocr
ASKED_20192 小时前
从 Chat Completions 到 Agent Runtime:主流大模型接口协议全景与设计对比
人工智能
站长工具箱2 小时前
讯飞Loomy测评:整合飞书钉钉QQ消息的AI自动办公工具深度体验
人工智能·钉钉·飞书
小刘快学习2 小时前
广告素材生产,直连模型还是走聚合网关
人工智能