⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来

⚡ Promise.all 性能优化:让 Agent 的工具调用飞起来

摘要:Agent 一次要读 3 个文件,串行要 6 秒,并行只要 2 秒。本文从 Promise 基础讲起,手把手教你用 Promise.all 优化 Agent 的工具调用性能,附带完整的错误处理方案。


📌 前言

上一篇我们实现了 ReAct 循环,Agent 能自主规划、分步执行任务了。

但有一个问题:当 LLM 一次返回多个 tool_calls 时,我们是串行执行的。

javascript 复制代码
// ❌ 串行执行:一个一个来
for (const toolCall of response.tool_calls) {
    const result = await tool.invoke(toolCall.args);  // 每个都要等
}

如果 LLM 一次要读 3 个文件,每个读取需要 2 秒,串行就是 6 秒。

用 Promise.all 并行执行,只需要 2 秒。


🎯 本文适合谁

  • 对 Promise 了解但不深入的同学
  • 想优化 Agent 工具调用性能的开发者
  • 想搞懂 Promise.all / Promise.allSettled / Promise.race 区别的同学

📚 一、Promise 基础回顾

什么是 Promise?

Promise 是 ES6 提供的异步编程解决方案。它代表一个未来才会完成的操作

javascript 复制代码
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        resolve('完成!');  // 成功
        // reject('失败!');  // 失败
    }, 1000);
});

Promise 的三个状态

scss 复制代码
┌───────────┐
│  Pending   │ ← 初始状态
│  等待中...  │
└─────┬─────┘
      │
      ├──── resolve() ──→ ┌───────────┐
      │                    │ Fulfilled  │
      │                    │  已完成 ✅  │
      │                    └───────────┘
      │
      └──── reject()  ──→ ┌───────────┐
                          │ Rejected   │
                          │  已拒绝 ❌  │
                          └───────────┘

关键规则

  • 状态只能从 Pending 变为 FulfilledRejected 之一
  • 一旦变化,不可逆转

async / await:Promise 的语法糖

javascript 复制代码
// Promise 写法
function getData() {
    return fetch('/api/data')
        .then(res => res.json())
        .then(data => console.log(data))
        .catch(err => console.error(err));
}

