AI模型成本优化实战:6款开源工具实测,Token消耗降低40%-95%

前言:Token账单,AI时代最真实的焦虑

2026年,我见过很多团队经历同一个轨迹:兴奋地接入大模型API,做出Demo,用户开始用,然后------月底收到账单,CEO一脸懵。

一个日活1万的AI客服,每用户每天平均5次对话,每次输入500 Token + 输出200 Token。按Claude Sonnet价格算:

  • 输入:10000 × 5 × 500 = 2500万Token/天 ≈ 约¥75/天
  • 输出:10000 × 5 × 200 = 1000万Token/天 ≈ 约¥150/天
  • 合计:约¥225/天,一个月约¥6750

这还只是模型调用费用,不含服务器、存储、带宽。当用户增长到10万时,这个数字直接变成6.75万/月。

本文整理了我和团队在实际项目中验证过的6款开源工具和配套方案,每款工具都附有真实的节省幅度和踩坑记录,拿来即用。


一、先找到你的"Token黑洞"

在谈优化工具之前,必须先做诊断。Token浪费通常藏在以下几个地方,不同的黑洞需要不同的工具来填。

典型的Token浪费模式

python 复制代码
# 诊断脚本:统计你的API调用中Token的分布情况

import anthropic
from dataclasses import dataclass, field
from typing import List
import statistics

@dataclass
class TokenUsageRecord:
    input_tokens: int
    output_tokens: int
    prompt_preview: str  # Prompt前100字,用于分类
    
class TokenDiagnostics:
    """
    使用前的注意事项:
    这个工具需要在你现有的API调用中插入,
    建议先在测试环境跑一周,积累足够的样本再分析。
    不要在生产环境直接保存完整的prompt内容(可能含敏感信息),
    这里只保存前100字用于分类。
    """
    
    def __init__(self):
        self.records: List[TokenUsageRecord] = []
        self.client = anthropic.Anthropic()
    
    def tracked_create(self, **kwargs) -> anthropic.types.Message:
        """包装原有的client.messages.create,自动记录Token使用"""
        response = self.client.messages.create(**kwargs)
        
        # 从响应中提取Token使用量
        # 踩坑:usage字段在不同版本的SDK中位置可能不同
        usage = response.usage
        
        # 提取Prompt预览(只取第一条user消息的前100字)
        prompt_preview = ""
        for msg in kwargs.get("messages", []):
            if msg.get("role") == "user":
                content = msg.get("content", "")
                if isinstance(content, str):
                    prompt_preview = content[:100]
                elif isinstance(content, list):
                    for block in content:
                        if isinstance(block, dict) and block.get("type") == "text":
                            prompt_preview = block.get("text", "")[:100]
                            break
                break
        
        record = TokenUsageRecord(
            input_tokens=usage.input_tokens,
            output_tokens=usage.output_tokens,
            prompt_preview=prompt_preview
        )
        self.records.append(record)
        
        return response
    
    def generate_report(self) -> str:
        """生成Token使用分析报告"""
        if not self.records:
            return "没有记录数据"
        
        input_tokens = [r.input_tokens for r in self.records]
        output_tokens = [r.output_tokens for r in self.records]
        
        report = f"""
=== Token使用诊断报告 ===
样本数量:{len(self.records)} 次调用

【输入Token】
- 平均:{statistics.mean(input_tokens):.0f}
- 中位数:{statistics.median(input_tokens):.0f}
- 最大:{max(input_tokens)}
- 最小:{min(input_tokens)}

【输出Token】
- 平均:{statistics.mean(output_tokens):.0f}
- 中位数:{statistics.median(output_tokens):.0f}
- 最大:{max(output_tokens)}

【异常检测】
输入Token > 均值2倍的调用数:{sum(1 for t in input_tokens if t > statistics.mean(input_tokens) * 2)}
(这些调用值得重点检查,可能是Prompt过长或上下文未清理)

输出Token > 1000的调用数:{sum(1 for t in output_tokens if t > 1000)}
(检查是否max_tokens设置过大或输出冗余)
"""
        return report

通过诊断,我在实际项目中发现的最常见的三个黑洞:

  1. 重复的System Prompt:每次调用都传入同一份2000字的System Prompt,占总成本的60%+
  2. 无限增长的对话历史:多轮对话中,把所有历史消息都塞进去,导致第20轮对话时输入Token是第1轮的20倍
  3. 冗余的输出:max_tokens设置远大于实际需要,模型输出了大量"请注意..."之类的废话

