• LangChain新的竞争对手来了?
  • 发布于 4天前
  • 32 热度
    0 评论
  • Pigeon
  • 1 粉丝 36 篇博客
  •   
最近一个叫 Ell 的新语言模型编程库发布了。它希望能与 LangChain、Llama-Index 和 DSPy 等库竞争,如果可能,它想取代这些竞争对手。下面是 Ell 发布的推文。从一开始就很有信心,核心维护者称其为提示工程的未来。

Ell 的核心维护者说他是出于对使用 LangChain 的不满而构建的这个库。提供的分屏代码比较显示了 LangChain 的抽象是多么臃肿和冗长,而 Ell 在完成相同任务时显得更加简洁。但这是否意味着 Ell 具备正确的抽象?它值得投资吗?我想找出答案,所以决定查看 Ell 的文档,理解其核心抽象,进行实验,并形成自己的看法。我就不说怎么安装 Ell 了,直接开始。

首先,让我们看看 Ell 的创建者是如何看待 LangChain 的局限性以及他创建 Ell 的原因:

我们在概念上是否认同这个说法呢?为什么我们应该将提示视为程序,而不仅仅是字符串?自 ChatGPT 出现以来已经两年了,但在我看来,我通过 API 发送的内容 —— 甚至是在应用程序中获得回应的内容 —— 仍然只是一些字符串,问题、指令、上下文 —— 这都是文本。那么为什么我现在要认为我发送的是程序呢?

我认为文本始终是一组指令,而 LLM 则是执行这些指令的语言计算机,这听起来吸引人,但也有些不对。我主要将 LLM 视为知识压缩的结果,更像是数据库而非正常的计算机,更多地像是语言接口而非语言编译器。不过,这只是我的看法,并不是唯一的概念化方式。有人认为 —— 我倾向于同意 ——LLM 不仅仅是记住它们所训练的文本,还是捕捉到半通用的统计模式,“程序”,帮助它们进行如计算(不完善)或在某种程度上模拟逻辑推理。从这个意义上讲,我可以理解为什么有人会将 LLM 视为语言程序的编译器,从而将提示视为程序。

“提示是机器学习模型的参数。” 什么??我甚至不确定为什么有人会这样说。让我们假装没有看到这一点,开始探索 Ell。

#01
探索
基础知识
我从 Ell 的文档中开始一个基本的提示实现。在 Ell 里,提示本质上是一个函数,然后使用装饰器来指定用于处理其输出的语言模型。在这个例子中,我首先定义了要使用的基础模型客户端(OpenAI),然后将其与要使用的模型一起传递给 @ell.simpledecorator。就是这么简单。
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
 # 堆代码 duidaima.com
@ell.simple(model="gpt-4o-mini", client=client)
def hello(world: str):
    """You are a helpful assistant""" # System prompt
    name = world.capitalize()
    return f"Say hello to {name}!" # User prompt


print(hello("sam altman"))
这是我得到的响应:

在运行第一个程序后,我开始质疑使用函数来定义提示的合理性。当然,在函数内部,你可以使用文档字符串来定义系统提示,而该函数返回用户提示。但除此之外,我并不清楚将提示定义为函数而非简单字符串有什么好处。对我来说,一个真正有用的函数应当包括提示、对 LLM 的调用、LLM 的响应,以及基于该响应的一些后处理。但我们暂时先不下判断。

监控
Ell 的一个有趣方面是它允许你监控和版本控制提示,以及可视化延迟和 Token 使用等指标。而且这一切都是本地的,因此你的数据不会存储在第三方数据库中,不像 LangSmith 等解决方案。你只需用类似于:ell.init(store=’./logdir’) 的命令初始化本地数据库。这是一个不错的功能,但也许只是我和我开发 AI 应用方式的问题 —— 我从未觉得需要跟踪提示的变化以改善它们。我常常觉得直觉就足够了。