// async/await 写法(更优雅)
async function getData() {
    try {
        const res = await fetch('/api/data');
        const data = await res.json();
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}

💡 await 会暂停当前函数的执行,等 Promise 完成后再继续。它把异步代码写成了同步的风格,可读性大大提升。


📚 二、串行 vs 并行

场景模拟

假设我们要同时获取天气和推文数据:

javascript 复制代码
function getWeather() {
    return new Promise((resolve) => {
        setTimeout(() => resolve({ temp: 38, conditions: '晴转多云' }), 2000);
    });
}

function getTweets() {
    return new Promise((resolve) => {
        setTimeout(() => resolve(['今天天气真好', '周末去爬山']), 500);
    });
}

❌ 串行执行

javascript 复制代码
async function serial() {
    console.time('串行');
    const weather = await getWeather();   // 等 2000ms
    const tweets = await getTweets();     // 再等 500ms
    console.log(weather, tweets);
    console.timeEnd('串行');  // 总耗时 ≈ 2500ms
}
markdown 复制代码
时间线:
0ms ──────────── 2000ms ──── 2500ms
|──── getWeather ────|─ getTweets ─|
                   总耗时:2500ms

✅ 并行执行

javascript 复制代码
async function parallel() {
    console.time('并行');
    const [weather, tweets] = await Promise.all([getWeather(), getTweets()]);
    console.log(weather, tweets);
    console.timeEnd('并行');  // 总耗时 ≈ 2000ms
}
markdown 复制代码
时间线:
0ms ──────────── 2000ms
|──── getWeather ────|
|─ getTweets ─|
              总耗时:2000ms(取最慢的那个)

节省了 500ms! 如果任务更多、耗时差距更大,优化效果会更明显。


📚 三、Promise.all 详解

基本用法

javascript 复制代码
const results = await Promise.all([promise1, promise2, promise3]);
// results = [result1, result2, result3]

核心特性

特性 说明
并行执行 所有 Promise 同时开始执行
全部成功才成功 所有 Promise 都 resolve 后,才返回结果数组
一个失败就失败 任意一个 reject,Promise.all 立即 reject
结果有序 返回数组的顺序和传入 Promise 的顺序一致

结果顺序

javascript 复制代码
const p1 = new Promise(resolve => setTimeout(() => resolve('第一个'), 3000));
const p2 = new Promise(resolve => setTimeout(() => resolve('第二个'), 1000));
const p3 = new Promise(resolve => setTimeout(() => resolve('第三个'), 2000));

const results = await Promise.all([p1, p2, p3]);
console.log(results);  // ['第一个', '第二个', '第三个']
//                        ↑ p1的结果  ↑ p2的结果  ↑ p3的结果
//                        顺序和传入顺序一致,不是完成顺序!

错误处理

javascript 复制代码
// ❌ 错误示范:一个失败,全部失败
const p1 = Promise.resolve('成功');
const p2 = Promise.reject('失败');
const p3 = Promise.resolve('成功');

try {
    const results = await Promise.all([p1, p2, p3]);
} catch (err) {
    console.log(err);  // '失败'
    // results 拿不到!因为 Promise.all 直接 reject 了
}

📚 四、Promise.all 的替代方案

Promise.allSettled:不管成败,全部返回

javascript 复制代码
const results = await Promise.allSettled([
    Promise.resolve('成功'),
    Promise.reject('失败'),
    Promise.resolve('成功'),
]);

console.log(results);
// [
//   { status: 'fulfilled', value: '成功' },
//   { status: 'rejected', reason: '失败' },
//   { status: 'fulfilled', value: '成功' },
// ]

适用场景:Agent 工具调用 ------ 某个工具失败了,其他工具的结果仍然需要。

Promise.race:谁先完成用谁

javascript 复制代码
const result = await Promise.race([
    fetch('https://api1.example.com/data'),  // 可能慢
    fetch('https://api2.example.com/data'),  // 可能快
    new Promise((_, reject) => setTimeout(() => reject('超时'), 5000)),
]);

适用场景:多源数据竞速、超时控制。

对比总结

方法 成功条件 失败条件 返回值
Promise.all 全部成功 任一失败 结果数组
Promise.allSettled 不关心 不关心 状态+结果数组
Promise.race 第一个完成 第一个完成 单个结果

📚 五、在 Agent 中应用 Promise.all

串行版本(慢)

javascript 复制代码
// ❌ 串行执行工具调用
for (const toolCall of response.tool_calls) {
    const tool = tools.find(t => t.name === toolCall.name);
    if (tool) {
        const result = await tool.invoke(toolCall.args);  // 每个都要等
        messages.push(new ToolMessage({
            content: result,
            tool_call_id: toolCall.id,
        }));
    }
}

并行版本(快)

javascript 复制代码
// ✅ 并行执行工具调用
const toolResults = await Promise.all(
    response.tool_calls.map(async (toolCall) => {
        const tool = tools.find(t => t.name === toolCall.name);
        if (!tool) return `工具 ${toolCall.name} 不存在`;

        try {
            return await tool.invoke(toolCall.args);
        } catch (error) {
            return `工具调用失败:${error.message}`;
        }
    })
);

// 把结果包装成 ToolMessage(顺序和 tool_calls 一致!)
response.tool_calls.forEach((toolCall, index) => {
    messages.push(new ToolMessage({
        tool_call_id: toolCall.id,
        content: toolResults[index]
    }));
});

性能对比

假设 LLM 一次返回 3 个 tool_calls,每个工具执行需要 2 秒:

方案 执行方式 总耗时
串行 2s + 2s + 2s 6 秒
并行 max(2s, 2s, 2s) 2 秒

提速 3 倍! 如果工具调用更多,差距会更大。


📚 六、进阶:Promise.allSettled 在 Agent 中的应用

在 Agent 场景中,某个工具调用失败不应该影响其他工具。这时候用 Promise.allSettled 更合适:

javascript 复制代码
const toolResults = await Promise.allSettled(
    response.tool_calls.map(async (toolCall) => {
        const tool = tools.find(t => t.name === toolCall.name);
        if (!tool) throw new Error(`工具 ${toolCall.name} 不存在`);
        return await tool.invoke(toolCall.args);
    })
);

// 处理结果(不管成功失败都处理)
response.tool_calls.forEach((toolCall, index) => {
    const result = toolResults[index];

    const content = result.status === 'fulfilled'
        ? result.value
        : `工具调用失败:${result.reason}`;

    messages.push(new ToolMessage({
        tool_call_id: toolCall.id,
        content: content,
    }));
});

优势:即使某个工具失败了,其他工具的结果仍然能正常返回给 LLM。


📚 七、手写一个 Promise.all

理解原理最好的方式是自己实现一遍:

javascript 复制代码
function myPromiseAll(promises) {
    return new Promise((resolve, reject) => {
        const results = [];
        let completedCount = 0;
        const total = promises.length;

        if (total === 0) {
            resolve([]);
            return;
        }

        promises.forEach((promise, index) => {
            // 确保每个元素都是 Promise
            Promise.resolve(promise)
                .then((result) => {
                    results[index] = result;  // 保持顺序
                    completedCount++;

                    if (completedCount === total) {
                        resolve(results);  // 全部完成
                    }
                })
                .catch((err) => {
                    reject(err);  // 一个失败,立即 reject
                });
        });
    });
}

核心逻辑

  1. 创建一个结果数组 results
  2. 遍历每个 Promise,监听它的完成
  3. 完成时把结果放到对应的索引位置(保持顺序)
  4. 全部完成后 resolve
  5. 任何一个 reject,立即 reject

💡 重点总结

  1. 串行 vs 并行:串行是"一个一个等",并行是"同时开始,等最慢的"。

  2. Promise.all:全部成功才成功,一个失败就失败。结果顺序和传入顺序一致。

  3. Promise.allSettled:不管成败,全部返回。适合 Agent 场景(某个工具失败不影响其他)。

  4. Promise.race:谁先完成用谁。适合超时控制、多源竞速。

  5. Agent 性能优化:多个独立的工具调用用 Promise.all 并行执行,可以成倍提升速度。

  6. 错误处理:用 try-catch 包裹每个工具调用,把错误信息返回给 LLM,让它自己调整策略。


🔗 参考资料


💬 交流讨论

这是 LangChain Agent 系列的第四篇(完结篇)。四篇文章从概念到实战,完整覆盖了 Agent 开发的核心知识:

  1. LangChain 入门 ------ Agent 的本质和 LangChain.js 基础
  2. Tool Calling ------ 工具定义、绑定、调用的底层原理
  3. ReAct 循环 ------ 自主规划、分步执行的实现
  4. Promise.all ------ 工具调用的性能优化

接下来计划写 RAG(检索增强生成) ------ 让 Agent 能查询私有知识库,敬请期待 👀


觉得有用?点个赞👍收藏⭐关注👆,后续带你搞定 RAG + Memory,让 Agent 真正"活"起来!


📎 标签Promise性能优化AI AgentJavaScriptLangChain

相关推荐
Tbisnic1 小时前
深入浅出Word2Vec中的分层Softmax:从激活函数到梯度推导
人工智能·ai·损失函数·fasttext·层次softmax
程序员爱钓鱼1 小时前
Rust 控制流 if 详解:条件判断与 if 表达式
前端·后端·rust
郝开1 小时前
Windows 安装 Ostris AI Toolkit
人工智能·windows·toolkit·ai toolkit
张可1 小时前
人类如何利用超长上下文与 AI 协作
人工智能·aigc·ai编程
冷小鱼1 小时前
AI Agent的核心算法:自主学习与反思(Self-Reflection / Critique)
人工智能·学习·算法·自主学习·self-reflection·critique·反思
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端:错误处理与优雅降级策略
android·javascript·flutter·华为·harmonyos
Listen·Rain1 小时前
Vue3中computed详解
前端·javascript·vue.js
kyle~2 小时前
Swagger ---基于 描述文件 生成 交互式API文档 的工具
前端·后端·规格说明书·说明文档