二、6款工具实测

工具1:Anthropic Prompt Caching(官方缓存)

节省幅度:60%-90%(适用于System Prompt重复场景)

这是我目前见过ROI最高的单一优化手段,没有之一。

原理:把不经常变化的内容(System Prompt、知识库、文档)标记为可缓存,Anthropic服务端会把这部分内容的KV缓存保存一段时间(约5分钟)。缓存命中时,这部分Token的费用降低90%

python 复制代码
import anthropic

client = anthropic.Anthropic()

# 这是你的知识库或System Prompt(假设有5000字)
KNOWLEDGE_BASE = """
[你的产品文档、FAQ、规则手册等静态内容...]
这里是5000字的静态内容,每次调用都传入但内容完全不变。
""" * 50  # 模拟长内容

def answer_question_with_caching(user_question: str) -> str:
    """
    使用Prompt Caching的正确姿势
    
    踩坑记录1:cache_control必须放在内容的"断点"处,
    即你希望缓存截止的位置之后。
    把cache_control放错位置会导致缓存失效。
    
    踩坑记录2:内容必须超过1024个Token才能被缓存。
    如果你的静态内容比较短,可以考虑把多个静态部分合并。
    
    踩坑记录3:缓存TTL(生存时间)约为5分钟(Anthropic官方说明)。
    如果你的调用间隔超过5分钟,缓存会失效,需要重建。
    对于高频场景(每分钟多次调用),缓存效益非常高。
    """
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=500,
        system=[
            {
                "type": "text",
                "text": KNOWLEDGE_BASE,
                "cache_control": {"type": "ephemeral"}  # 标记此部分为可缓存
                # "ephemeral"是目前唯一支持的类型,代表短期缓存
            },
            {
                "type": "text",
                "text": "你是一名专业的客服代表,根据上方知识库回答用户问题,如果知识库没有答案请如实说明。"
                # 注意:这部分没有cache_control,所以每次都会重新计算
                # 但它很短,成本可以忽略不计
            }
        ],
        messages=[{"role": "user", "content": user_question}]
    )
    
    # 检查缓存命中情况(调试用)
    if hasattr(response.usage, 'cache_read_input_tokens'):
        cache_read = response.usage.cache_read_input_tokens
        cache_write = getattr(response.usage, 'cache_creation_input_tokens', 0)
        regular_input = response.usage.input_tokens
        print(f"Token使用:普通输入={regular_input}, 缓存写入={cache_write}, 缓存读取={cache_read}")
        
        # 估算节省的费用
        if cache_read > 0:
            saved_fraction = cache_read / (cache_read + regular_input + cache_write)
            print(f"此次调用Token节省约:{saved_fraction:.1%}")
    
    return response.content[0].text

# 实测数据(我们的客服系统):
# 未启用缓存前:平均每次调用 4500 input tokens
# 启用缓存后(高频时段):平均每次调用 4000 cache_read + 500 real_input
# cache_read的费用是普通input的10%
# 实际降本:(4000 * 0.9) / 4500 ≈ 80% 的成本节省

工具2:LLMLingua(微软开源,Prompt压缩)

节省幅度:40%-70%(适用于长文档检索场景)

LLMLingua是微软研究院开源的Prompt压缩工具,核心思路是:用一个小模型先"预读"你的Prompt,识别并删除对任务没有价值的内容(冗余词、无关句子),然后把压缩后的Prompt发给大模型。

bash 复制代码
# 安装
pip install llmlingua
python 复制代码
from llmlingua import PromptCompressor

# 初始化压缩器(第一次运行会下载约1.5GB的小模型,需要稳定网络)
# 踩坑:llm_lingua_model_name指定的模型需要能在本地运行,
# 如果服务器没有GPU,需要改用CPU版本(设置device_map="cpu"),
# 速度会慢3-5倍,但功能正常。
compressor = PromptCompressor(
    model_name="microsoft/llmlingua-2-xlm-roberta-large-meetingbank",
    use_llmlingua2=True,  # 使用第二代,效果更好
    device_map="cuda"  # 有GPU用cuda,没有用cpu
)

