青蛙小白
博客 / 2024/09

初探轻量级LLM应用开发框架ell

2024/09/19 · — 字 · 阅读约 — 分钟 ·
目录

ell是一个全新的开发大语言模型应用框架。

ell的官方文档里将自己称为"大语言模型编程库", “ell是一个轻量级的提示工程库,将提示(prompt)视为函数”。

现在面向LLM的编程框架层出不穷,LangChain几乎为我们封装了所有,但是有些过于重了。ell的特点是"轻量化"。

使用LangChain或者直接面向OpenAI API的原生调用代码,除了Prompt之外还要写很多调用代码,而且对于Prompt缺乏版本控制管理,这成为了一个痛点 。ell轻量化的特点是它将是对LLM的操作简化到了只需写提示词。

ell的特点:

  • 将对LLM的调用简化到只需写提示词(Prompt),并且可以方便的对提示词进行版本跟踪
  • 有可视化的工具(ell studio),可以可视化监控跟踪每一次请求
  • 原生支持多模态

1.ell的设计原则

1.1 提示词不仅仅是字符串, 而是程序LMP

提示词不仅仅是字符串, 而是程序LMP(Prompts are programs, not strings)。

import ell

@ell.simple(model="gpt-4o-mini")
def hello(world: str):
    """You are a helpful assistant""" # System prompt
    name = world.capitalize()
    return f"Say hello to {name}!" # User prompt

hello("sam altman") # just a str, "Hello Sam Altman! ..."

在ell中提示词不仅仅是字符串;它们是所有将字符串发送到LLM的代码。

在ell中,我们将使用LLM的一种特定方式称为LMP(language model program)。

LMP是完全封装的函数,用于生成要发送到各种多模态语言模型的字符串提示或消息列表。这种封装为用户创建了一个干净的界面,用户只需指定给LMP的必需数据。

1.2 提示工程是一个优化过程

提示工程是一个优化过程(Prompt engineering is an optimization process)。

提示工程的过程涉及许多迭代,类似于机器学习中的优化过程。因为LMP只是函数,ell为此过程提供了丰富的工具。

ell通过静态和动态分析自动对提示进行版本控制和序列化,并直接将gpt-4o-mini自动生成的commit信息发送到本地存储。这个过程类似于机器学习训练循环中的检查点,但它不需要任何特殊的IDE或编辑器,这一切都是用常规的Python代码完成的。

import ell

ell.init(store='./logdir')  # Versions your LMPs and their calls

# ... define your lmps

hello("strawberry") # the source code of the LMP the call is saved to the store

1.3 监控、版本控制和可视化工具ell studio

ell提供了监控、版本控制和可视化工具(Tools for monitoring, versioning, and visualization)。

对LLM的每一次调用都很重要, 所以要有日志的跟踪监控

ell-studio --storage-dir ./logdir

有了合适的工具,提示工程从一门神秘的艺术转变为一门科学。Ell Studio是一个本地、开源的工具,用于Prompt的版本控制、监控、可视化。使用Ell Studio,您可以在实践中不断优化提示效果。

1.4 测试时的计算很重要

测试时的计算很重要(Test-time compute is important)

在将一个语言模型的演示转化为实际应用时,我们经常需要多次调用这个模型。ell这个工具通过把问题分解成更小的、更易于管理的部分,使得我们在模型使用过程中,能够灵活地调整和优化,从而提高模型的性能。

想象一下,你想要让LLM做很多事情。一开始,你可能会给它一些简单的指令,看看它能不能完成。但是,当你想让它做更复杂的事情时,你可能需要给它很多条指令,而且这些指令之间还有一些联系。ell就相当于一个工具箱,它能帮你把这些复杂的指令拆分成一个个小指令,然后按顺序执行。这样一来,你就可以更轻松地让LLM完成复杂的任务,而且还能够随时调整这些指令,让LLM做得更好。

import ell
@ell.simple(model="gpt-4o-mini", temperature=1.0, n=10)
def write_ten_drafts(idea : str):
  """You are an adept story writer. The story should only be 3 paragraphs"""
  return f"Write a story about {idea}."

@ell.simple(model="gpt-4o", temperature=0.1)
def choose_the_best_draft(drafts : List[str]):
  """You are an expert fiction editor."""
  return f"Choose the best draft from the following list: {'\n'.join(drafts)}."

drafts = write_ten_drafts(idea)

best_draft = choose_the_best_draft(drafts) # Best of 10 sampling.

