PHP and LLMs — Web Scraping and Building an Events Database using Tools

Alfred Nutile
15 min readAug 28, 2024

--

Listen to NoteBookLM generate a podcast on this chapter at https://readorlisten.com/episodes/php-and-llms-web-scraping-and-building-an-events-database-using-tools

This is a chapter from the “PHP and LLMs” book you can buy at

https://bit.ly/php_llms

Or get a sample at

https://bit.ly/php_llms_sample

Or join the mailing list and keep up to date https://sundance-solutions.mailcoach.app/php-and-llms

Enjoy!

Summary of What You Will Learn

  • Understand how to process data scraped from websites using LLMs.
  • Learn to create tools that can manage and process complex arrays of objects like events.
  • Explore the use of LLMs to find and create events from web pages.
  • See how to define complex parameter structures (JSON) for LLM tools.
  • Gain insights into orchestration, chaining tool results, and generating a final response.

Big Picture View

Here, we explore how we can take data scraped from a website and use LLMs to process the content. The chapter includes examples of tools that can extract relevant information from web pages and convert it into Events in our database.

We don’t delve into scraping here, since it’s pretty common and there are many ways to do it. We focus on building a tool that can handle a complex array of objects (Events) for the LLM to pass into it.

Once we have that tool in place, the following prompt can do a lot:

<role>
You are an assistant helping to parse data from HTML content and find the events on the page.
<task>
Using the context below, find all the events and pass them into the create event tool.
After that, save it to a document.
<context>
[INSERT OUR HTML HERE]

As you can see above, we tell the prompt to use our tool. Keep in mind, users may not be so specific; they might write “Find and create events from this page”, but we can test that as well.

We need to create a simple Event model in the database to store the events. After that, we’ll build the tool and define the necessary arguments. When the LLM provides the function call, the tool will then generate the events.

As we saw in a previous chapter, Tools are ways to define a function and parameters that the LLM can use to complete a task. The task can be simple like get_the_weather, but we are going to do more complex tasks. To start with, this tool is going to have a complex parameter definition (this will be JSON for Claude; we'll show the abstracted version later in this chapter).

The get_the_weather is a Class we made to process the arguments. Below is the example structure for the create_event_tool

{
"name": "create_event_tool",
"description": "If the user needs to create one or more events this tool helps",
"input_schema": {
"type": "object",
"properties": {
"events": {
"description": "Array of event objects",
"type": "array",
"items": {
"type": "object",
"properties": {
"start_time": {
"type": "string",
"description": "Start time of the event"
},
"end_time": {
"type": "string",
"description": "End time of the event"
},
"title": {
"type": "string",
"description": "Title of the event"
},
"location": {
"type": "string",
"description": "Location of the event"
},
"summary": {
"type": "string",
"description": "Summary or description of the event"
}
}
}
}
},
"required": [
"events"
]
}
}

This took me a while to get right, but thanks to Claude’s help, I was able to figure out how to do an array of objects.

  • name — this will be seen later registered in our application.
  • description — here we do a good job of letting the LLM know how to use the tool.
  • input_schema — this is the tricky part

You can see here a very simple tool in the ChatGPT docs as an example :

{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}

Now, let’s take it up a notch. Our first property “events” has the type of “array”.

And in that object key, we start to define our properties. As you can see in the curl command below, we are just going to pass this to Claude.

curl --location 'https://api.anthropic.com/v1/messages' \
--header 'x-api-key: my_key' \
--header 'anthropic-version: 2023-06-01' \
--header 'content-type: application/json' \
--data '{
"model": "claude-3-5-sonnet-20240620",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "ALL THE HTML / TEXT WITH EVENTS"
}
],
"tools": [
{
"name": "create_event_tool",
"description": "If the user needs to create one or more events this tool helps",
"input_schema": {
"type": "object",
"properties": {
"events": {
"description": "Array of event objects",
"type": "array",
"items": {
"type": "object",
"properties": {
"start_time": {
"type": "string",
"description": "Start time of the event"
},
"end_time": {
"type": "string",
"description": "End time of the event"
},
"title": {
"type": "string",
"description": "Title of the event"
},
"location": {
"type": "string",
"description": "Location of the event"
},
"summary": {
"type": "string",
"description": "Summary or description of the event"
}
}
}
}
},
"required": [
"events"
]
}
}
]
}'

It will reply as follows:

