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 store1.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 安装
ell和ell studio都包含在ell-ai中。
poetry add ell-ai -vvpyproject.toml:
[tool.poetry.dependencies]
python = "^3.11"
ell-ai = "^0.0.3"2.2 设置环境变量OPENAI_API_KEY和OPENAI_BASE_URL
将OPENAI_API_KEY和OPENAI_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端口,在浏览器打开可以看到下面的界面:

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

让我们接下来修改一下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:

注意
当前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还不够全面和稳定,一些特殊场景未必能很好满足,未来增加和改动的可能性比较大。
还不建议将其用到生产环境,建议个人本地学习或在一些小项目上尝尝鲜。