除了存储每个LMP的源代码之外,ell还可选地将对LLM的每次调用保存到本地。这允许你生成调用数据集、按版本比较LMP输出,并充分利用提示工程的所有相关产物。

1.5 根据需要选择复杂性或简单性

使用LLM通常只需要传递字符串,但有时并非如此:

import ell

@ell.tool()
def scrape_website(url : str):
   return requests.get(url).text

@ell.complex(model="gpt-5-omni", tools=[scrape_website])
def get_news_story(topic : str):
   return [
      ell.system("""Use the web to find a news story about the topic"""),
      ell.user(f"Find a news story about {topic}.")
   ]

message_response = get_news_story("stock market")
if message_response.tool_calls:
   for tool_call in message_response.tool_calls:
      #...
if message_response.text:
   print(message_response.text)
if message_response.audio:
   # message_response.play_audio() supprot for multimodal outputs will work as soon as the LLM supports it
   pass

使用@ell.simple会让LMP生成简单的字符串输出。但当需要更复杂或多模态的输出时,可以使用@ell.complex,让语言模型返回Message对象的响应。

1.6 多模态是一等公民

LLM可以处理和生成各种类型的文本、图像、音频和视频等内容。针对各种类型的输出进行提示工程就像提示生成文本一样简单。

from PIL import Image
import ell


@ell.simple(model="gpt-4o", temperature=0.1)
def describe_activity(image: Image.Image):
   return [
      ell.system("You are VisionGPT. Answer <5 words all lower case."),
      ell.user(["Describe what the person in the image is doing:", image])
   ]

# Capture an image from the webcam
describe_activity(capture_webcam_image()) # "they are holding a book"

ell支持丰富的多模态输入和输出类型转换。可以将PIL图像、音频和其他多模态输入内嵌在LMP返回的Message对象中。

1.7 提示工程库不应干扰您的工作流程

ell被设计为一个轻量级且不显眼的库。它不需要我们更的编码风格或使用特殊的编辑器。

我们可以继续在的IDE中使用常规Python代码来定义和修改的提示,同时利用ell的功能来可视化和分析提示。可以一次一个函数地从langchain迁移到ell。

2.开始使用ell

2.1 安装

ellell studio都包含在ell-ai中。

poetry add ell-ai -vv

pyproject.toml:

[tool.poetry.dependencies]
python = "^3.11"
ell-ai = "^0.0.3"

2.2 设置环境变量OPENAI_API_KEYOPENAI_BASE_URL

OPENAI_API_KEYOPENAI_BASE_URL放到.env文件中:

OPENAI_API_KEY=<your key>
OPENAI_BASE_URL=<your base url>

2.3 创建第一个大语言模型程序(LMP)

在创建一个ell的LMP之前,先看一下使用OpenAI chat completions API的代码:

from dotenv import load_dotenv
import os
from openai import OpenAI

assert load_dotenv()
assert os.environ.get("OPENAI_BASE_URL")
assert os.environ.get("OPENAI_API_KEY")

messages = [
    {
        "role": "system",
        "content": """你是用户的生活助理-小智。
识别用户的意图并用简洁的语言给出合适的回应。
        """,
    },
    {"role": "user", "content": "我想明天早上5点起床"},
]

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o-mini", messages=messages, temperature=0.9, max_tokens=1000
)

print(response.choices[0].message.content)

运行上面的程序可能的输出如下:

好的,明天早上5点起床。建议你今晚早点睡,确保充足的休息!需要我设置闹钟提醒吗?

现在,让我们看看如何使用 ell 来实现相同的结果:

from dotenv import load_dotenv
import os
from openai import OpenAI
import ell

assert load_dotenv()
assert os.environ.get("OPENAI_BASE_URL")
assert os.environ.get("OPENAI_API_KEY")

client = OpenAI()


@ell.simple(model="gpt-4o-mini", client=client, temperature=0.9, max_tokens=1000)
def reconize_intent(user_input: str):
    """你是用户的生活助理-小智。
    识别用户的意图并用简洁的语言给出合适的回应。
    """  # System prompt
    return user_input  # User prompt


content = reconize_intent("我想明天早上5点起床")
print(content)

运行上面的程序可能的输出如下:

好的,建议你今晚早点睡,设定一个闹钟在5点。确保把手机放在能听到的地方哦!

ell通过将提示定义为函数单元来简化提示。在此示例中,reconize_intent函数通过文档字符串定义系统提示,并通过返回字符串定义用户提示。