{
"id": "msg_01GDtWoCBAnDpdLmLbJZGp2u",
"type": "message",
"role": "assistant",
"model": "claude-3-5-sonnet-20240620",
"content": [
{
"type": "text",
"text": "Certainly! I'll parse the events from the Dallas Cowboys schedule and create them using the create_event_tool. I'll focus on the regular season games, as there are quite a few events. Let's create these events:"
},
{
"type": "tool_use",
"id": "toolu_01B9W2pzR5RqDj11QciiseJq",
"name": "create_event_tool",
"input": {
"events": [
{
"title": "Dallas Cowboys at Cleveland Browns",
"start_time": "2024-09-08T15:25:00-05:00",
"end_time": "2024-09-08T18:25:00-05:00",
"location": "Cleveland Browns Stadium",
"summary": "NFL Week 1: Dallas Cowboys at Cleveland Browns"
},
{
"title": "Dallas Cowboys vs New Orleans Saints",
"start_time": "2024-09-15T12:00:00-05:00",
"end_time": "2024-09-15T15:00:00-05:00",
"location": "AT&T Stadium",
"summary": "NFL Week 2: Dallas Cowboys vs New Orleans Saints"
},
{
"title": "Dallas Cowboys vs Baltimore Ravens",
"start_time": "2024-09-22T15:25:00-05:00",
"end_time": "2024-09-22T18:25:00-05:00",
"location": "AT&T Stadium",
"summary": "NFL Week 3: Dallas Cowboys vs Baltimore Ravens"
},
{
"title": "Dallas Cowboys at New York Giants",
"start_time": "2024-09-26T19:15:00-05:00",
"end_time": "2024-09-26T22:15:00-05:00",
"location": "MetLife Stadium",
"summary": "NFL Week 4: Dallas Cowboys at New York Giants"
},
{
"title": "Dallas Cowboys at Pittsburgh Steelers",
"start_time": "2024-10-06T19:20:00-05:00",
"end_time": "2024-10-06T22:20:00-05:00",
"location": "Acrisure Stadium",
"summary": "NFL Week 5: Dallas Cowboys at Pittsburgh Steelers"
}
]
}
}
],
"stop_reason": "tool_use",
"stop_sequence": null,
"usage": {
"input_tokens": 2652,
"output_tokens": 593
}
}

Our tool will iterate over these results to create events — simple, right? But wait, there’s a catch! Keep reading!

Orchestration

It is really easy to call the LLM with PHP code like:

<?php
$result = $client->chat()->create([
'model' => 'gpt-4',
'messages' => [
['role' => 'user', 'content' => 'Hello!'],
],
]);

However, we want to do more than that: we want to be able to chain the results of that to either one or more tools and a final response or just the final response. That is where an Orchestration-like class comes in.

<?php
public function handle(Chat $chat, string $prompt): Message
{
$chat->addInput(
message: $prompt,
role: RoleEnum::User
);

$messageInDto = MessageInDto::from([
'content' => $prompt,
'role' => 'user',
]);
$response = LlmDriverFacade::driver($chat->getDriver())
->chat([
$messageInDto,
]);
if (! empty($response->tool_calls)) {
Log::info('Orchestration Tools Found', [
'tool_calls' => collect($response->tool_calls)
->pluck('name')->toArray(),
]);
$count = 1;
foreach ($response->tool_calls as $tool_call) {
Log::info('[LaraChain] - Tool Call '.$count, [
'tool_call' => $tool_call->name,
'tool_count' => count($response->tool_calls),
]);
$message = $chat->addInput(
message: $response->content ?? 'Calling Tools', //@NOTE ollama, openai blank but claude needs this :(
role: RoleEnum::Assistant,
tool: $tool_call->name,
tool_id: $tool_call->id,
args: $tool_call->arguments,
);
$tool = app()->make($tool_call->name);
$results = $tool->handle($message);
$message->updateQuietly([
'role' => RoleEnum::Tool,
'body' => $results->content,
]);
$count++;
}
$messages = $chat->getChatResponse();
$response = LlmDriverFacade::driver($chat->getDriver())
->chat($messages);
return $chat->addInput(
message: $response->content,
role: RoleEnum::Assistant,
);
} else {
Log::info('No Tools found just gonna chat');
$assistantMessage = $chat->addInput(
message: $response->content ?? 'Calling Tools',
role: RoleEnum::Assistant
);
return $assistantMessage;
}
}