# 示例:压缩一段从RAG检索到的长文档
long_retrieved_context = """
【检索到的文档片段1】
根据我们的服务协议第3.2条款,用户在购买产品后享有7天无理由退款权利。
退款申请需要通过官方App提交,提交后我们的客服团队将在3个工作日内处理。
退款金额将原路退回,信用卡退款通常需要5-7个工作日到账,
支付宝/微信支付退款通常在24小时内到账。
需要注意的是,定制化商品、已拆封的电子产品和食品类商品不在无理由退款范围内。
如果商品存在质量问题,则不受7天限制,我们提供365天的质量问题退换货服务。

【检索到的文档片段2】  
关于会员积分:用户每消费1元获得1个积分。
积分可以在结算时抵扣,100积分=1元。
积分有效期为2年,到期未使用自动清零。
会员等级分为:普通会员(0-999积分)、银牌会员(1000-4999积分)、
金牌会员(5000-19999积分)、钻石会员(20000积分以上)。
不同等级享受不同折扣:普通无折扣、银牌95折、金牌9折、钻石88折。

[... 更多检索到的文档片段,总共可能有3000-5000 Token ...]
"""

user_question = "我买的手机想退货,能退吗?"

# 压缩文档
compressed = compressor.compress_prompt(
    context=long_retrieved_context.split("\n\n"),  # 按段落分割
    instruction="回答用户关于退款政策的问题",
    question=user_question,
    target_token=500,   # 目标压缩到500 Token
    # 踩坑:target_token只是"目标",不是精确值。
    # 如果设得太低,压缩可能损失关键信息,导致回答不准确。
    # 建议先设为原始长度的30-40%,测试回答质量后再调整。
    condition_compare=True,
    iterative_size=200
)

print(f"原始长度:{len(long_retrieved_context.split())} 词")
print(f"压缩后长度:{len(compressed['compressed_prompt'].split())} 词")
print(f"压缩比:{compressed['ratio']:.1%}")
print(f"\n压缩后的Prompt:\n{compressed['compressed_prompt'][:500]}...")

# 然后用压缩后的内容调用大模型
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=300,
    messages=[{
        "role": "user",
        "content": f"基于以下信息回答问题:\n{compressed['compressed_prompt']}\n\n问题:{user_question}"
    }]
)

注意事项:LLMLingua的压缩质量与原文的冗余程度强相关。对于已经非常精炼的文本(如法律条文、数学公式),过度压缩可能导致信息失真,建议只对RAG检索到的大段文档使用,不要对关键指令压缩。


工具3:对话历史截断(自研方案)

节省幅度:50%-80%(适用于多轮对话场景)

这是最简单但最容易被忽视的优化。在多轮对话中,把所有历史消息原封不动地传入是最常见的Token浪费。

python 复制代码
from typing import List, Dict
import tiktoken  # OpenAI开源的Token计数库,对大多数模型有参考价值

