初探轻量级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)。
1import ell
2
3@ell.simple(model="gpt-4o-mini")
4def hello(world: str):
5 """You are a helpful assistant""" # System prompt
6 name = world.capitalize()
7 return f"Say hello to {name}!" # User prompt
8
9hello("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代码完成的。
1import ell
2
3ell.init(store='./logdir') # Versions your LMPs and their calls
4
5# ... define your lmps
6
7hello("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的每一次调用都很重要, 所以要有日志的跟踪监控
1ell-studio --storage-dir ./logdir
有了合适的工具,提示工程从一门神秘的艺术转变为一门科学。Ell Studio是一个本地、开源的工具,用于Prompt的版本控制、监控、可视化。使用Ell Studio,您可以在实践中不断优化提示效果。
1.4 测试时的计算很重要 #
测试时的计算很重要(Test-time compute is important)
在将一个语言模型的演示转化为实际应用时,我们经常需要多次调用这个模型。ell这个工具通过把问题分解成更小的、更易于管理的部分,使得我们在模型使用过程中,能够灵活地调整和优化,从而提高模型的性能。
想象一下,你想要让LLM做很多事情。一开始,你可能会给它一些简单的指令,看看它能不能完成。但是,当你想让它做更复杂的事情时,你可能需要给它很多条指令,而且这些指令之间还有一些联系。ell就相当于一个工具箱,它能帮你把这些复杂的指令拆分成一个个小指令,然后按顺序执行。这样一来,你就可以更轻松地让LLM完成复杂的任务,而且还能够随时调整这些指令,让LLM做得更好。
1import ell
2@ell.simple(model="gpt-4o-mini", temperature=1.0, n=10)
3def write_ten_drafts(idea : str):
4 """You are an adept story writer. The story should only be 3 paragraphs"""
5 return f"Write a story about {idea}."
6
7@ell.simple(model="gpt-4o", temperature=0.1)
8def choose_the_best_draft(drafts : List[str]):
9 """You are an expert fiction editor."""
10 return f"Choose the best draft from the following list: {'\n'.join(drafts)}."
11
12drafts = write_ten_drafts(idea)
13
14best_draft = choose_the_best_draft(drafts) # Best of 10 sampling.
除了存储每个LMP的源代码之外,ell还可选地将对LLM的每次调用保存到本地。这允许你生成调用数据集、按版本比较LMP输出,并充分利用提示工程的所有相关产物。
1.5 根据需要选择复杂性或简单性 #
使用LLM通常只需要传递字符串,但有时并非如此:
1import ell
2
3@ell.tool()
4def scrape_website(url : str):
5 return requests.get(url).text
6
7@ell.complex(model="gpt-5-omni", tools=[scrape_website])
8def get_news_story(topic : str):
9 return [
10 ell.system("""Use the web to find a news story about the topic"""),
11 ell.user(f"Find a news story about {topic}.")
12 ]
13
14message_response = get_news_story("stock market")
15if message_response.tool_calls:
16 for tool_call in message_response.tool_calls:
17 #...
18if message_response.text:
19 print(message_response.text)
20if message_response.audio:
21 # message_response.play_audio() supprot for multimodal outputs will work as soon as the LLM supports it
22 pass
使用@ell.simple
会让LMP生成简单的字符串输出。但当需要更复杂或多模态的输出时,可以使用@ell.complex
,让语言模型返回Message对象的响应。
1.6 多模态是一等公民 #
LLM可以处理和生成各种类型的文本、图像、音频和视频等内容。针对各种类型的输出进行提示工程就像提示生成文本一样简单。
1from PIL import Image
2import ell
3
4
5@ell.simple(model="gpt-4o", temperature=0.1)
6def describe_activity(image: Image.Image):
7 return [
8 ell.system("You are VisionGPT. Answer <5 words all lower case."),
9 ell.user(["Describe what the person in the image is doing:", image])
10 ]
11
12# Capture an image from the webcam
13describe_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 安装 #
ell
和ell studio
都包含在ell-ai
中。
1poetry add ell-ai -vv
pyproject.toml
:
1[tool.poetry.dependencies]
2python = "^3.11"
3ell-ai = "^0.0.3"
2.2 设置环境变量OPENAI_API_KEY
和OPENAI_BASE_URL
#
将OPENAI_API_KEY
和OPENAI_BASE_URL
放到.env
文件中:
1OPENAI_API_KEY=<your key>
2OPENAI_BASE_URL=<your base url>
2.3 创建第一个大语言模型程序(LMP) #
在创建一个ell的LMP之前,先看一下使用OpenAI chat completions API的代码:
1from dotenv import load_dotenv
2import os
3from openai import OpenAI
4
5assert load_dotenv()
6assert os.environ.get("OPENAI_BASE_URL")
7assert os.environ.get("OPENAI_API_KEY")
8
9messages = [
10 {
11 "role": "system",
12 "content": """你是用户的生活助理-小智。
13识别用户的意图并用简洁的语言给出合适的回应。
14 """,
15 },
16 {"role": "user", "content": "我想明天早上5点起床"},
17]
18
19client = OpenAI()
20
21response = client.chat.completions.create(
22 model="gpt-4o-mini", messages=messages, temperature=0.9, max_tokens=1000
23)
24
25print(response.choices[0].message.content)
运行上面的程序可能的输出如下:
1好的,明天早上5点起床。建议你今晚早点睡,确保充足的休息!需要我设置闹钟提醒吗?
现在,让我们看看如何使用 ell 来实现相同的结果:
1from dotenv import load_dotenv
2import os
3from openai import OpenAI
4import ell
5
6assert load_dotenv()
7assert os.environ.get("OPENAI_BASE_URL")
8assert os.environ.get("OPENAI_API_KEY")
9
10client = OpenAI()
11
12
13@ell.simple(model="gpt-4o-mini", client=client, temperature=0.9, max_tokens=1000)
14def reconize_intent(user_input: str):
15 """你是用户的生活助理-小智。
16 识别用户的意图并用简洁的语言给出合适的回应。
17 """ # System prompt
18 return user_input # User prompt
19
20
21content = reconize_intent("我想明天早上5点起床")
22print(content)
运行上面的程序可能的输出如下:
1好的,建议你今晚早点睡,设定一个闹钟在5点。确保把手机放在能听到的地方哦!
ell通过将提示定义为函数单元来简化提示。在此示例中,reconize_intent
函数通过文档字符串定义系统提示,并通过返回字符串定义用户提示。
最终的的提示可以简单地使用的参数调用该函数,而不是手动构建消息。这种使提示更具可读性、可维护性和可重用性。
@ell.simple
装饰器是ell中的一个关键概念。它将一个常规的Python函数转换为一个大语言模型程序(LMP):
- 函数的文档字符串成为系统消息
- 函数的返回值成为用户消息
- 装饰器处理API调用并以字符串形式返回模型的响应
这种封装允许更简洁、更可重用的代码。可以像调用任何其他Python 函数一样调用您LMP。
可以启用ell的verbose模式,这样可以了解后台的细节。启用verbose模式只需要在开始调用ell.init(verbose=True)
1ell.init(verbose=True)
启用verbose后,可以看到有关的LLM调用的输入和输出的详细信息。
1╔══════════════════════════════════════════════════════════════════════════════════════╗
2║ reconize_intent(我想明天早上5点..)
3╠══════════════════════════════════════════════════════════════════════════════════════╣
4║ Prompt:
5╟──────────────────────────────────────────────────────────────────────────────────────╢
6│ system: 你是用户的生活助理-小智。
7│ 识别用户的意图并用简洁的语言给出合适的回应。
8│
9│ user: 我想明天早上5点起床
10╟──────────────────────────────────────────────────────────────────────────────────────╢
11║ Output:
12╟──────────────────────────────────────────────────────────────────────────────────────╢
13│ assistant: 好的,建议你今晚早点睡,这样明天早上能更好地起床。要我帮你设定闹钟吗?
14╚══════════════════════════════════════════════════════════════════════════════════════╝
15好的,建议你今晚早点睡,这样明天早上能更好地起床。要我帮你设定闹钟吗?
2.4 Prompt的版本控制 #
ell为LMP提供强大的版本控制功能。要启用此功能,只需在代码开始的地方添加以下代码:
1ell.init(store='./logdir', autocommit=True, verbose=True)
这行代码时将在 ./logdir
目录中设置一个存储库并启用自动提交。ell将在./logdir/ell.db
中存储所有的提示及其版本。
启用版本控制生成./logdir/ell.db
后,可以使用ell-studio
来查看提示。在终端中,运行下面的命令:
1ell-studio --storage-dir ./logdir
服务默认监听本机8080端口,在浏览器打开可以看到下面的界面:
点击inocations还可以看到调用历史:
让我们接下来修改一下reconize_intent
这个LMP:
1from dotenv import load_dotenv
2import os
3from openai import OpenAI
4import ell
5
6assert load_dotenv()
7assert os.environ.get("OPENAI_BASE_URL")
8assert os.environ.get("OPENAI_API_KEY")
9
10client = OpenAI()
11
12
13@ell.simple(model="gpt-4o-mini", client=client, temperature=0.9, max_tokens=1000)
14def reconize_intent(user_input: str):
15 """你是用户的生活助理-小智。
16 识别用户的意图并用简洁的语言给出合适的回应。
17 回应的格式是一个json数组,每个对象json包含字段intent和parameters
18 如果无法识别用户的意图, json对象中intent字段的值固定为unknown, 并将user_input字段设置为用户的输入
19 """ # System prompt
20 return user_input # User prompt
21
22
23ell.init(store="./logdir", autocommit=True, verbose=True)
24content = reconize_intent("我想明天早上5点起床")
25print(content)
运行上面的程序可能的输出如下:
1[
2 {
3 "intent": "set_alarm",
4 "parameters": {
5 "time": "5:00 AM"
6 }
7 }
8]
此时再次查看ell studio,可以看到reconize_intent
这个LMP更新到了Version 2:
注意
当前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 modelgpt-4o-mini
used by LMPwrite_commit_message_for_diff
using client"的错误。 目前的解决方法是在运行程序前使用export OPENAI_BASE_URL=<base url>
和export OPENAI_API_KEY=<api key>
来手动将环境变量设置上
3.总结 #
ell的目前还是一个新的框架, API还不够全面和稳定,一些特殊场景未必能很好满足,未来增加和改动的可能性比较大。
还不建议将其用到生产环境,建议个人本地学习或在一些小项目上尝尝鲜。