As we discussed in earlier chapters, it’s important to maintain the state of the chat and messages. To achieve this, I typically use a Chat and Message model. This setup allows me to seamlessly connect everything based on the maintained state. We covered this in a previous chapter.

Let’s look at this line by line:

<?php
$messages = $chat->getChatResponse();

Here, we tell the Chat model to give us the thread of messages so we can continue our chat with the LLM.

<?php
$response = LlmDriverFacade::driver($message->getDriver())
->chat($messages);

We pass our message to the LLMs. But, in here is the LLM Driver loading the tools we have.

<?php
$this->getFunctions();

We will look at this one in more detail shortly, but just consider it will load the tool we are going to make, register it with our Driver so it knows to offer this tool as an option in the chat interaction.

Let’s zoom into chat with tools:

<?php 
foreach ($response->tool_calls as $tool_call) {
$message = $chat->addInput(
message: $response->content ?? 'Calling Tools', //ollama, openai blank but claude needs this :(
role: RoleEnum::Assistant,
show_in_thread: false,
meta_data: MetaDataDto::from([
'tool' => $tool_call->name,
'tool_id' => $tool_call->id,
'driver' => $chat->getDriver(),
'args' => $tool_call->arguments,
]),
);
$tool = app()->make($tool_call->name);
$results = $tool->handle($message);
$message->updateQuietly([
'is_chat_ignored' => true,
'role' => RoleEnum::Tool,
'body' => $results->content,
]);
$count++;
}

You can see it just iterates over tool(s) until it is done, then finishes off with:

<?php
$messages = $chat->getChatResponse();
/**
* @NOTE
* I have to continue to pass in tools once used above
* Since Claude needs them.
*/
$response = LlmDriverFacade::driver($chat->getDriver())
->setToolType(ToolTypes::Source)
->chat($messages);
$assistantMessage = $chat->addInput(
message: $response->content,
role: RoleEnum::Assistant,
show_in_thread: true,
meta_data: null,
tools: null
);

So as you see above, we use getChatResponse to get the message thread, then we pass the final results after all the tools are done being called, back to the LLM to make the final message.

Deep Dive

Ok now that we are done with the “Big Picture View”, let’s dive into the details.

Start with the Request

The request comes in via a Job, Controller, or Command (there are other ways but just to focus on a few). The request then does the work, in this case to scrape a webpage. Then, we take that content and pass it into the Orchestrate class like this:

https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/Orchestration/Orchestrate.php#L14

<?php
use Facades\App\Services\LlmServices\Orchestration\Orchestrate;
Orchestrate::handle($chat, $prompt);

NOTE: These classes have tests if you want to read how they work

So what is happening here is we are always dealing with Chat. I do this so we have a root to the thread. Sometimes we will use Message, but in this case, we are working at the Chat level. You will see in a prior chapter we made the Chat model.

And the $prompt is the start of this work, which by now has gone through a few revisions. The prompt is basically the question the user is asking of the system. The question has a tool in it by happenstance in that the user might not even know the tool exists; they just asked for something that happened to have the needed tools.

The Orchestrate class then hands the question and the tools to the LLM to see if it can answer it with or without tools.

If the response comes back tool-based, e.g.:

{
"id": "msg_01GDtWoCBAnDpdLmLbJZGp2u",
"type": "message",
"role": "assistant",
"model": "claude-3-5-sonnet-20240620",
"content": [
{
"type": "text",
"text": "Certainly! I'll parse the events from the Dallas Cowboys schedule and create them using the create_event_tool. I'll focus on the regular season games, as there are quite a few events. Let's create these events:"
},
{
"type": "tool_use",
"id": "toolu_01B9W2pzR5RqDj11QciiseJq",
"name": "create_event_tool",
"input": {
"events": [
{
"title": "Dallas Cowboys at Cleveland Browns",
"start_time": "2024-09-08T15:25:00-05:00",
"end_time": "2024-09-08T18:25:00-05:00",
"location": "Cleveland Browns Stadium",
"summary": "NFL Week 1: Dallas Cowboys at Cleveland Browns"
},
{
"title": "Dallas Cowboys vs New Orleans Saints",
"start_time": "2024-09-15T12:00:00-05:00",
"end_time": "2024-09-15T15:00:00-05:00",
"location": "AT&T Stadium",
"summary": "NFL Week 2: Dallas Cowboys vs New Orleans Saints"
},
{
"title": "Dallas Cowboys vs Baltimore Ravens",
"start_time": "2024-09-22T15:25:00-05:00",
"end_time": "2024-09-22T18:25:00-05:00",
"location": "AT&T Stadium",
"summary": "NFL Week 3: Dallas Cowboys vs Baltimore Ravens"
},
{
"title": "Dallas Cowboys at New York Giants",
"start_time": "2024-09-26T19:15:00-05:00",
"end_time": "2024-09-26T22:15:00-05:00",
"location": "MetLife Stadium",
"summary": "NFL Week 4: Dallas Cowboys at New York Giants"
},
{
"title": "Dallas Cowboys at Pittsburgh Steelers",
"start_time": "2024-10-06T19:20:00-05:00",
"end_time": "2024-10-06T22:20:00-05:00",
"location": "Acrisure Stadium",
"summary": "NFL Week 5: Dallas Cowboys at Pittsburgh Steelers"
}
]
}
}
],
"stop_reason": "tool_use",
"stop_sequence": null,
"usage": {
"input_tokens": 2652,
"output_tokens": 593
}
}

Then we process the tools requests.

The LLM Client

All of this happens in the Client, we will focus on the Claude client for now. https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/ClaudeClient.php

We cover this in an earlier chapter

<?php
$response = LlmDriverFacade::driver($chat->getDriver())
->chat([
$messageInDto,
]);

The driver is set to claude so we then talk to the CluadeClient chat method:

<?php
/**
* @param MessageInDto[] $messages
*/
public function chat(array $messages): CompletionResponse
{
$model = $this->getConfig('claude')['models']['completion_model'];
$maxTokens = $this->getConfig('claude')['max_tokens'];
Log::info('LlmDriver::Claude::chat');
$messages = $this->remapMessages($messages);
$payload = [
'model' => $model,
'max_tokens' => $maxTokens,
'messages' => $messages,
];
$payload = $this->modifyPayload($payload);
$results = $this->getClient()->post('/messages', $payload);
if (! $results->ok()) {
$error = $results->json()['error']['type'];
$message = $results->json()['error']['message'];
Log::error('Claude API Error Chat', [
'type' => $error,
'message' => $message,
]);
throw new \Exception('Claude API Error Chat');
}
return ClaudeCompletionResponse::from($results->json());
}

We cover a lot of this in other chapters, but for now I want to focus on:

<?php
$payload = $this->modifyPayload($payload);

This is how we turned the CreateEventTool into an LLM agnostic tool payload.

The modifyPayload calls the parent class to get the functions and then its own remapFunctions (seen below) all the LLM client have their own remapping, though most use the same format as ChatGPT.

The code below does not matter as much as realizing that we are taking the formatting of the tools parameters (will show after this) and turning them into the JSON format Claude API requires.