例如,知道与某个提示相关的延迟如何帮助精炼它?如果延迟很高,通常只是意味着输入正在促使更长的响应(就像我让模型写文章时)。解决方案是指定诸如 “写一篇简短的文章” 之类的内容,而不是详细的内容,但我仅通过查看输出就能解决这个问题,而无需复杂的监控。同样的逻辑适用于 Token 使用。图表很好看,但我还没看到它们比直觉更有用的地方。

我敢打赌这些图表对于咨询来说很棒,对不熟悉生成式 AI 的客户来说也很有吸引力。根据我的经验,客户往往喜欢可交付的成果 ——PPT、报告、图表或详细的 Notion 演示文稿 —— 因为这让他们感觉做了一些有用的事情,即使实际上并没有。这类似于大多数人对沉默的感觉不适。如果会议中有长时间的沉默,他们会对体验做出负面评价,而如果有不断的聊天 —— 即使是肤浅或误导性的 —— 他们的评价会更高。人们喜欢噪音,我觉得所有这些对提示变化的监控不过是装扮成科学方法的噪音。

但我也很想听听不同的观点(欢迎在评论中分享你的想法)。

我确实觉得将提示 - 响应对存储在数据库中有些用处,因为这可以帮助创建微调数据集。然而,重要的是要考虑那些防止基于输出训练模型的规则。我觉得这很虚伪,尤其是来自那些在 “合理使用” 下抓取网络内容的公司,现在却声称使用他们的输出是不公平的。

我很好奇 Ell Studio 是如何构建的。前端使用 React,这一点很明显。

至于后端,刚开始我还不确定。我刚开始猜测它可能是 FastAPI,但我得检查一下才能确定。果然,后端是 FastAPI。这做得相当不错,我很喜欢这种方式。

工具使用
Ell 还支持工具使用 —— 只需使用 @ell。complex 装饰器。我尝试使用它进行 SERPAPI 搜索,但失败了。即使使用文档中的确切示例,我也无法让它工作。我明白了;这个库仍处于早期阶段,所以这可能只是一个简单的错误,稍后会修复。
这是我尝试的代码:
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os
import requests

# Load environment variables
load_dotenv()

# Initialize ell logging store
ell.init(store='./logdir')

# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Define a tool to search on the internet using SERPAPI
@ell.tool()
def search_on_internet(query: str) -> str:
    search_url = "https://serpapi.com/search"
    params = {
        "q": query,
        "api_key": os.getenv("SERPAPI_API_KEY"),
    }
    response = requests.get(search_url, params=params)

    if response.status_code == 200:
        data = response.json()
        if "organic_results" in data:
            results = data["organic_results"][:3]
            text = results[0]["snippet"] if results else "No snippet available"
            final = {
                "results": results,
                "text": text
            }
            return f"Top 3 results: {final['results']}\n\nText: {final['text']}"
        else:
            return "No results found."
    else:
        return f"Error fetching search results: {response.status_code}"

# Define a complex task using ell and GPT model
@ell.complex(model="gpt-4o-mini", client=client, tools=[search_on_internet])
def article(country: str):
    """You are a helpful assistant"""  # System prompt
    name = country.capitalize()
    return f"Who is the president of {name} in 2024?"  # User prompt

# Test the function
print(article("united states"))
注意:如果使用 Ell 的最新版本,它现在可以工作了。
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os
import requests

# Load environment variables
load_dotenv()

# Initialize ell logging store
ell.init(store='./logdir')

# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Define a tool to search on the internet using SERPAPI
@ell.tool()
def search_on_internet(query: str) -> str:
    search_url = "https://serpapi.com/search"
    params = {
        "q": query,
        "api_key": os.getenv("SERPAPI_API_KEY"),
    }
    response = requests.get(search_url, params=params)

    if response.status_code == 200:
        data = response.json()
        if "organic_results" in data:
            results = data["organic_results"][:3]
            text = results[0]["snippet"] if results else "No snippet available"
            final = {
                "results": results,
                "text": text
            }
            return f"Top 3 results: {final['results']}\n\nText: {final['text']}"
        else:
            return "No results found."
    else:
        return f"Error fetching search results: {response.status_code}"