最终的的提示可以简单地使用的参数调用该函数,而不是手动构建消息。这种使提示更具可读性、可维护性和可重用性。

@ell.simple装饰器是ell中的一个关键概念。它将一个常规的Python函数转换为一个大语言模型程序(LMP):

  • 函数的文档字符串成为系统消息
  • 函数的返回值成为用户消息
  • 装饰器处理API调用并以字符串形式返回模型的响应

这种封装允许更简洁、更可重用的代码。可以像调用任何其他Python 函数一样调用您LMP。

可以启用ell的verbose模式,这样可以了解后台的细节。启用verbose模式只需要在开始调用ell.init(verbose=True)

ell.init(verbose=True)

启用verbose后,可以看到有关的LLM调用的输入和输出的详细信息。

╔══════════════════════════════════════════════════════════════════════════════════════╗
║ reconize_intent(我想明天早上5点..)
╠══════════════════════════════════════════════════════════════════════════════════════╣
║ Prompt:
╟──────────────────────────────────────────────────────────────────────────────────────╢
│      system: 你是用户的生活助理-小智。
│                  识别用户的意图并用简洁的语言给出合适的回应。
│        user: 我想明天早上5点起床
╟──────────────────────────────────────────────────────────────────────────────────────╢
║ Output:
╟──────────────────────────────────────────────────────────────────────────────────────╢
│   assistant: 好的,建议你今晚早点睡,这样明天早上能更好地起床。要我帮你设定闹钟吗?
╚══════════════════════════════════════════════════════════════════════════════════════╝
好的,建议你今晚早点睡,这样明天早上能更好地起床。要我帮你设定闹钟吗?

2.4 Prompt的版本控制

ell为LMP提供强大的版本控制功能。要启用此功能,只需在代码开始的地方添加以下代码:

ell.init(store='./logdir', autocommit=True, verbose=True)

这行代码时将在 ./logdir 目录中设置一个存储库并启用自动提交。ell将在./logdir/ell.db中存储所有的提示及其版本。

启用版本控制生成./logdir/ell.db后,可以使用ell-studio来查看提示。在终端中,运行下面的命令:

ell-studio --storage-dir ./logdir

服务默认监听本机8080端口,在浏览器打开可以看到下面的界面:

091305
091305

点击inocations还可以看到调用历史:

091306
091306

让我们接下来修改一下reconize_intent这个LMP:

from dotenv import load_dotenv
import os
from openai import OpenAI
import ell

assert load_dotenv()
assert os.environ.get("OPENAI_BASE_URL")
assert os.environ.get("OPENAI_API_KEY")

client = OpenAI()


@ell.simple(model="gpt-4o-mini", client=client, temperature=0.9, max_tokens=1000)
def reconize_intent(user_input: str):
    """你是用户的生活助理-小智。
    识别用户的意图并用简洁的语言给出合适的回应。
    回应的格式是一个json数组,每个对象json包含字段intent和parameters
    如果无法识别用户的意图, json对象中intent字段的值固定为unknown, 并将user_input字段设置为用户的输入
    """  # System prompt
    return user_input  # User prompt


ell.init(store="./logdir", autocommit=True, verbose=True)
content = reconize_intent("我想明天早上5点起床")
print(content)

运行上面的程序可能的输出如下:

[
    {
        "intent": "set_alarm",
        "parameters": {
            "time": "5:00 AM"
        }
    }
]

此时再次查看ell studio,可以看到reconize_intent这个LMP更新到了Version 2:

091307
091307

注意

当前ell在做版本控制时,写入的commit messages使用了其内部LMP的write_commit_message_for_diff,这个LMP默认使用的gpt-4o-mini,即commit message也利用了大模型的能力。 前面使用load_dotenv()文件加载.env中的环境变量,发现目前无法对write_commit_message_for_diff生效,会报"ERROR: No API key found for model gpt-4o-mini used by LMP write_commit_message_for_diff using client"的错误。 目前的解决方法是在运行程序前使用export OPENAI_BASE_URL=<base url>export OPENAI_API_KEY=<api key>来手动将环境变量设置上

3.总结

ell的目前还是一个新的框架, API还不够全面和稳定,一些特殊场景未必能很好满足,未来增加和改动的可能性比较大。

还不建议将其用到生产环境,建议个人本地学习或在一些小项目上尝尝鲜。

参考链接

TAGS # aigc
评论