<?php
public function remapFunctions(array $functions): array
{
return collect($functions)->map(function ($function) {
$properties = [];
$required = [];

$type = data_get($function, 'parameters.type', 'object');
foreach (data_get($function, 'parameters.properties', []) as $property) {
$name = data_get($property, 'name');
if (data_get($property, 'required', false)) {
$required[] = $name;
}
$subType = data_get($property, 'type', 'string');
$properties[$name] = [
'description' => data_get($property, 'description', null),
'type' => $subType,
];
if ($subType === 'array') {
$subItems = $property['properties'][0]->properties; //stop at this for now
$subItemsMapped = [];
foreach ($subItems as $subItemKey => $subItemValue) {
$subItemsMapped[$subItemValue->name] = [
'type' => $subItemValue->type,
'description' => $subItemValue->description,
];
}
$properties[$name]['items'] = [
'type' => 'object',
'properties' => $subItemsMapped,
];
}
}
$itemsOrProperties = $properties;
return [
'name' => data_get($function, 'name'),
'description' => data_get($function, 'description'),
'input_schema' => [
'type' => 'object',
'properties' => $itemsOrProperties,
'required' => $required,
],
];
})->values()->toArray();

At this point we just make an HTTP request to the API and return it.

NOTE: I talk about MessageInDto and ClaudeCompletionResponse in another section but the goal here is ALL llms take the same Data Object and return the same data object. In this case ClaudeCompletionResponse extends CompletionResponse. This is really powerful in that we can now talk to and return the same object from any LLM we plug in.

The CreateEventTool

Ok now that we see how Orchestrate works and how the ClaudeClient reformats the functions lets look at the foundation to tools and in this case the CreateEventTool

https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/Functions/CreateEventTool.php

You will see this extends FunctionContract so let's start with that below:

https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/Functions/FunctionContract.php

<?php
namespace App\Services\LlmServices\Functions;
use App\Models\Message;
abstract class FunctionContract
{
protected string $name;
protected string $description;
protected string $type = 'object';
abstract public function handle(
Message $message,
): FunctionResponse;
public function getFunction(): FunctionDto
{
return FunctionDto::from(
[
'name' => $this->getName(),
'description' => $this->getDescription(),
'parameters' => [
'type' => $this->type,
'properties' => $this->getProperties(),
],
]
);
}
public function getName(): string
{
return $this->name;
}
public function getKey(): string
{
return $this->name;
}
public function getDescription(): string
{
return $this->description;
}
public function getParameters(): array
{
return $this->getProperties();
}
/**
* @return PropertyDto[]
*/
abstract protected function getProperties(): array;
}

Our tools must have a name and a good description.

See some docs at https://docs.anthropic.com/en/docs/build-with-claude/tool-use

Then, we have the handle that all tools have that will return the object FunctionDto

<?php

namespace App\Services\LlmServices\Functions;
class FunctionDto extends \Spatie\LaravelData\Data
{
public function __construct(
public string $name,
public string $description,
public ParametersDto $parameters,
) {}
}

Ok now lets see how the CreateEventTool extends this:

<?php

namespace App\Services\LlmServices\Functions;
use App\Models\Event;
use App\Models\Message;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class CreateEventTool extends FunctionContract
{
protected string $name = 'create_event_tool';
protected string $description = 'If the user needs to create one or more events this tool help';
public function handle(
Message $message): FunctionResponse
{
Log::info('CreateEventTool called');
$args = $message->args;
$eventArray = data_get($args, 'events', []);
foreach ($eventArray as $event) {
$start_date = null;
$end_date = null;
$description = data_get($event, 'description', null);
$start_time = data_get($event, 'start_time', null);
$end_time = data_get($event, 'end_time', null);
$location = data_get($event, 'location', null);
$title = data_get($event, 'title', 'No Title Found');
$all_day = data_get($event, 'all_day', false);
if ($start_time != '' || $start_time != null) {
$start_date = Carbon::parse($start_time)->format('Y-m-d');
$start_time = Carbon::parse($start_time)->format('H:i:s');
}
if ($end_time != '' || $end_time != null) {
$end_date = Carbon::parse($end_time)->format('Y-m-d');
$end_time = Carbon::parse($end_time)->format('H:i:s');
}
Event::updateOrCreate([
'title' => $title,
'start_date' => $start_date,
'start_time' => $start_time,
'location' => $location,
],
[
'description' => $description,
'end_date' => $end_date,
'end_time' => $end_time,
'all_day' => $all_day,
]);
}
return FunctionResponse::from([
'content' => json_encode($eventArray),
'prompt' => $message->body,
]);
}
/**
* @return PropertyDto[]
*/
protected function getProperties(): array
{
return [
new PropertyDto(
name: 'events',
description: 'Array of event objects',
type: 'array',
required: true,
properties: [
new PropertyDto(
name: 'items',
description: 'Event object',
type: 'object',
required: true,
properties: [
new PropertyDto(
name: 'start_time',
description: 'Start time of the event',
type: 'string',
required: true
),
new PropertyDto(
name: 'end_time',
description: 'End time of the event',
type: 'string',
required: false
),
new PropertyDto(
name: 'title',
description: 'Title of the event',
type: 'string',
required: true
),
new PropertyDto(
name: 'location',
description: 'Location of the event',
type: 'string',
required: false
),
new PropertyDto(
name: 'description',
description: 'Description of the event',
type: 'string',
required: true
),
]
),
]
),
];
}
}

Let’s look first at the function/tool name create_event_tool this is key since we register this in https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/LlmServiceProvider.php which we registered in an earlier chapter.

<?php
public function boot(): void
{
$this->app->bind('llm_driver', function () {
return new LlmDriverClient;
});

$this->app->bind('create_event_tool', function () {
return new CreateEventTool;
});
}

Then we register the tool in the BaseClient: https://github.com/alnutile/php-llms/blob/main/app/Services/LlmServices/BaseClient.php

<?php
public function getFunctions(): array
{
$functions = collect(
[
new CreateEventTool,
]
);

return $functions->transform(
/** @phpstan-ignore-next-line */
function (FunctionContract $function) {
return $function->getFunction();
}
)->toArray();
}

So, now when any LLM Client runs and calls getFunctions seen above, it will get the needed functions from this method and we append that to the chat payload.

<?php
//app/Services/LlmServices/Orchestration/Orchestrate.php
foreach ($response->tool_calls as $tool_call) {
Log::info('[LaraChain] - Tool Call '.$count, [
'tool_call' => $tool_call->name,
'tool_count' => count($response->tool_calls),
]);

$message = $chat->addInput(
message: $response->content ?? 'Calling Tools', //@NOTE ollama, openai blank but claude needs this :(
role: RoleEnum::Assistant,
tool: $tool_call->name,
tool_id: $tool_call->id,
args: $tool_call->arguments,
);
$tool = app()->make($tool_call->name);
$results = $tool->handle($message);
$message->updateQuietly([
'role' => RoleEnum::Tool,
'body' => $results->content,
]);
$count++;
}

Orchestrate then knows to call the tool class and its handle method.

Note that Orchestrate sets the $message to have args and other tool info. That is key.

At this point, we see that the tool gets the args and iterates over them, knowing the format will match its parameters.

<?php
$eventArray = data_get($args, 'events', []);

foreach ($eventArray as $event) {
$start_date = null;
$end_date = null;
$description = data_get($event, 'description', null);
$start_time = data_get($event, 'start_time', null);
$end_time = data_get($event, 'end_time', null);
$location = data_get($event, 'location', null);
$title = data_get($event, 'title', 'No Title Found');
$all_day = data_get($event, 'all_day', false);
if ($start_time != '' || $start_time != null) {
$start_date = Carbon::parse($start_time)->format('Y-m-d');
$start_time = Carbon::parse($start_time)->format('H:i:s');
}
if ($end_time != '' || $end_time != null) {
$end_date = Carbon::parse($end_time)->format('Y-m-d');
$end_time = Carbon::parse($end_time)->format('H:i:s');
}
Event::updateOrCreate([
'title' => $title,
'start_date' => $start_date,
'start_time' => $start_time,
'location' => $location,
],
[
'description' => $description,
'end_date' => $end_date,
'end_time' => $end_time,
'all_day' => $all_day,
]);
return FunctionResponse::from([
'content' => json_encode($eventArray),
'prompt' => $message->body,
]);
}

Finally, all Tools return FunctionResponse so Orchestrate can use the response if needed.

Let’s look at the getProperties of the CreateEventTool. As noted in a previous chapter, we abstract out the parameters so that it starts as and array of PropertyDto objects. That later any LLM can transform.

<?php
/**
* @return PropertyDto[]
*/
protected function getProperties(): array
{
return [
new PropertyDto(
name: 'events',
description: 'Array of event objects',
type: 'array',
required: true,
properties: [
new PropertyDto(
name: 'items',
description: 'Event object',
type: 'object',
required: true,
properties: [
new PropertyDto(
name: 'start_time',
description: 'Start time of the event',
type: 'string',
required: true
),
new PropertyDto(
name: 'end_time',
description: 'End time of the event',
type: 'string',
required: false
),
new PropertyDto(
name: 'title',
description: 'Title of the event',
type: 'string',
required: true
),
new PropertyDto(
name: 'location',
description: 'Location of the event',
type: 'string',
required: false
),
new PropertyDto(
name: 'description',
description: 'Description of the event',
type: 'string',
required: true
),
]
),
]
),
];
}

What You’ve Learned

In summary, we see a few patterns that lay the foundation to all our interactiosn with LLMs (some of this we covered in previous chapters):

  • Our LLM Clients have a DataObject in and DataObject out that they all share.
  • Our Tools have parameters that are objects that then any LLM Client can transform to the needed JSON for that API.
  • The messages we get back have to be transformed as well and like above the different LLM Clients need a different format to the messages.
  • Tools are powerful and with the Orchestrate class we can chain them together naturally or forced (more on that later)

--

--

Alfred Nutile

LaraLlama.io Open-Source Product Created Day to day developer using Laravel, PHP, Vue.js, Inertia