LLPhant - 一个全面的PHP生成式AI框架
我们设计这个框架的目的是尽量做到简单,同时为您提供构建强大应用所需的工具。 它与Symfony和Laravel兼容。
我们正在努力扩展对不同LLM的支持。目前,我们支持OpenAI、Anthropic、Mistral和Ollama, Ollama可以用于本地运行LLM,例如Llama 2。
我们要感谢一些启发我们或我们使用的优秀项目:
- 从使用LangChain和LLamaIndex中学到的知识
- OpenAI PHP SDK的出色工作。
我们可以在LLPhant上找到很棒的外部资源(如果您有资源,请联系我们添加):
- 🇫🇷 Construire un RAG en PHP avec la doc de Symfony, LLPhant et OpenAI : Tutoriel Complet
- 🇫🇷 Retour d'expérience sur la création d'un agent autonome
目录
开始
需要PHP 8.1+
首先,通过Composer包管理器安装LLPhant:
composer require theodo-group/llphant
如果您想尝试该库的最新功能,可以使用:
composer require theodo-group/llphant:dev-main
您可能还想查看OpenAI PHP SDK的要求,因为它是主要的客户端。
用例
生成式AI有很多用例,并且每天都会有新的用例产生。让我们看看最常见的用例。 根据来自MLOPS社区的调查和麦肯锡的这项调查 ,AI最常见的用例如下:
- 创建语义搜索,可以在大量数据中找到相关信息。示例:Slite
- 创建使用语义搜索和文本摘要来回答客户问题的聊天机器人/增强FAQ。示例:Quivr正在使用类似的技术。
- 为客户创建个性化内容(产品页面、电子邮件、消息等)。示例:Carrefour。
- 创建文本摘要器,可以将长文本总结成短文本。
虽然尚未广泛传播,但随着采用的增加:
- 创建增强电子商务体验的个人购物助手。示例:Madeline
- 创建能够执行各种任务的AI代理。示例:AutoGpt
- 创建可以帮助您编写或审查代码的编码工具。示例:Code Review GPT
如果您想了解更多来自社区的使用案例,您可以在这里看到GenAI Meetups的列表。 您还可以在Qdrant’s website上看到其他用例。
用法
您可以使用OpenAI、Mistral、Ollama 或Anthropic作为LLM引擎。
OpenAI
调用OpenAI最简单的方法是设置OPENAI_API_KEY环境变量。
export OPENAI_API_KEY=sk-XXXXXX
您也可以创建一个OpenAIConfig对象并将其传递给OpenAIChat或OpenAIEmbeddings的构造函数。
$config = new OpenAIConfig();
$config->apiKey = 'fakeapikey';
$chat = new OpenAIChat($config);
Mistral
如果您想使用Mistral,只需使用OpenAIConfig
对象指定要使用的模型,并将其传递给MistralAIChat
。
$config = new OpenAIConfig();
$config->apiKey = 'fakeapikey';
$chat = new MistralAIChat($config);
Ollama
如果您想使用Ollama,只需使用OllamaConfig
对象指定要使用的模型,并将其传递给OllamaChat
。
$config = new OllamaConfig();
$config->model = 'llama2';
$chat = new OllamaChat($config);
Anthropic
要调用Anthropic模型,您必须提供API密钥。您可以设置ANTHROPIC_API_KEY环境变量。
export ANTHROPIC_API_KEY=XXXXXX
您还必须使用AnthropicConfig
对象指定要使用的模型,并将其传递给AnthropicChat
。
$chat = new AnthropicChat(new AnthropicConfig(AnthropicConfig::CLAUDE_3_5_SONNET));
创建没有配置的聊天将使用CLAUDE_3_HAIKU模型。
$chat = new AnthropicChat();
聊天
💡 此类可用于生成内容、创建聊天机器人或创建文本摘要器。
您可以使用OpenAIChat
、MistralAIChat
或OllamaChat
生成文本或创建聊天。
我们可以使用它从提示中简单生成文本。这将直接询问LLM答案。
$response = $chat->generateText('what is one + one ?'); // 将返回类似 "Two" 的答案
如果您想在前端显示类似于ChatGPT的文本流,您可以使用以下方法。
return $chat->generateStreamOfText('can you write me a poem of 10 lines about life ?');
您可以添加指令,使LLM以特定方式表现。
$chat->setSystemMessage('Whatever we ask you, you MUST answer "ok"');
$response = $chat->generateText('what is one + one ?'); // 将返回 "ok"
图像
您可以使用OpenAIImage
生成图像。
我们可以使用它从提示中简单生成图像。
$response = $image->generateImage('A cat in the snow', OpenAIImageStyle::Vivid); // 将返回一个LLPhant\Image\Image对象
问答中自定义系统消息
在使用QuestionAnswering
类时,可以自定义系统消息,以根据您的具体需求指南AI的响应风格和上下文敏感性。此功能允许您增强用户与AI之间的互动,使其更适应和响应特定场景。
以下是如何设置自定义系统消息:
use LLPhant\Query\SemanticSearch\QuestionAnswering;
$qa = new QuestionAnswering($vectorStore, $embeddingGenerator, $chat);
$customSystemMessage = 'Your are a helpful assistant. Answer with conversational tone. \\n\\n{context}.';
$qa->systemMessageTemplate = $customSystemMessage;
工具
此功能非常出色,并且适用于OpenAI和Anthropic。
OpenAI已经优化了其模型,以确定是否应该调用工具。 要利用这一点,只需将可用工具的描述发送给OpenAI, 无论是作为单个提示还是在较广泛的对话中。
在响应中,模型将提供调用的工具名称以及参数值, 如果认为应该调用一个或多个工具的话。
一个潜在的应用是确定用户在支持互动期间是否有其他查询。 更令人惊叹的是,它可以根据用户查询自动执行操作。
我们尽量使此功能的使用变得简单。
让我们看一个如何使用它的示例。 假设您有一个发送电子邮件的类。
class MailerExample
{
/**
* This function send an email
*/
public function sendMail(string $subject, string $body, string $email): void
{
echo 'The email has been sent to '.$email.' with the subject '.$subject.' and the body '.$body.'.';
}
}
您可以创建一个FunctionInfo对象来描述您的方法给OpenAI。 然后将其添加到OpenAIChat对象中。 如果来自OpenAI的响应包含工具名称和参数,LLPhant将调用该工具。
这个PHP脚本很可能会调用我们传递给OpenAI的sendMail方法。
$chat = new OpenAIChat();
// This helper will automatically gather information to describe the tools
$tool = FunctionBuilder::buildFunctionInfo(new MailerExample(), 'sendMail');
$chat->addTool($tool);
$chat->setSystemMessage('You are an AI that deliver information using the email system.
When you have enough information to answer the question of the user you send a mail');
$chat->generateText('Who is Marie Curie in one line? My email is student@foo.com');
如果您想对函数的描述有更多的控制,可以手动构建:
$chat = new OpenAIChat();
$subject = new Parameter('subject', 'string', 'the subject of the mail');
$body = new Parameter('body', 'string', 'the body of the mail');
$email = new Parameter('email', 'string', 'the email address');
$tool = new FunctionInfo( 'sendMail', new MailerExample(), '发送邮件', [$subject, $body, $email] );
$chat->addTool($tool); $chat->setSystemMessage('你是一个通过电子邮件系统传递信息的AI。当你有足够的信息回答用户的问题时,你会发送邮件'); $chat->generateText('用一句话描述玛丽·居里是谁?我的邮箱是student@foo.com');
你可以安全地在Parameter对象中使用以下类型:string, int, float, bool。数组类型是支持的,但仍处于实验阶段。
使用`AnthropicChat`你还可以指示LLM引擎使用本地调用的工具的结果作为下一个推理的输入。
这里有一个简单的例子。假设我们有一个`WeatherExample`类,其中包含一个`currentWeatherForLocation`方法,该方法调用外部服务以获取天气信息。
该方法的输入是描述位置的字符串,并返回包含当前天气描述的字符串。
```php
$chat = new AnthropicChat();
$location = new Parameter('location', 'string', '城市、州或省以及国家的名称');
$weatherExample = new WeatherExample();
$function = new FunctionInfo(
'currentWeatherForLocation',
$weatherExample,
'返回给定位置的当前天气。结果包含天气的描述以及当前的摄氏温度',
[$location]
);
$chat->addFunction($function);
$chat->setSystemMessage('你是一个通过调用外部服务获取信息来回答特定位置天气问题的AI');
$answer = $chat->generateText('威尼斯的天气怎么样?');
嵌入
💡 嵌入用于比较两个文本,并查看它们的相似程度。这是语义搜索的基础。
嵌入是文本的向量表示,捕捉了文本的含义。 对于OpenAI的小模型,它是一个包含1536个元素的浮点数组。
要操作嵌入,我们使用包含文本和一些有用的元数据的Document
类。
创建嵌入遵循以下流程:
读取数据
流程的第一部分是从一个来源读取数据。 这可以是一个数据库、csv文件、json文件、文本文件、网站、pdf、word文档、excel文件等。 唯一的要求是你能读取数据并从中提取文本。
目前我们只支持文本文件、pdf和docx,但我们计划在未来支持其他数据类型。
你可以使用FileDataReader
类来读取文件。它需要一个文件路径或目录作为参数。
第二个可选参数是用于存储嵌入的实体类名。
该类需要继承Document
类,
如果你希望使用Doctrine向量存储,还需要继承DoctrineEmbeddingEntityBase
类(该类继承自Document
类)。
以下是使用示例PlaceEntity
类作为文档类型的示例:
$filePath = __DIR__.'/PlacesTextFiles';
$reader = new FileDataReader($filePath, PlaceEntity::class);
$documents = $reader->getDocuments();
如果你愿意使用默认的Document
类,你可以这样做:
$filePath = __DIR__.'/PlacesTextFiles';
$reader = new FileDataReader($filePath);
$documents = $reader->getDocuments();
要创建自己的数据读取器,你需要创建一个实现DataReader
接口的类。
文档拆分
嵌入模型有一个可以处理的字符串大小限制。
为避免这个问题,我们将文档拆分成较小的块。
DocumentSplitter
类用于将文档拆分成较小的块。
$splitDocuments = DocumentSplitter::splitDocuments($documents, 800);
嵌入格式化器
EmbeddingFormatter
是一个可选步骤,用于将每个文本块格式化为具有最多上下文的格式。
添加标题和指向其他文档的链接可以帮助LLM理解文本的上下文。
$formattedDocuments = EmbeddingFormatter::formatEmbeddings($splitDocuments);
嵌入生成器
这是通过调用LLM为每个文本块生成嵌入的步骤。
2024年1月30日 : 添加Mistral嵌入API
你需要拥有一个Mistral账户以使用此API。更多信息请访问Mistral网站。
你需要设置MISTRAL_API_KEY环境变量或将其传递给MistralEmbeddingGenerator
类的构造函数。
2024年1月25日 : 新嵌入模型和API更新 OpenAI有两个新模型可用于生成嵌入。更多信息请访问OpenAI博客。
状态 | 模型 | 嵌入大小 |
---|---|---|
默认 | text-embedding-ada-002 | 1536 |
新 | text-embedding-3-small | 1536 |
新 | text-embedding-3-large | 3072 |
你可以使用以下代码来嵌入文档:
$embeddingGenerator = new OpenAI3SmallEmbeddingGenerator();
$embeddedDocuments = $embeddingGenerator->embedDocuments($formattedDocuments);
你还可以使用以下代码从文本创建嵌入:
$embeddingGenerator = new OpenAI3SmallEmbeddingGenerator();
$embedding = $embeddingGenerator->embedText('我喜欢美食');
//然后你可以使用这个嵌入执行相似度搜索
还有OllamaEmbeddingGenerator
,其嵌入大小为1024。
向量存储
一旦你有了嵌入,你就需要将它们存储在向量存储中。 向量存储是一个可以存储向量并执行相似性搜索的数据库。 当前有这些vectorStore类:
- MemoryVectorStore将嵌入存储在内存中
- FileSystemVectorStore将嵌入存储在文件中
- DoctrineVectorStore将嵌入存储在postgresql数据库中(需要doctrine/orm)
- QdrantVectorStore将嵌入存储在QdrantvectorStore中(需要hkulekci/qdrant)
- RedisVectorStore将嵌入存储在Redis数据库中(需要predis/predis)
- ElasticsearchVectorStore将嵌入存储在Elasticsearch数据库中(需要elasticsearch/elasticsearch)
- MilvusVectorStore将嵌入存储在Milvus数据库中
- ChromaDBVectorStore将嵌入存储在ChromaDB数据库中
- AstraDBVectorStore将嵌入存储在AstraDBB数据库中
使用DoctrineVectorStore
类将嵌入存储在数据库中的示例:
$vectorStore = new DoctrineVectorStore($entityManager, PlaceEntity::class);
$vectorStore->addDocuments($embeddedDocuments);
完成这一步后,你可以对数据进行相似性搜索。 你需要传递想要搜索的文本嵌入和想要获取的结果数量。
$embedding = $embeddingGenerator->embedText('法国这个国家');
/** @var PlaceEntity[] $result */
$result = $vectorStore->similaritySearch($embedding, 2);
要获取完整示例,你可以查看Doctrine集成测试文件。
Doctrine VectorStore
对于Web开发人员来说,一个简单的解决方案是使用带有pgvector扩展的postgresql数据库作为vectorStore。 你可以在其github仓库中找到有关pgvector扩展的所有信息。
我们建议你三种简单的解决方案来获取启用了扩展的postgresql数据库:
- 使用带有docker-compose-pgvector.yml文件的docker
- 使用Supabase
- 使用Neon
无论哪种情况,你都需要激活扩展:
CREATE EXTENSION IF NOT EXISTS vector;
然后你可以创建一个表并存储向量。 这个sql查询将在测试文件夹中创建与PlaceEntity对应的表。
CREATE TABLE IF NOT EXISTS test_place (
id SERIAL PRIMARY KEY,
content TEXT,
type TEXT,
sourcetype TEXT,
sourcename TEXT,
embedding VECTOR
);
⚠️ 如果嵌入长度不是1536,你需要在实体中通过覆盖$embedding属性来指定它。
通常,如果你使用OpenAI3LargeEmbeddingGenerator
类,你需要在实体中将长度设置为3072。
或者,如果你使用MistralEmbeddingGenerator
类,你需要在实体中将长度设置为1024。
PlaceEntity
#[Entity]
#[Table(name: 'test_place')]
class PlaceEntity extends DoctrineEmbeddingEntityBase
{
#[ORM\Column(type: Types::STRING, nullable: true)]
public ?string $type;
#[ORM\Column(type: VectorType::VECTOR, length: 3072)]
public ?array $embedding;
}
Redis VectorStore
前提条件:
- 运行Redis服务器(参见Redis quickstart)
- 安装Predis composer包(参见Predis)
然后使用服务器凭据创建一个新的Redis客户,并将其传递给RedisVectorStore构造函数:
use Predis\Client;
$redisClient = new Client([
'scheme' => 'tcp',
'host' => 'localhost',
'port' => 6379,
]);
$vectorStore = new RedisVectorStore($redisClient, 'llphant_custom_index'); // 默认索引是llphant
你现在可以像使用任何其他VectorStore一样使用RedisVectorStore。
Elasticsearch VectorStore
前提条件:
- 运行Elasticsearch服务器(参见Elasticsearch quickstart)
- 安装Elasticsearch PHP客户端(参见Elasticsearch PHP client)
然后使用服务器凭据创建一个新的Elasticsearch客户,并将其传递给ElasticsearchVectorStore构造函数:
使用Elastic\Elasticsearch\ClientBuilder;
$client = (new ClientBuilder())::create()
->setHosts(['http://localhost:9200'])
->build();
$vectorStore = new ElasticsearchVectorStore($client, 'llphant_custom_index'); // 默认索引是llphant
现在你可以像使用其他向量存储一样使用ElasticsearchVectorStore。
Milvus VectorStore
前提条件:Milvus服务器运行中(参见Milvus文档)
然后使用你的服务器凭据创建一个新的Milvus客户端(LLPhant\Embeddings\VectorStores\Milvus\MiluvsClient
),并将其传递给MilvusVectorStore构造函数:
$client = new MilvusClient('localhost', '19530', 'root', 'milvus');
$vectorStore = new MilvusVectorStore($client);
现在你可以像使用其他向量存储一样使用MilvusVectorStore。
ChromaDB VectorStore
前提条件:Chroma服务器运行中(参见Chroma文档)。 你可以使用这个docker compose文件在本地运行它。
然后创建一个新的ChromaDB向量存储(LLPhant\Embeddings\VectorStores\ChromaDB\ChromaDBVectorStore
),例如:
$vectorStore = new ChromaDBVectorStore(host: 'my_host', authToken: 'my_optional_auth_token');
现在你可以像使用其他向量存储一样使用这个向量存储。
AstraDB VectorStore
前提条件:一个AstraDB账号,你可以在其中创建和删除数据库(参见AstraDB文档)。
目前你不能在本地运行这个数据库。你需要设置ASTRADB_ENDPOINT
和ASTRADB_TOKEN
环境变量以连接到你的实例。
然后创建一个新的AstraDB向量存储(LLPhant\Embeddings\VectorStores\AstraDB\AstraDBVectorStore
),例如:
$vectorStore = new AstraDBVectorStore(new AstraDBClient(collectionName: 'my_collection'));
// 你可以使用任何嵌入生成器,但嵌入长度必须与为你的集合定义的长度匹配
$embeddingGenerator = new OpenAI3SmallEmbeddingGenerator();
$currentEmbeddingLength = $vectorStore->getEmbeddingLength();
if ($currentEmbeddingLength === 0) {
$vectorStore->createCollection($embeddingGenerator->getEmbeddingLength());
} elseif ($embeddingGenerator->getEmbeddingLength() !== $currentEmbeddingLength) {
$vectorStore->deleteCollection();
$vectorStore->createCollection($embeddingGenerator->getEmbeddingLength());
}
现在你可以像使用其他向量存储一样使用这个向量存储。
问答
LLM的一个流行用例是创建一个可以回答你私人数据问题的聊天机器人。
你可以使用LLPhant中的QuestionAnswering
类来构建一个。
它利用向量存储进行相似性搜索以获取相关信息,并返回由OpenAI生成的答案。
这里是一个使用MemoryVectorStore
的示例:
$dataReader = new FileDataReader(__DIR__.'/private-data.txt');
$documents = $dataReader->getDocuments();
$splitDocuments = DocumentSplitter::splitDocuments($documents, 500);
$embeddingGenerator = new OpenAIEmbeddingGenerator();
$embeddedDocuments = $embeddingGenerator->embedDocuments($splitDocuments);
$memoryVectorStore = new MemoryVectorStore();
$memoryVectorStore->addDocuments($embeddedDocuments);
//一旦vectorStore准备好,你就可以使用QuestionAnswering类来回答问题
$qa = new QuestionAnswering(
$memoryVectorStore,
$embeddingGenerator,
new OpenAIChat()
);
$answer = $qa->answerQuestion('Alice的秘密是什么?');
多查询转换
在问答过程中,第一个步骤可以将输入查询转换为对聊天引擎更有用的内容。
其中一种转换可能是MultiQuery
转换。
此步骤将原始查询作为输入,然后请求查询引擎重新表述它,以便使用一组查询来从向量存储中检索文档。
$chat = new OpenAIChat();
$qa = new QuestionAnswering(
$vectorStore,
$embeddingGenerator,
$chat,
new MultiQuery($chat)
);
检索文档转换器和重新排序
从向量存储检索到的文档列表可以在发送给聊天作为上下文之前进行转换。其中一种转换可以是重新排序阶段,根据与问题的相关性对文档进行排序。重新排序器返回的文档数量可以小于或等于向量存储返回的文档数量。 下面是一个例子:
$nrOfOutputDocuments = 3;
$reranker = new LLMReranker(chat(), $nrOfOutputDocuments);
$qa = new QuestionAnswering(
new MemoryVectorStore(),
new OpenAI3SmallEmbeddingGenerator(),
new OpenAIChat(new OpenAIConfig()),
retrievedDocumentsTransformer: $reranker
);
$answer = $qa->answerQuestion('谁是《茶花女》的作曲者?', 10);
AutoPHP
你现在可以使用LLPhant在PHP中制作AutoGPT克隆。
这里是一个使用SerpApiSearch工具创建自主PHP代理的简单示例。 你只需描述目标并添加你想使用的工具。 我们将在未来添加更多工具。
使用LLPhant\Chat\FunctionInfo\FunctionBuilder;
使用LLPhant\Experimental\Agent\AutoPHP;
使用LLPhant\Tool\SerpApiSearch;
require_once 'vendor/autoload.php';
// 你描述目标
$objective = '找到2023年法国男子足球队中至少两名球员的妻子或女友的名字。';
// 你可以将工具添加到代理中,让它可以使用它们。你需要API密钥才能使用SerpApiSearch
// 查看这里:https://serpapi.com
$searchApi = new SerpApiSearch();
$function = FunctionBuilder::buildFunctionInfo($searchApi, 'search');
$autoPHP = new AutoPHP($objective, [$function]);
$autoPHP->run();
常见问题
为什么使用LLPhant而不是直接使用OpenAI PHP SDK?
OpenAI PHP SDK是一个与OpenAI API交互的绝佳工具。 LLPhant将允许你执行复杂的任务,比如存储嵌入并进行相似性搜索。 它还通过提供更简单的API来简化OpenAI API的日常使用。
贡献者
感谢我们的贡献者:
赞助商
LLPhant由Theodo赞助,这是一家领先的数字代理机构,使用生成式AI构建Web应用程序。
```