# Define a complex task using ell and GPT model
@ell.complex(model="gpt-4o-mini", client=client, tools=[search_on_internet])
def article(country: str):
    """You are a helpful assistant"""  # System prompt
    name = country.capitalize()
    return f"Who is the president of {name} in 2024?"  # User prompt

# Test the function
output = article("united states")

if output.tool_calls:
    print(output.tool_calls[0]())
多模态是头等公民
我喜欢 Ell 将多模态作为一流特性。这开启了广泛的用例,我对此感到非常兴奋。能够上传图像并根据这些图像提问非常酷。
这是我使用的代码:
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os
from PIL import Image

# Load environment variables
load_dotenv()

# Initialize ell logging store
ell.init(store='./logdir')

# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))


@ell.simple(model="gpt-4o", client=client)
def describe_activity(image: Image.Image):
   return [
      ell.system("You are VisionGPT. Answer <5 words all lower case."),
      ell.user(["Describe the content of the image:", image])
   ]

# Load Image
img = Image.open("product_2.jpg")

# desc
print(describe_activity(img))
我得到的结果相当准确。你可以看看这里:

结构化输出
我喜欢 Ell 支持结构化输出。不幸的是,这一功能仅对特定版本的 GPT-4 模型可用。由于许多库如 Instructor 提供更广泛的覆盖,我觉得 Ell 需要更多地支持所有可用模型的结构化输出。这可以通过 Instructor 实现的基本重试逻辑来完成。我首先测试了文档中提供的代码。以下是代码:
from pydantic import BaseModel, Field
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os

# Load environment variables
load_dotenv()

# Initialize ell logging store
ell.init(store='./logdir')

# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

class MovieReview(BaseModel):
    title: str = Field(description="The title of the movie")
    rating: int = Field(description="The rating of the movie out of 10")
    summary: str = Field(description="A brief summary of the movie")


@ell.complex(model="gpt-4o-2024-08-06", client=client, response_format=MovieReview)
def generate_movie_review(movie: str) -> MovieReview:
    """You are a movie review generator. Given the name of a movie, you need to return a structured review."""
    return f"generate a review for the movie {movie}"
然后我想到了构建一个合成数据生成器。生成器以公司名称为输入,并为该公司生成合成员工。相当酷吧?这只是众多潜在用例中的一个。你可以想象使用这种结构化输出生成功能构建一个金融分析师 Agent 或国际象棋 Agent。
from pydantic import BaseModel, Field
import ell
from dotenv import load_dotenv
from openai import OpenAI
import os

# Load environment variables
load_dotenv()

# Initialize ell logging store
ell.init(store='./logdir')

# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Define your desired output structure
class Employee(BaseModel):
    name: str
    position: str
    department: str
    hire_date: str
    salary: int
@ell.complex(model="gpt-4o-2024-08-06", client=client, response_format=Employee, n=10)
def generate_synthetic_data(company: str) -> list[Employee]:
    """"""
    return f"generate employees for this company: {company}"


for employee in generate_synthetic_data("Nebius"):
    print(employee.parsed)
总结
总的来说,我很喜欢尝试 Ell,看到不同的抽象被构建来与 LLM 一起工作真是太棒了。我并不认同所有的抽象,但软件总是有其偏见 —— 对我来说不完美的东西可能对别人来说正合适。Ell 的成熟度不如 DSPy,因此实现诸如 RAG 之类的功能可能需要更多工作去做。Ell 也没有 LangChain 提供的广泛集成。但随着时间的推移,事情会发生变化,说实话,谁还需要另一个偏见强烈的集成工具呢?

我鼓励大家尝试 Ell 并形成自己的看法。有时,我在想当输入只是字符串而输出是字符串加上一些元数据时,我们是否真的需要在 API 调用之上添加抽象。如果需要,正确的抽象级别应该是什么?这又扯到另外一个话题上了。
用户评论