Supercharging If-Statements With Prompt Classification Using Ollama and LangChain

使用 Ollama 和 LangChain 通过 Prompt 分类增强 If 语句

6 分钟阅读

编者按:Andrew Nguonly 一直在构建我们最近见过的最令人印象深刻的项目之一 - 一个用于浏览网络的 LLM 协驾驶员,由本地 LLM 提供支持。其中包含许多细微的架构决策,因此我们非常激动地发布他的这篇博客,详细介绍其中一项决策。

Lumos 重新亮相!🪄

我之前已经广泛地介绍了 Lumos,所以我将简明扼要地重新介绍一下。 Lumos 是一个用于浏览网络的 LLM 协驾驶员,由本地 LLM 提供支持。它是一个 Chrome 扩展程序,可以 抓取当前页面上的内容 并在 在线、内存 RAG 工作流程中处理解析结果,所有这些都在单个请求上下文中完成。Lumos 基于 LangChain 构建,并由 Ollama 提供支持。它是开源且免费使用的。

Lumos 非常适合我们知道 LLM 擅长的任务

  • 总结新闻文章、帖子和聊天记录
  • 询问有关餐厅和产品评论的问题
  • 从密集的 technical 文档中提取详细信息

我甚至发现 Lumos 对我的西班牙语学习很有帮助。该应用程序的人机工程学设计非常方便。随着我越来越多地使用该应用程序,我正在发现浏览器中的 LLM 可以派上用场的新方法。

重建计算器 🧮

对于基于文本的任务,LLM 具有创造性和智能。但是,它们并非设计为确定性的。 Andrej Karpathy 曾经将 LLM 描述为“梦想机器”。像计算 456*4343 这样简单的操作,LLM 也无法保证正确。对于包含多个操作数和运算符的复杂方程式,即使是顶级模型也无能为力。

456*4343 — 56/(443-11+4) 是多少?

GPT-3.5 错误地“计算”了 456*4343
Llama2 错误地“计算”了 456*4343

LLM 需要额外的工具来完成某些工作,例如执行代码或解决数学问题。Lumos 也不例外。我不记得我为什么需要在浏览器中使用计算器(计算税款?),但我知道我不想拿出手机或打开新标签页。我只是希望我的 LLM 能够正确回答数学问题。

因此,我决定在 Lumos 中构建一个计算器。

使用 Ollama 进行 Prompt 分类 🦙

我之前尝试过使用 Ollama 进行 prompt 分类,并认为这项技术非常有价值。“分类 prompt”的输出如果可靠,可以增强 if 语句和分支逻辑。

虽然 Lumos 不是作为 LangChain Agent 实现的,但我希望使用它的体验就像与 Agent 交互一样。它应该独立于直接指令执行工具。应用程序应该知道何时使用计算器。使用 Ollama 确定 prompt 是否需要计算器工具很容易实现。

const isArithmeticExpression = async (
  baseURL: string,
  model: string,
  prompt: string,
): Promise<boolean> => {
  // check for prefix trigger
  if (prompt.trim().toLowerCase().startsWith("calculate:")) {
    return new Promise((resolve) => resolve(true));
  }

  // otherwise, attempt to classify prompt
  const ollama = new Ollama({ baseUrl: baseURL, model: model, temperature: 0, stop: [".", ","]});
  const question = `Is the following prompt a math equation with numbers and operators? Answer with 'yes' or 'no'.\n\nPrompt: ${prompt}`;
  return ollama.invoke(question).then((response) => {
    console.log(`isArithmeticExpression classification response: ${response}`);
    const answer = response.trim().split(" ")[0].toLowerCase();
    return answer.includes("yes");
  });
};

只需询问 LLM prompt 是否是包含数字和运算符的数学方程式,并检查响应是否包含“yes”或“no”。非常简单。即使没有 JSON 模式或函数调用,实现也是可靠的。为二元响应 prompt LLM 与要求它针对任意数量的可能不相关的类别进行分类的方法形成对比。当尝试将 prompt 与工具匹配时,会出现这种情况。

Llama2 和 Mistral 在我的测试中都表现良好。将模型温度设置为 0 并配置停止序列 ([".", ","]) 进一步提高了可靠性和响应延迟。相对于用户对常规 prompt(例如几秒)体验到的平均推理时间,分类的 elapsed 时间只是增量的。诚然,这种增加的延迟可能不足以满足某些用例。

Lumos 控制台日志

同样重要的是要重申,利用本地 LLM 使此操作实际上是免费的。Ollama 的实用性在这种用例中真正闪耀。为了给用户最大的控制权,该机制还包括触发器的功能,用户可以在 prompt 中包含前缀以保证将执行工具。这类似于 ChatGPT 的 @ 引用来调用特定的 GPT。

456 x 4343 = 1980408 🔢

Lumos 正确计算了 456*4343