class SmartContextManager:
    """
    智能上下文管理器
    
    为什么不直接用"只保留最近N条消息"的简单截断:
    简单截断会切断对话的逻辑连贯性。
    比如用户在第1条消息说了"我的订单号是12345",
    第10条消息问"帮我查一下状态",
    如果截断了第1条,模型不知道订单号是多少。
    
    正确的做法是:保留摘要 + 保留最近几轮完整对话。
    """
    
    def __init__(self, max_tokens: int = 4000, summary_threshold: int = 3000):
        self.max_tokens = max_tokens
        self.summary_threshold = summary_threshold  # 超过此值时触发摘要
        self.messages: List[Dict] = []
        self.summary: str = ""  # 历史对话的压缩摘要
        self.client = anthropic.Anthropic()
        
        # 用于估算Token数的编码器(cl100k_base适用于大多数现代模型)
        try:
            self.encoder = tiktoken.get_encoding("cl100k_base")
        except:
            self.encoder = None  # 如果tiktoken不可用,退化到按字符数估算
    
    def _count_tokens(self, text: str) -> int:
        """估算文本的Token数"""
        if self.encoder:
            return len(self.encoder.encode(text))
        else:
            # 粗略估算:中文约1.5字/token,英文约0.75词/token
            return max(len(text) // 2, 1)
    
    def _total_context_tokens(self) -> int:
        """计算当前上下文的总Token数"""
        total = self._count_tokens(self.summary)
        for msg in self.messages:
            content = msg.get("content", "")
            if isinstance(content, str):
                total += self._count_tokens(content)
        return total
    
    def add_message(self, role: str, content: str) -> None:
        """添加消息,超出阈值时自动触发摘要"""
        self.messages.append({"role": role, "content": content})
        
        if self._total_context_tokens() > self.summary_threshold:
            self._compress_old_messages()
    
    def _compress_old_messages(self) -> None:
        """
        将旧消息压缩为摘要
        
        策略:保留最近4条消息(2轮对话)的完整内容,
        对更早的消息生成摘要,替换原始内容。
        
        为什么保留最近2轮:
        用户的最新意图和上下文通常在最近的对话中,
        更早的内容大多是历史背景,适合压缩。
        """
        if len(self.messages) <= 4:
            return  # 消息太少,不需要压缩
        
        # 分离旧消息和新消息
        old_messages = self.messages[:-4]
        recent_messages = self.messages[-4:]
        
        # 为旧消息生成摘要
        old_messages_text = "\n".join([
            f"{msg['role'].upper()}: {msg['content']}"
            for msg in old_messages
        ])
        
        summary_prompt = f"""
请将以下对话历史压缩成一段简洁的摘要(100字以内)。
摘要应保留:用户的核心需求、已确认的关键信息(如订单号、用户信息)、
已解决和未解决的问题。

对话历史:
{old_messages_text}

摘要(只输出摘要内容,不要加前缀):
"""
        
        response = self.client.messages.create(
            model="claude-sonnet-4-6",  # 用便宜的模型生成摘要即可
            max_tokens=200,
            messages=[{"role": "user", "content": summary_prompt}]
        )
        
        new_summary = response.content[0].text
        
        # 合并新旧摘要
        if self.summary:
            self.summary = f"[历史摘要]\n{self.summary}\n\n[新增摘要]\n{new_summary}"
        else:
            self.summary = new_summary
        
        # 只保留最近4条消息
        self.messages = recent_messages
        
        tokens_after = self._total_context_tokens()
        print(f"上下文压缩完成,当前约 {tokens_after} Token")
    
    def build_messages_for_api(self) -> List[Dict]:
        """构建发送给API的消息列表"""
        if not self.summary:
            return self.messages
        
        # 将摘要作为第一条user消息注入
        # 注意:这里用了一个小技巧,通过assistant的确认来让模型"接受"摘要
        context_messages = [
            {
                "role": "user",
                "content": f"[对话背景摘要,请在回答时参考]\n{self.summary}"
            },
            {
                "role": "assistant",
                "content": "好的,我已了解之前的对话背景,请继续。"
            }
        ] + self.messages
        
        return context_messages


# 使用示例
manager = SmartContextManager(max_tokens=4000, summary_threshold=2000)

# 模拟多轮对话
conversations = [
    ("user", "你好,我的订单号是ORD-2026-78901,想查询一下状态"),
    ("assistant", "您好!我查到您的订单ORD-2026-78901,目前状态是'已发货',预计明天下午送达。"),
    ("user", "好的,那如果我想修改收货地址还来得及吗?"),
    ("assistant", "很抱歉,订单已发货,无法修改收货地址。建议您联系快递员或到附近快递站取件。"),
    # ... 更多对话
]

for role, content in conversations:
    manager.add_message(role, content)
    if role == "user":
        messages = manager.build_messages_for_api()
        print(f"当前发送给API的消息数:{len(messages)}")

工具4:模型分层路由

节省幅度:40%-60%(适用于混合复杂度的查询场景)

核心思路:不是所有问题都需要最聪明最贵的模型。把查询按复杂度分级,简单问题用便宜模型,复杂问题才动用高级模型。

python 复制代码
class ModelRouter:
    """
    智能模型路由器
    
    成本对比(2026年5月参考价,以每百万Token计):
    - Claude Haiku 4.5:输入 ¥0.8 / 输出 ¥4
    - Claude Sonnet 4.6:输入 ¥18 / 输出 ¥90
    - Claude Opus 4.6:输入 ¥112 / 输出 ¥560
    
    价格差距高达10-140倍,分层路由的节省空间非常大。
    
    踩坑:分类本身也要花Token。
    这里的分类器要尽量简单快速,
    不然分类费用可能比节省的费用还高。
    """
    
    MODELS = {
        "fast": "claude-haiku-4-5-20251001",   # 最快最便宜
        "standard": "claude-sonnet-4-6",        # 性价比最高
        "advanced": "claude-opus-4-6"           # 最强但最贵
    }
    
    def __init__(self):
        self.client = anthropic.Anthropic()
        self.routing_stats = {"fast": 0, "standard": 0, "advanced": 0}
    
    def classify_query(self, query: str) -> str:
        """
        快速分类查询复杂度
        这个分类用最便宜的模型,且严格限制输出长度
        """
        classification_prompt = f"""
将以下用户查询分类为:fast、standard 或 advanced。

规则:
- fast:简单FAQ、单一事实查询、简短翻译(如"你们几点开门"、"退款要几天")
- standard:需要一定分析的查询、多步骤问题、中等长度的写作任务
- advanced:复杂推理、代码生成、需要深度专业知识、法律/医疗/金融建议

查询:{query}

只输出一个词:fast、standard 或 advanced。
"""
        response = self.client.messages.create(
            model=self.MODELS["fast"],  # 用最便宜的模型做分类
            max_tokens=5,  # 只需要输出一个词,限制到5个Token
            temperature=0,
            messages=[{"role": "user", "content": classification_prompt}]
        )
        
        level = response.content[0].text.strip().lower()
        if level not in ["fast", "standard", "advanced"]:
            level = "standard"  # 分类失败时默认用标准模型
        
        return level
    
    def route_and_answer(self, query: str, system_prompt: str = "") -> tuple[str, str]:
        """
        路由查询并获取答案
        返回:(答案, 使用的模型级别)
        """
        level = self.classify_query(query)
        self.routing_stats[level] += 1
        model = self.MODELS[level]
        
        kwargs = {
            "model": model,
            "max_tokens": 500 if level == "fast" else 1000 if level == "standard" else 2000,
            "messages": [{"role": "user", "content": query}]
        }
        if system_prompt:
            kwargs["system"] = system_prompt
        
        response = self.client.messages.create(**kwargs)
        return response.content[0].text, level
    
    def get_cost_report(self) -> str:
        total = sum(self.routing_stats.values())
        if total == 0:
            return "暂无数据"
        
        return f"""
路由统计报告:
- Fast(Haiku):{self.routing_stats['fast']} 次({self.routing_stats['fast']/total:.1%})
- Standard(Sonnet):{self.routing_stats['standard']} 次({self.routing_stats['standard']/total:.1%})
- Advanced(Opus):{self.routing_stats['advanced']} 次({self.routing_stats['advanced']/total:.1%})
"""

工具5:输出长度控制

节省幅度:20%-40%(几乎适用于所有场景)

这是最容易实施、最常被忽视的优化。输出Token通常比输入贵3-5倍,而很多系统让模型"自由发挥"输出长度,产生了大量无效的冗余内容。

python 复制代码
# 常见的输出冗余类型和解决方案

class OutputOptimizer:
    
    def build_concise_system_prompt(self, role: str, task: str) -> str:
        """
        在System Prompt中加入明确的输出简洁指令
        
        踩坑:只说"简洁"效果不佳,模型对"简洁"的理解因人而异。
        需要给出具体约束:字数限制、禁止特定模式、格式要求。
        """
        return f"""
你是{role}。{task}

【输出格式要求 - 严格遵守】
1. 直接回答问题,不要以"好的"、"当然"、"非常感谢您的问题"等开头
2. 不要在答案末尾加"希望这个回答对您有帮助"等套话
3. 如无必要,不使用markdown格式(用户在纯文本环境中)
4. 回答控制在{200}字以内,除非问题本身需要更长的解释
5. 不要重复用户的问题
"""
    
    def add_length_constraint(self, prompt: str, max_chars: int) -> str:
        """在Prompt末尾追加字数约束"""
        return f"{prompt}\n\n请将回答控制在{max_chars}字以内。"

工具6:批量处理(Anthropic Batch API)

节省幅度:50%(适用于非实时批量任务)

对于不需要实时响应的任务(如批量数据处理、离线标注、定期报告生成),Anthropic的Batch API提供50%的折扣,且可以同时处理最多10万条请求。

python 复制代码
import anthropic
import json

client = anthropic.Anthropic()

def process_batch_requests(items: list[dict]) -> list[dict]:
    """
    使用Batch API批量处理请求
    
    适用场景:
    - 数据集的批量分类/标注
    - 定时报告生成
    - 离线的文档摘要处理
    
    不适用场景:
    - 实时用户对话(Batch API有延迟,通常1-24小时才能完成)
    - 需要流式输出的场景
    
    踩坑:Batch API的结果不保证顺序,必须用custom_id来对应请求和结果。
    在下面的代码中,我们用商品ID作为custom_id,确保结果能正确对应。
    """
    
    # 构建批量请求
    requests = []
    for item in items:
        requests.append({
            "custom_id": f"item_{item['id']}",  # 必须唯一,用于结果对应
            "params": {
                "model": "claude-haiku-4-5-20251001",  # Batch通常用便宜模型
                "max_tokens": 200,
                "messages": [{
                    "role": "user",
                    "content": f"为以下商品生成50字营销描述:{item['name']},特点:{item['features']}"
                }]
            }
        })
    
    # 提交批处理任务
    batch = client.beta.messages.batches.create(requests=requests)
    print(f"批处理任务已提交,ID:{batch.id}")
    print(f"任务状态:{batch.processing_status}")
    
    # 轮询等待完成(实际生产中建议用webhook或定时任务,而不是阻塞等待)
    import time
    while True:
        batch = client.beta.messages.batches.retrieve(batch.id)
        if batch.processing_status == "ended":
            break
        print(f"处理中...已完成:{batch.request_counts.succeeded}/{len(requests)}")
        time.sleep(30)  # 每30秒检查一次
    
    # 获取结果
    results = []
    for result in client.beta.messages.batches.results(batch.id):
        if result.result.type == "succeeded":
            results.append({
                "id": result.custom_id.replace("item_", ""),
                "description": result.result.message.content[0].text
            })
        else:
            print(f"请求 {result.custom_id} 失败:{result.result.error}")
    
    return results

三、组合方案:实测降本80%的完整配置

以下是我们在生产环境中实际部署的组合方案,综合了以上6种工具:

优化手段 适用场景 节省幅度 实施难度
Prompt Caching System Prompt重复 60-90%
对话历史截断 多轮对话 50-80%
模型分层路由 混合复杂度查询 40-60%
输出长度控制 所有场景 20-40%
LLMLingua压缩 RAG/长文档场景 40-70%
Batch API 离线批量任务 50%

重要提示:这些手段的节省幅度不是简单相加的,实际组合效果取决于你的具体业务场景和调用模式。建议从"实施难度低"的手段开始,每实施一项都要测量实际效果,而不是一次性全部上。


总结

Token成本优化不是"省小钱",在AI产品规模化的过程中,它往往决定了产品是否能盈利。

从我的实战经验来看,大多数团队在没有做任何优化的情况下,至少有50%的Token是被浪费的。而这些浪费,往往可以通过本文介绍的工具,在不降低用户体验的前提下消除。

最后记住一个原则:先测量,再优化,再验证。不要凭感觉判断哪里浪费最多,用诊断工具找到真正的黑洞,才能把钱花在刀刃上。

相关推荐
Fzuim1 个月前
Claude Code v2.1.88 三层「自愈记忆」架构深度解析
ai·架构·claude code·上下文管理·记忆机制
七牛云行业应用2 个月前
GPT-5.4 mini 与 nano 深度评测:核心差异、API 成本实测与选型指南
人工智能·openai·api调用·gpt-5.4·大模型降本
大傻^2 个月前
Prompt提示词设计工程:从原则到实战的系统性方法论(附模板与调试工具)
prompt·提示词优化·a/b测试·上下文管理·角色提示
大傻^2 个月前
【OpenClaw -07】OpenClaw 记忆系统:三层记忆架构与 Daily Notes 机制
embedding·rag·长期记忆·上下文管理·openclaw·记忆架构
Light603 个月前
MCP:AI智能体时代的“USB-C接口“,微调终结者还是效率革命?
工具调用·ai智能体·mcp协议·企业ai·上下文管理·微调替代·标准化接口
董厂长4 个月前
langchain上下文管理的方式
langchain·上下文压缩·上下文管理