Lumos 计算器 的实现非常简单。它被构建为 LangChain Tool,以保留应用程序未来移植到强大的 Agent 中的可移植性。对于自定义工具,LangChain 建议构建一个 DynamicTool  DynamicStructuredTool,但也很容易对基础 Tool 类进行子类化。

import { Tool } from "@langchain/core/tools";

export class Calculator extends Tool {
  name = "calculator";
  description = "A tool for evaluating arithmetic expressions";

  constructor() {
    super();
  }

  protected _call = (expression: string): Promise<string> => {
    const tokens = this._extractTokens(expression);
    let answer;
    try {
      answer = this._evaluateExpression(tokens);
    } catch (error) {
      if (error instanceof Error) {
        answer = `Error: ${error.message}`;
      } else {
        answer = "Error: Unable to execute calculator tool.";
      }
    }
    return Promise.resolve(answer.toString());
  };

  _extractTokens = (expression: string): string[] => { ... };

  _evaluateExpression = (tokens: string[]): number => { ... };
}

当 Lumos 收到类似于数学方程式的 prompt 时,无论是简单还是复杂,它都会知道调用计算器。

为复杂条件语句扩展分类 🌲

分类技术再次复制用于 Lumos 的多模态功能。如果用户询问图像,应用程序应该知道从页面下载图像。否则,出于明显的效率原因,它应该跳过下载。我决定概括这种方法并创建一个可配置的函数。

const classifyPrompt = async (
  baseURL: string,
  model: string,
  type: string,
  originalPrompt: string,
  classifcationPrompt: string,
  prefixTrigger?: string,
): Promise<boolean> => {
  // check for prefix trigger
  if (prefixTrigger) {
    if (originalPrompt.trim().toLowerCase().startsWith(prefixTrigger)) {
      return new Promise((resolve) => resolve(true));
    }
  }

  // otherwise, attempt to classify prompt
  const ollama = new Ollama({
    baseUrl: baseURL,
    model: model,
    temperature: 0,
    stop: [".", ","],
  });
  const finalPrompt = `${classifcationPrompt} Answer with 'yes' or 'no'.\n\nPrompt: ${originalPrompt}`;
  return ollama.invoke(finalPrompt).then((response) => {
    console.log(`${type} classification response: ${response}`);
    const answer = response.trim().split(" ")[0].toLowerCase();
    return answer.includes("yes");
  });
};

现在,classifyPrompt() 接受“分类 prompt”和触发器参数。该函数可以在整个应用程序代码中重用。

下载图像的决定也取决于多模态模型(例如 LLaVA)的可用性。用户必须先下载一个模型,然后才能发出描述图像的 prompt。在 Lumos 中,用户配置也会作为下载决策的一部分进行检查。

import { getLumosOptions, isMultimodal } from "../pages/Options";

const CLS_IMG_TYPE = "isImagePrompt";
const CLS_IMG_PROMPT = "Is the following prompt referring to an image or asking to describe an image?";
const CLS_IMG_TRIGGER = "based on the image";

const options = await getLumosOptions();

if (
  isMultimodal(options.ollamaModel) &&
  (await classifyPrompt(
    options.ollamaHost,
    options.ollamaModel,
    CLS_IMG_TYPE,
    prompt,
    CLS_IMG_PROMPT,
    CLS_IMG_TRIGGER,
  ))
) {
  // download images
  ...
}

将分类结果包含在 if 语句表达式中感觉自然、简单且有效。使用这种方法,开发人员可以完全控制应用程序的执行。在某种程度上,依赖于 LLM 的代码分支可能会变得可测试!

为了让 Lumos 确定图像是否已下载,分类结果与用户的配置状态相结合。此外,基于结合复杂的应用程序状态(例如用户配置、访问控制、缓存状态等)和分类结果的一致决策,对于 LLM 来说,大规模完成可能更具挑战性。

同时对多个 LLM 功能进行 A/B 测试可能会从此方法中受益。对于敏感的用例,例如权限工具执行或 RAG 的特权数据访问,此设计可能很有吸引力。任何决定都不能碰运气。

Lumos 的下一步是什么?🔮

在短期内,我将继续探索将 更多工具集成 到 Lumos 中。我将评估迁移到 Agent 架构的好处,并逐步解决运行本地 LLM 应用程序的一些已知效率和速度挑战。

然而,从长远来看,存在更大的机遇值得考虑。Chrome 扩展程序功能强大,但其能力仍然非常有限。当我在思考浏览器中 LLM 的新用例时,值得研究是否完全需要一个新的浏览器。目前,这些只是一些想法。与此同时,我将尽情享受在这个早期且激动人心的时刻构建 LLM 应用程序的过山车。LangChain 和 Ollama 让这段旅程更加顺畅。😎

参考文献

  1. Lumos (GitHub)
  2. 由 Ollama 提供支持的浏览器本地 LLM(第一部分)
  3. 由 Ollama 提供支持的浏览器本地 LLM(第二部分)
  4. 让我们规范化在线、内存 RAG! (第三部分)