<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alfred Nutile</title>
    <description>The latest articles on DEV Community by Alfred Nutile (@alnutile).</description>
    <link>https://dev.to/alnutile</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F70060%2F92beb4c1-c526-4aac-930f-c9d68bcdcf0c.png</url>
      <title>DEV Community: Alfred Nutile</title>
      <link>https://dev.to/alnutile</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alnutile"/>
    <language>en</language>
    <item>
      <title>PHP and LLMs book - Local LLMs: Streamlining Your Development Workflow</title>
      <dc:creator>Alfred Nutile</dc:creator>
      <pubDate>Mon, 26 Aug 2024 15:08:38 +0000</pubDate>
      <link>https://dev.to/alnutile/php-and-llms-book-local-llms-streamlining-your-development-workflow-3d9i</link>
      <guid>https://dev.to/alnutile/php-and-llms-book-local-llms-streamlining-your-development-workflow-3d9i</guid>
      <description>&lt;p&gt;This is a chapter from the PHP and LLMs book you can buy at &lt;/p&gt;

&lt;p&gt;&lt;a href="https://bit.ly/php_llms" rel="noopener noreferrer"&gt;https://bit.ly/php_llms&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or get a sample at&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bit.ly/php_llms_sample" rel="noopener noreferrer"&gt;https://bit.ly/php_llms_sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Big Picture
&lt;/h2&gt;

&lt;p&gt;This chapter covers the basics of working with local Large Language Models (LLMs) in a PHP development environment. We'll go through setting up Ollama on your machine and creating a simple PHP client to interact with its API. The chapter demonstrates how to use both completion and chat modes, and includes a practical example of using LLMs for news summarization. Along the way, we'll cover important development practices like mocking API responses and writing effective tests. By the end of this chapter, you should have a foundational understanding of integrating local LLMs into your PHP applications, setting the stage for more advanced topics in later chapters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to set up a local LLM environment using Ollama&lt;/li&gt;
&lt;li&gt;The advantages and considerations of working with local LLMs&lt;/li&gt;
&lt;li&gt;How to create a basic PHP client to communicate with the Ollama API&lt;/li&gt;
&lt;li&gt;How to implement and test both completion and chat functionalities in your client&lt;/li&gt;
&lt;li&gt;The practical application of LLMs for news summarization and article filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up your local Computer
&lt;/h2&gt;

&lt;p&gt;We are going to use different LLMs and models, but to start this chapter I will show how to setup Ollama on your machine.&lt;br&gt;
Keep in mind that an M1 or M2 Mac will not be great speed wise and you can overwhelm these systems with too many requests, but we'll mock a lot of the requests we do so this will not be an issue overall. Later in the book, I will cover how to use Laravel queues to help manage that, and how our Mock Driver can mock results so you can work in the UI without being impacted by this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ollama
&lt;/h2&gt;

&lt;p&gt;This is the easy part! They have a nice download page: &lt;a href="http://ollama.com/" rel="noopener noreferrer"&gt;http://ollama.com/&lt;/a&gt;. Once installed, you'll have an icon in your tool bar (or taskbar on Windows) and you can basically "Restart" when needed since they do a ton of updates.&lt;/p&gt;

&lt;p&gt;After you've installed Ollama, we need to pull down two models to start this process.&lt;/p&gt;

&lt;p&gt;First let's use a small model called &lt;code&gt;phi3&lt;/code&gt; for results so we do not tax your machine too much.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some great models that are larger are &lt;code&gt;llama3.1&lt;/code&gt; and &lt;code&gt;mistral&lt;/code&gt; but we'll stick to the smaller model &lt;code&gt;phi3&lt;/code&gt; &lt;br&gt;
since it will take up less memory. Explore &lt;a href="https://ollama.com/library" rel="noopener noreferrer"&gt;https://ollama.com/library&lt;/a&gt; for more models.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull phi3 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are their docs &lt;a href="https://github.com/ollama/ollama/blob/main/docs/README.md" rel="noopener noreferrer"&gt;https://github.com/ollama/ollama/blob/main/docs/README.md&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;OK, so now you have a chat model. For simple chat at the commandline to start you can just open a terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run phi3:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/images%2Follama_run.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/images%2Follama_run.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty cool! But let's do more. Because you are running Ollama on your machine it is accessable via a port you can make http requests using the API &lt;a href="https://github.com/ollama/ollama/blob/main/docs/api.md" rel="noopener noreferrer"&gt;https://github.com/ollama/ollama/blob/main/docs/api.md&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi3:latest",
  "prompt": "What is PHP?"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Or without streaming.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/api/generate &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "model": "phi3:latest",
  "prompt": "What is PHP?",
  "stream": false //added
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In the pursuit of accurate and reliable results, our approach often employs a 'multi-shot' strategy. Think of it as refining your initial request through multiple interactions with the LLM, much like crafting a masterpiece through successive drafts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am going to default to non-streaming. I will show how to stream, but will mostly stick to non-streaming. This is because much of what I am building is not for chat, and, when it is for chat, I want the quality of the results to be multi-shot. Here is an explanation from &lt;/p&gt;

&lt;p&gt;Google Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In the pursuit of accurate and reliable results, our approach often employs a "multi-shot" strategy. Think of it as refining your initial request through multiple interactions with the LLM, much like crafting a masterpiece through successive drafts.

Each interaction serves as a "shot," where the LLM processes your prompt and delivers a response. We then evaluate this response and, if necessary, provide additional guidance or context in subsequent prompts, shaping the output toward the desired outcome. This iterative process allows us to fine-tune the results, ensuring they align with your intent and meet the highest standards of accuracy.

While this multi-shot approach may take slightly longer than a single prompt, the emphasis is on delivering the most precise and useful information, even if it means sacrificing a bit of speed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, back to the fun part! Let's make our first "Client" to talk to this API and return results. From there, we will build up to "abstract" it as a driver we can use on any LLM service.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP Client with Tests
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: there are libraries for these but I want you to see how easy it is, using HTTP, to talk to these APIs. This pays off later as we do things like Pool requests, or talk to APIs that have no library yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We will start with some simple code on this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;//app/Services/Ollama/Client.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services\Ollama&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://localhost:11434/api/generate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'phi3:latest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'prompt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"options"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"temperature"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'stream'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note on &lt;code&gt;temperature&lt;/code&gt; and the elephant in the room &lt;code&gt;hallucinations&lt;/code&gt;. We can prevent hallucinations almost 100%. And I am 100% confident of that from past experience. Preventing hallucinations or what I call &lt;code&gt;drifting&lt;/code&gt; depends on a few things like, temperature, context building, prompting skills, etc that I will go over in this book. So let us put that concern aside for a bit and I promise you by the end of this book you will see it more of a problem with the prompt or the context than with the LLM.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;So let us put that concern (hallucinations) aside for a bit and I promise you by the end of this book you will see it more of a problem with the prompt or the context than with the LLM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So here (and by default) I set the temperature to 0.1 or 0 in many cases, lowering the amount of "creativity" the LLM will reply with.&lt;/p&gt;

&lt;p&gt;Let's make a test. Below you will see the first run is there for us to get a &lt;code&gt;fixture&lt;/code&gt; from the actual repsonse, then we will update the test to then use the &lt;code&gt;fixture&lt;/code&gt; with our HTTP mock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'should return response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'What is PHP?'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;put_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'simple_ollama_client_results.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok I hate when people show a code example and introduce a custom something that then I am like "why?". But I just use &lt;br&gt;
this all the time and add it to my &lt;code&gt;app/helpers.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// app/helpers.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'put_fixture'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;put_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tests/fixtures'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;makeDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tests/fixtures'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'tests/fixtures/%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file_name&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nv"&gt;$content&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then register it in &lt;code&gt;composer.json&lt;/code&gt; here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"autoload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"psr-4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"App\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Database\\Factories\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database/factories/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Database\\Seeders\\"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"database/seeders/"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"app/helpers.php"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I know there is the &lt;a href="https://github.com/php-vcr/php-vcr" rel="noopener noreferrer"&gt;VCR library&lt;/a&gt; as well but I just wanted something more simple in my day to day workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With our initial run, we got a file put into this folder &lt;code&gt;tests/fixtures/simple_ollama_client_results.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And, in that file, we have the payload that it returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phi3:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-08-22T15:19:44.208546Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PHP, which stands for &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;PHP: Hypertext Preprocessor,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; is a widely-used open source server-side scripting language. It was originally designed in 1994 by Rasmus Lerdorf to manage the creation and maintenance of websites..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"done_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//lots&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;numbers&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6179024209&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"load_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7809625&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;760911000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;215&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5409058000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are some details in the above response we can talk about later, but for now we have the &lt;code&gt;context&lt;/code&gt;, and the &lt;code&gt;done_reason&lt;/code&gt;.  All these LLMs will have different stop reasons. As we work on abstracting them out we will define a consistent version of and all the results will conform so for example when we get to Tools, there will be a different stop reason for that.&lt;/p&gt;

&lt;p&gt;Now, let's update the test, moving the fixture we just generated in place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'should return response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'simple_ollama_client_results.json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nc"&gt;\Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'localhost:11434/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nc"&gt;\Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventStrayRequests&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'What is PHP?'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//put_fixture('simple_ollama_client_results.json', $response-&amp;gt;json()); removed&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;get_fixture&lt;/code&gt; method is another helper I use to get the fixture from the &lt;code&gt;tests/fixtures&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// app/helpers.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'get_fixture'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$decode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;base_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;'tests/fixtures/%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$file_name&lt;/span&gt;
        &lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$decode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now using the generated fixture we can fake the interaction. This is a good start as we flesh this out more in the &lt;em&gt;"Agnostic Driver Chapter"&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Chat Method
&lt;/h2&gt;

&lt;p&gt;You can read the Ollama docs &lt;a href="https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion" rel="noopener noreferrer"&gt;here&lt;/a&gt; about how chat works. Those docs will help us in this section since we want to have a threaded discussion, not just a prompt completion as seen in the previous section. &lt;/p&gt;

&lt;p&gt;We have our first PHP class that can ask the LLM a question. Let's make a new method for "chat". This allows us to add context to the question(s). Keep in mind for many one off integrations the &lt;code&gt;completion&lt;/code&gt; we did above is all you need. But you will see as we get into tools it becomes more of a thread. &lt;/p&gt;

&lt;p&gt;A thread in this case would look like this, simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Find me all the events on this page and create a list of events and save them to the system"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;thinking&amp;gt;I will use the event_tool and pass in the data I found on the page.&amp;lt;/thinking&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"event_create_tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;formmated&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I created the following events in your system..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is a good example of what comes out of a request to the LLM, in this case the LLM was asked a question by you, the &lt;code&gt;user&lt;/code&gt; role, then it started to build a chain of responses based on it needing to use tools to answer the question. We will show real results like this in later chapters.&lt;/p&gt;

&lt;p&gt;In the next section, we update the client for using chat over completion, then do our first use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update Client for Chat
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: the tests are simple, but you'll see how we build on the code with confidence and the tests over time.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;//app/Services/Ollama/Client.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://localhost:11434/api/chat'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'phi3:latest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'messages'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'stream'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"options"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"temperature"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'should return chat response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'What is PHP?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nf"&gt;put_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'simple_ollama_client_chat_results.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we take our fixture and use it, here is what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"phi3:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-08-22T17:11:40.160747Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PHP, which stands for Hypertext Preprocessor, is a widely-used open source server-side scripting language. It was originally created by Rasmus Lerdorf in 1994 and..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"done_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16670826417&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"load_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5122545583&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;84989000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eval_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;412&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eval_duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11462114000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use it for the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'should return chat response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'simple_ollama_client_chat_results.json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// added&lt;/span&gt;

    &lt;span class="nc"&gt;\Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="c1"&gt;// added&lt;/span&gt;
            &lt;span class="s1"&gt;'localhost:11434/*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// added&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// added&lt;/span&gt;

    &lt;span class="nc"&gt;\Illuminate\Support\Facades\Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preventStrayRequests&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'What is PHP?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;//put_fixture('simple_ollama_client_chat_results.json', $response-&amp;gt;json()); removed&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So just like before we send in the results, prove it works. Again this is going to change quite a bit in the Abstraction chapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Use Case- News Summary
&lt;/h2&gt;

&lt;p&gt;There is a feature in LaraLlama.io that allows you to search the web. I use that to search daily for news, and then send me a summary of the news for that day or week.&lt;/p&gt;

&lt;p&gt;There are three things I can use the LLM for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse the HTML to get the content about the article&lt;/li&gt;
&lt;li&gt;Reject the article if it is not about the subject I want, e.g., a bad search result.&lt;/li&gt;
&lt;li&gt;Then summarize all the articles that came in that day.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will talk about this more later on in the Prompt chapter but this is all about Prompt and Context. We are giving the LLM Context or Content to parse with the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;role&amp;gt;
You are extracting the content from the provided HTML or text that is related to technology news. 

&amp;lt;task&amp;gt;
First see if the article provided talks about technology news and if not just return bool false. Else pull out the content of the article as a Title, Summary, URL, and Content formatted as below.

&amp;lt;format&amp;gt;
Title:
Url:
Summary:
Content:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1 and 2 Filter non-related data and then extract data
&lt;/h3&gt;

&lt;p&gt;In the Prompting chapter, I'll show you how to test your prompt but for now we will go with the prompt above.&lt;/p&gt;

&lt;p&gt;Then we use our client in our new &lt;code&gt;NewsFeedParser&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;//app/Domains/NewsFeedParser.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Domains&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Facades\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsFeedParser&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;PROMPT
&amp;lt;role&amp;gt;
You are extracting the content from the provided HTML or text that is related to technology news. 

&amp;lt;task&amp;gt;
First see if the article provided talks about technology news and if not just return bool false. Else pull out the content of the article as a Title, Summary, URL, and Content formatted as below.

&amp;lt;format&amp;gt;
Title:
Url:
Summary:
Content:

&amp;lt;content&amp;gt;
$context
PROMPT;&lt;/span&gt;

        &lt;span class="cd"&gt;/**
         * @NOTE
         * This is going to change quite a bit
         * for now keeping it simple
         */&lt;/span&gt;
        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;data_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NOTE: I use Laravel's &lt;a href="https://laravel.com/docs/11.x/facades#real-time-facades" rel="noopener noreferrer"&gt;Real Time Facades&lt;/a&gt; - watch how this pays off in the next test.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first test to see it work will be for &lt;code&gt;false&lt;/code&gt; responses where the LLM weeds out search results that are not about the technology news we want, so below you'll see &lt;code&gt;put_fixture('news_feed_false.json', $results);&lt;/code&gt; where I get the results and save them for a mocked test after this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can return false'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'This is about how to make a hamburger'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Domains\NewsFeedParser&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;put_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_false.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then our mocked test to see it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can return false'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'This is about how to make a hamburger'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_false.json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//removed&lt;/span&gt;

    &lt;span class="nc"&gt;Facades\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'completion'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//removed&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//removed&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//removed&lt;/span&gt;

    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Domains\NewsFeedParser&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//put_fixture('news_feed_false.json', $results); removed&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NOTE: See how we mock now at the &lt;code&gt;Client&lt;/code&gt; level not at &lt;code&gt;Http&lt;/code&gt;. I typically test one class at a time, even in my Feature tests and mock the surrounding classes. You can easily remove this though and test them together. By the way, we'll pass Data objects around using the Spatie Data object which will be tested as well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OK, so we can handle &lt;code&gt;false&lt;/code&gt;. Now, let's do a good response example.&lt;/p&gt;

&lt;p&gt;First we make the test to generate the mock data. We will create a Laravel news article and save it to our &lt;code&gt;tests/fixtures&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--tests/fixtures/news_feed_good.html--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Laravel News&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Laravel 11 Released&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Laravel 11 has been released, and it's packed with new features and improvements. Here's what's new in this version:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;New Blade Directives&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Improved Form Validation&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Improved File Uploads&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we create a test to generate the mock data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can return an article summary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_good.html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Domains\NewsFeedParser&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another way we can create a fixture is to temporarily update the tested method to generate a fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// app/Domains/NewsFeedParser.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;put_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_good_response.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;data_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'response'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'false'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case my prompt gave me mixed results. This is a good example of how we could keep testing to get good results from our prompt, but we will save that for a later chapter 🙂&lt;/p&gt;

&lt;p&gt;Now that we have the fixture, we move it to our test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'can return an article summary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_good_response.json'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nc"&gt;Facades\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'completion'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//added&lt;/span&gt;

    &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'news_feed_good.html'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\App\Domains\NewsFeedParser&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;//put_fixture('news_feed_good_response.json', $results); removed&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it for our &lt;code&gt;NewsFeedParser&lt;/code&gt; - we now have the class that uses the LLM to verify the content is what we want, and then extract the content we want out of the HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 Summarize all the news
&lt;/h3&gt;

&lt;p&gt;Now that we have the news we want (in this example we can assume it was saved in a database for later use), let's do two examples of creating a "News Summary" experience, one with completion and one with chat. &lt;/p&gt;

&lt;p&gt;We will cover both, but I asked Ollama what is the advantage of one over the other, and I liked some of the information it offered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When using LLMs like Ollama, the choice between sending input as chat or completion depends on how you want to provide context and what type of response you expect.

Chat:

In chat mode, you're essentially having a conversation with the model. You send a message, and the model responds. Then, you can send another message building upon the previous one, and so on. This is useful when:

You want to provide context incrementally, like in a conversation.
You need the model to maintain state or recall previous messages.
In your example, sending 5 news articles as separate chat messages would allow the model to process each article individually, taking into account any relationships between them. This might be useful if you want the model to:

Identify common themes or trends across multiple articles
Recognize patterns or connections between different articles
Completion:

In completion mode, you're providing a prompt and expecting a response that completes or generates text based on that input. You can send a single prompt with multiple components (like your 5 news articles) as long as they're concatenated into a single string.

When sending multiple inputs as a single completion prompt, the model will process them as a whole, without maintaining state between individual messages. This is useful when:

You want to provide a fixed amount of context upfront.
The relationships between different components are not crucial for the response.
In your example, sending all 5 news articles and the summary prompt in a single completion request would allow the model to process them as a whole, generating a summary that takes into account all the articles.

Context window:

When using chat, you get more control over the context window, as each message builds upon previous ones. The model maintains state and can recall previous messages, allowing for more nuanced responses.

In completion mode, the context window is fixed to the input prompt itself. While the model can still recognize patterns or relationships within that prompt, it won't maintain state across individual components like it would in a chat conversation.

So, if you want to provide more context and control over how the model processes your inputs, use chat. If you're comfortable providing all relevant information upfront and don't need incremental processing, completion might be the better choice.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Completion Example
&lt;/h4&gt;

&lt;p&gt;In this example we get all our articles for this summary, it could be 1, it could be 10. The concern here is that there is too much content and we break the "context window". This limit will increase over time, like with Google Gemini it is a 2 million tokens window!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the Terminology chapter. But the TLDR is this: tokens represent the space your question takes up, and we need to leave room for the reply of the LLM. Combined, this forms your context window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this case, we build up our messages thread from a query to the database to generate "context" for the threaded prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;//app/Domains/News/NewsDigestCompletion.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Domains\News&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\News&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Facades\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsDigestCompletion&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Carbon&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Carbon&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;News&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'News: Title: %s Content: %s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;PROMPT
&amp;lt;role&amp;gt;
You are my news digest assistant
&amp;lt;task&amp;gt;
Take the news articles from the &amp;lt;content&amp;gt; section below and create a TLDR followed by a title and summary of each one
If not news is passed in the just say "No News in this

&amp;lt;content&amp;gt;
{ $messages }
PROMPT;&lt;/span&gt;

        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;data_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'No Results'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we get all the news, mash it into a big text blob (the LLM can do some interesting things to understand the text - it does not need to be perfectly formatted), then we add it to our prompt.&lt;/p&gt;

&lt;p&gt;From here, we get our results and give them back to the Class making the request.&lt;/p&gt;

&lt;h4&gt;
  
  
  Chat Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;//app/Domains/News/NewsDigest.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Domains\News&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\News&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Facades\App\Services\Ollama\Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NewsDigest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Carbon&lt;/span&gt; &lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Carbon&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;News&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;whereBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$end&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'News:  Title: %s Content: %s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$news&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;
                &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;PROMPT
&amp;lt;role&amp;gt;
You are my news digest assistant
&amp;lt;task&amp;gt;
Take the news articles from this thread and create a TLDR followed by a title and summary of each one
If not news is passed in the just say "No News in this thread"
PROMPT;&lt;/span&gt;

        &lt;span class="nv"&gt;$messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'content'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$prompt&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;data_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'No Results'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see above we are sending the array of news articles with our prompt as part of the message thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: We are taking this in small steps. Later, we will get into Tools and how to make a prompt like this below just work:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;role&amp;gt;
You are my assistant helping with news and events
&amp;lt;task&amp;gt;
Get all the news articles from this week and make a digest of them an email them to info@dailyai.studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can see three "Tools" in that prompt, &lt;code&gt;news_fetch&lt;/code&gt;, &lt;code&gt;news_digest&lt;/code&gt;, and &lt;code&gt;send_email&lt;/code&gt;, that will "just work" by the time we are at the end of this book. The LLM will look at the prompt, the tools you tell it the system has, and know which ones to use and in what order. As for the news feature, this can all be wrapped up into a scheduler to search the web daily and then send off a digest. And once the Tools are part of the natural language of the prompt, a user can easily create these complex workflows.&lt;/p&gt;

&lt;p&gt;More on this in the "Tools" chapter!&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The core concepts and benefits of utilizing local LLMs&lt;/li&gt;
&lt;li&gt;How to install and configure Ollama on your local machine&lt;/li&gt;
&lt;li&gt;The basics of interacting with the Ollama API using cURL&lt;/li&gt;
&lt;li&gt;The importance of considering context window limitations&lt;/li&gt;
&lt;li&gt;How to build a simple PHP client to communicate with Ollama&lt;/li&gt;
&lt;li&gt;How to mock API responses for efficient testing&lt;/li&gt;
&lt;li&gt;A practical example of using LLMs for news summarization&lt;/li&gt;
&lt;li&gt;The strategic use of both completion and chat modes for different LLM interactions&lt;/li&gt;
&lt;li&gt;A preview of the exciting possibilities that await as we delve deeper into LLMs&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>llm</category>
    </item>
    <item>
      <title>Mapping LLM Integration Levels to Levels of Autonomous Driving, it does not have to be all or nothing!</title>
      <dc:creator>Alfred Nutile</dc:creator>
      <pubDate>Tue, 02 Jul 2024 02:15:34 +0000</pubDate>
      <link>https://dev.to/alnutile/mapping-llm-integration-levels-to-levels-of-autonomous-driving-it-does-not-have-to-be-all-or-nothing-2m0j</link>
      <guid>https://dev.to/alnutile/mapping-llm-integration-levels-to-levels-of-autonomous-driving-it-does-not-have-to-be-all-or-nothing-2m0j</guid>
      <description>&lt;p&gt;I thought it would be interesting to compare LLM integration into your day to day business workflows to Levels of Automobile Automation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BTW Follow me on YouTube &lt;a href="https://www.youtube.com/@alfrednutile" rel="noopener noreferrer"&gt;https://www.youtube.com/@alfrednutile&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are the levels of Automobile Automation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Level 0 - No Automation: The driver performs all driving tasks.&lt;/li&gt;
&lt;li&gt;Level 1 - Driver Assistance: The vehicle can assist with either steering or acceleration/deceleration, but not both simultaneously.&lt;/li&gt;
&lt;li&gt;Level 2 - Partial Automation: The vehicle can control both steering and acceleration/deceleration in some situations, but the driver must remain engaged.&lt;/li&gt;
&lt;li&gt;Level 3 - Conditional Automation: The vehicle can handle all aspects of driving in certain conditions, but the driver must be ready to take control when prompted.&lt;/li&gt;
&lt;li&gt;Level 4 - High Automation: The vehicle can handle all driving tasks in specific circumstances without human intervention.&lt;/li&gt;
&lt;li&gt;Level 5 - Full Automation: The vehicle can perform all driving tasks under all conditions, with no human intervention needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now there seems to be a lot of “all or nothing” mindset when it comes to working with LLMs and I think each tasks you face day to day might benefit differently or not at all from where this tech is right now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The challenge—and opportunity—lies in identifying which of your daily tasks could benefit from LLM assistance and at what level&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Level 0
&lt;/h2&gt;

&lt;p&gt;Sometimes this is it! You might try and use it to write cover letters for job postings only to find out that Grammarly was enough. But you tried. You pasted a few bits of your writing style into a chat window, pasted the job posting and said “Write me a cover letter in my voice for this job” and what came out was embarrassing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Fmean-girls-embarrassed.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Fmean-girls-embarrassed.gif" alt="Embarrassed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great stop right there for that task but keep an open mind to other tasks where LLMs might be more helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1
&lt;/h2&gt;

&lt;p&gt;One example of this level we can think about it and me writing this article then giving I write to the LLM to review. I can give it some examples of my voice and then ask it to just update any places it could use more “meat on the bones” etc. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Ftasting-the-beef-matty-matheson.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Ftasting-the-beef-matty-matheson.gif" alt="Meat on the Bones"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I might not like what it outputs or maybe I do. Maybe I use it as is or maybe I compare the two and just move some bits over. But I might end up with something a little better than what Grammarly could have done. Using examples of my tone it does a good job of not sounding like an alien from another planet trying to sound human and professional. &lt;/p&gt;

&lt;h2&gt;
  
  
  Level 2
&lt;/h2&gt;

&lt;p&gt;Let’s take the above and take it up a notch. Let’s say I have a dozen articles in my &lt;a href="http://www.larallama.io" rel="noopener noreferrer"&gt;www.larallama.io&lt;/a&gt; collection that I wrote, and then I start a chat with a bullet list of points I want to cover in a blog post and I tell it have at it!&lt;/p&gt;

&lt;p&gt;Maybe just maybe it can pull it off. Maybe it can do some great tweets or LinkedIn “tweets”? &lt;/p&gt;

&lt;p&gt;Anyways maybe it keeps the tone, or ten prompts later it pulls it off. That is great - it took the bones of my story and really fleshed it out for me, potentially saving time and enhancing the quality of the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 3
&lt;/h2&gt;

&lt;p&gt;Ok like above we are building confidence and we even setup a system like &lt;a href="http://www.larallama.io" rel="noopener noreferrer"&gt;www.larallama.io&lt;/a&gt; to collect data from the week. News about Laravel, news about RAG systems, news about LaraLlama.io itself. And then I tell it “Every Friday Write a News Letter and Email me a copy”&lt;/p&gt;

&lt;p&gt;Great, but then I check it and if it looks good I post it to the mailing list using [INSERT YOUR MAILING LIST SERVICE HERE]&lt;/p&gt;

&lt;p&gt;And that is great, I have a moment to check it, even tell it to try again etc. I am not 100% trusting it yet but getting closer. &lt;/p&gt;

&lt;p&gt;This level of automation can significantly reduce your workload while maintaining quality control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 4
&lt;/h2&gt;

&lt;p&gt;Then comes Level 4! Level 3 proves itself week after week. Readers are writing back how impressed they are! They are even sending emails to LaraLlama that become suggestions for next articles and building reports on customer feedback. &lt;br&gt;
It just does not end!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Fwe-cant-stop-it-power-rangers-zeo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsundance-software.nyc3.digitaloceanspaces.com%2Flarallama%2Fwe-cant-stop-it-power-rangers-zeo.gif" alt="Can not Stop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we turn on FULL AUTOMATION!!! The system now sends the emails every Friday without my proofing, based on the consistent quality it has demonstrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 5
&lt;/h2&gt;

&lt;p&gt;Ok my example ended above this one just goes over the top. Let’s list out what just happened above in Four to see how crazy this could get then wrap this up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One, the system is sending emails every Friday about news of the week.&lt;/li&gt;
&lt;li&gt;Two, the system is reading customer feedback to adjust each week to that feedback and preventing the infamous trolling of the system that would lead it to writing about some obscure topic.&lt;/li&gt;
&lt;li&gt;Three, the system is also taking suggesting based on the stats of the articles read!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of automation represents a significant leap in AI-assisted content creation and management, though it's important to note that human oversight may still be necessary in some capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it Up
&lt;/h2&gt;

&lt;p&gt;And that's how far we can potentially go with automation for our different day to day work tasks. However, it's important to remember that not all business processes are ready for any level of AI integration. For instance, tasks like helping a customer get a quote on a new web feature still require a human touch (though this might change in a few years). Similarly, creating engaging videos about innovative ideas for LaraLlama.io remains a primarily human-driven process. (For now 🤔)&lt;/p&gt;

&lt;p&gt;The key takeaway is that LLM integration isn't an all-or-nothing proposition. There's a wide spectrum of possibilities between no automation and full automation. The challenge—and opportunity—lies in identifying which of your daily tasks could benefit from LLM assistance and at what level. Some processes might be ready for Level 4 or 5 automation, while others might be best served by Level 1 or 2 support.&lt;/p&gt;

&lt;p&gt;As we move forward, it's crucial to approach LLM integration strategically. Start small, experiment with different levels of automation for various tasks, and gradually increase the AI's role as you build confidence in its capabilities. &lt;/p&gt;

&lt;p&gt;By understanding these different levels of LLM integration, you can make informed decisions about how to leverage this powerful technology in your day-to-day business workflows, potentially boosting productivity and innovation in ways we're only beginning to explore. (That is such an LLM worded sentence 🤦‍♂️)&lt;/p&gt;

</description>
      <category>llm</category>
      <category>larallama</category>
      <category>rag</category>
    </item>
    <item>
      <title>Laravel RAG System in 4 Steps!</title>
      <dc:creator>Alfred Nutile</dc:creator>
      <pubDate>Tue, 25 Jun 2024 11:35:38 +0000</pubDate>
      <link>https://dev.to/alnutile/laravel-rag-system-in-4-steps-599</link>
      <guid>https://dev.to/alnutile/laravel-rag-system-in-4-steps-599</guid>
      <description>&lt;h1&gt;
  
  
  Laravel RAG System in 4 Steps!
&lt;/h1&gt;

&lt;p&gt;This post will show how easy it is to get going with Laravel, Vectorized data and LLM chat. It can be the foundation to a RAG system. There are links to the code and more in this article &lt;a href="https://dev.to/alnutile/laravel-rag-system-in-4-steps-2jc"&gt;Original Article&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;📺 YouTube Channel - &lt;a href="https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9"&gt;https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📖 The Docs - &lt;a href="https://docs.larallama.io/"&gt;https://docs.larallama.io/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;🚀 The Site - &lt;a href="https://www.larallama.io"&gt;https://www.larallama.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🫶🏻 &lt;a href="https://patreon.com/larallama"&gt;https://patreon.com/larallama&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🐦 &lt;a href="https://x.com/alnutile"&gt;https://x.com/alnutile&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧑🏻‍💻 The Code - &lt;a href="https://github.com/LlmLaraHub/laralamma"&gt;https://github.com/LlmLaraHub/laralamma&lt;/a&gt; &lt;br&gt;
n&lt;br&gt;
  📰 The NewsLetter - &lt;a href="https://sundance-solutions.mailcoach.app/larallama-app"&gt;https://sundance-solutions.mailcoach.app/larallama-app&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;🖊️ Medium - &lt;a href="https://medium.com/@alnutile"&gt;https://medium.com/@alnutile&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;🤝🏻 LinkedIn - &lt;a href="https://www.linkedin.com/in/alfrednutile/"&gt;https://www.linkedin.com/in/alfrednutile/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📺 YouTube Playlist - &lt;a href="https://www.youtube.com/watch?v=KM7AyRHx0jQ&amp;amp;list=PLL8JVuiFkO9I1pGpOfrl-A8-09xut-fDq"&gt;https://www.youtube.com/watch?v=KM7AyRHx0jQ&amp;amp;list=PLL8JVuiFkO9I1pGpOfrl-A8-09xut-fDq&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💬 Discussions - &lt;a href="https://github.com/orgs/LlmLaraHub/discussions"&gt;https://github.com/orgs/LlmLaraHub/discussions&lt;/a&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>llm</category>
      <category>ollama</category>
      <category>rag</category>
    </item>
    <item>
      <title>Laravel RAG System in 4 Steps!</title>
      <dc:creator>Alfred Nutile</dc:creator>
      <pubDate>Mon, 24 Jun 2024 21:38:00 +0000</pubDate>
      <link>https://dev.to/alnutile/laravel-rag-system-in-4-steps-2jc</link>
      <guid>https://dev.to/alnutile/laravel-rag-system-in-4-steps-2jc</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwz4040mq2bv03mnhfs2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwz4040mq2bv03mnhfs2.png" alt="Welcome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make sure to follow me on &lt;a href="https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post will show how easy it is to get going with Laravel, Vectorized data and LLM chat. It can be the foundation to a RAG system. There are links to the code and more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Retrieval augmented generation system — an architectural approach that can improve the efficacy of large language model (LLM) applications&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Repo is &lt;a href="https://github.com/alnutile/laravelrag/tree/main" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The main branch will get you going on a fresh install of Laravel. If you copy the .env.example to .env you can get started just follow along.&lt;/p&gt;

&lt;p&gt;Follow Along or Watch the Video COMING SOON!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 Setting up Vector
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: Each pull do composer install. If that is not enough run composer dump&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can see the Branch here &lt;a href="https://github.com/alnutile/laravelrag/tree/vector_setup" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag/tree/vector_setup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have Laravel Setup using HERD, DBEngine, or the PostGres app. Then using TablePlus or Command Line or whatever make the database in this example “laravelrag”&lt;/p&gt;

&lt;p&gt;Now we want to are going to install this library which will setup the Vector Extension for us.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

composer require pgvector/pgvector
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pgvector-migrations"&lt;/span&gt;
php artisan migrate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 2 Now for the Model
&lt;/h2&gt;

&lt;p&gt;The Branch is &lt;a href="https://github.com/alnutile/laravelrag/tree/model_setup" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag/tree/model_setup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will keep it simple here and have a model named Chunk&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin98nsnwd3zw0hgt41db.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fin98nsnwd3zw0hgt41db.png" alt="Chunk"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will be where we store “chunks” of a document. In our example we will chunk up a long Text document so we can keep it simple for now. But in the end all things become text! PDF, PPT, Docx etc.&lt;/p&gt;

&lt;p&gt;You will see in the code it is not about pages as much as chunks that are x size with x overlap of content.&lt;/p&gt;

&lt;p&gt;In this code we will default to 600 characters chunks with an bit of an overlap you can see the code &lt;a href="https://github.com/alnutile/laravelrag/blob/chat_with_data/app/Domains/Chunking/TextChunker.php" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 Add the LLM Drivers
&lt;/h2&gt;

&lt;p&gt;Repo Link is &lt;a href="https://github.com/alnutile/laravelrag/tree/llm_driver" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag/tree/llm_driver&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: Each pull do composer install. If that is not enough run composer dump&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We bring in my LLMDriver folder which is not an official package (sorry just too lazy) and then some other libraries&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

composer require spatie/laravel-data laravel/pennant


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I am going to use my LLM Driver for this and then plug in Ollama and later Claude.&lt;/p&gt;

&lt;p&gt;But first get Ollama going on your machine read about it here.&lt;/p&gt;

&lt;p&gt;We are going to pull llama3 and mxbai-embed-large (for embedding data)&lt;/p&gt;

&lt;p&gt;Or just use your API creds for OpenAi it should make sense when you see the code and the config file “config/llmdriver.php”&lt;/p&gt;

&lt;p&gt;Just set the Key value in your .env or checkout &lt;code&gt;config/llmdriver.php&lt;/code&gt; for more options.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

LLM_DRIVER=ollama


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let’s open TinkerWell (I want to avoid coding a UI so we can focus on the concept more) &lt;a href="https://tinkerwell.app/" rel="noopener noreferrer"&gt;https://tinkerwell.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Load up the Provider &lt;code&gt;bootstrap/providers.php&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;App\Providers\AppServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;\App\Services\LlmServices\LlmServiceProvider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Ok so we see it is working now lets chunk a document.&lt;/p&gt;

&lt;p&gt;NOTE: This ideally all would run in Horizon or Queue jobs to deal with a ton of details like timeouts and more. We will see what happens if we just go at it this way for this demo.&lt;/p&gt;

&lt;p&gt;Also keep an eye on the tests folders I have some “good” examples on how to test your LLM centric applications like &lt;code&gt;tests/Feature/ChunkTextTest.php&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ok now we run the command to Embed the data&lt;/p&gt;

&lt;p&gt;![Embed Files](&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jdpzg4c85hoys28cul5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jdpzg4c85hoys28cul5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now we have a ton of chunked data!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp8fadvpdtrcahgxsrqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyp8fadvpdtrcahgxsrqn.png" alt="Database"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The columns are for the different size embeddings depending on the embed models you are using. I got some feedback here and went the route you see above.&lt;/p&gt;

&lt;p&gt;Now lets chat with the data!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 Chatting with your Data
&lt;/h2&gt;

&lt;p&gt;Ok we want the user to ask the LLM a question, but the LLM needs “context” and a Prompt that reduces drift, that is when an LLM makes up answers. I have seen it reduced to 100% in these systems.&lt;/p&gt;

&lt;p&gt;First let’s vectorize the input so we can search for related data.&lt;/p&gt;

&lt;p&gt;Since we embedded the data the question then gets embedded or vectorized to then use it to do the search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifg525rl1aw6hep11wny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifg525rl1aw6hep11wny.png" alt="Prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we take the text question and pass it to the embed api (Ollama and OpenAi offer this)&lt;/p&gt;

&lt;p&gt;Here is the code so you can see how simple it really is with HTTP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnht6gpodygvxj5hjr8w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnht6gpodygvxj5hjr8w.png" alt="Embed Data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You will see I use Laravel Data from Spatie so not matter the LLM service it is always the same type of date in and out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we use the Distance query to do a few things lets break it down&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohv2p45yvxuvbxnopkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ohv2p45yvxuvbxnopkp.png" alt="Distance Query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We take the results of the embedData gives us and pass it into the query using Vector to format it Pgvector\Laravel\Vector library:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Pgvector\Laravel\Vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we use that in the distance query&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr459w7zaean8g4jm4m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxr459w7zaean8g4jm4m4.png" alt="Distance2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used Cosine since I feel the results had been a bit better. Why? I did a bit of ChatGPT work to decide which one and why. Here are some results:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The order of effectiveness for similarity metrics can vary depending on the nature of the data and the specific use case. However, here’s a general guideline based on common scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cosine Similarity&lt;/strong&gt;: Cosine similarity is often considered one of the most effective metrics for measuring similarity between documents, especially when dealing with high-dimensional data like text documents. It’s robust to differences in document length and is effective at capturing semantic similarity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inner Product&lt;/strong&gt;: Inner product similarity is another metric that can be effective, particularly for certain types of data. It measures the alignment between vectors, which can be useful in contexts where the direction of the vectors is important.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L2 (Euclidean) Distance&lt;/strong&gt;: L2 distance is a straightforward metric that measures the straight-line distance between vectors. While it’s commonly used and easy to understand, it may not always be the most effective for capturing complex relationships between documents, especially in high-dimensional spaces.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In summary, the order of effectiveness is typically Cosine Similarity &amp;gt; Inner Product &amp;gt; L2 Distance. However, it’s important to consider the specific characteristics of your data and experiment with different metrics to determine which one works best for your particular application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok back to the example. So now we have our question vectorized and we have search results. The code also takes a moment to knit back the chunks with siblings so instead of getting just the chunk we get the chunk before and after. &lt;a href="https://github.com/alnutile/laravelrag/blob/chat_with_data/app/Services/LlmServices/DistanceQuery.php#L33" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag/blob/chat_with_data/app/Services/LlmServices/DistanceQuery.php#L33&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the results we are going to build a prompt. This is tricky since it takes time to get it right so you might want to pull it into ChatGPT or Ollama and mess around a bit. The key here is setting the temperature to 0 to keep the system from drifting. That is not easy yet in Ollama &lt;a href="https://github.com/ollama/ollama/issues/2505" rel="noopener noreferrer"&gt;https://github.com/ollama/ollama/issues/2505&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;![results(&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oulye3gg5rtfbh13jzap.png" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oulye3gg5rtfbh13jzap.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Ok let’s break this down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Item 1 the Prompt
&lt;/h3&gt;

&lt;p&gt;Here we define a Role, Task and Format (JSON, Markdown, Table etc) checkout &lt;a href="https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/" rel="noopener noreferrer"&gt;https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/&lt;/a&gt; for some tips!&lt;br&gt;
Item 2 the Input&lt;/p&gt;

&lt;p&gt;Here we pass the original text question to help the LLM understand the users request&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Garbage in Garbage Out&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Item 3 the Context
&lt;/h3&gt;

&lt;p&gt;Context, this is key. “Garbage in Garbage Out” is still the rule here as well. Put good data into your RAG system. In this example I imported some Laravel docs. But this is the data from the Distance Query!&lt;/p&gt;

&lt;h3&gt;
  
  
  Item 4 LLM
&lt;/h3&gt;

&lt;p&gt;We are just doing a completion api here. This is not a “Chat” which is an array of questions and answers but that would work as well. This is just the Prompt we built passed in. I am using Claude driver to show how easily we can switch systems. Also I feel like Ollama, unless you set the temperature, is a bit trickier right now to keep on track. And Claude is FAST!&lt;br&gt;
Item 5 The Answer&lt;/p&gt;

&lt;p&gt;As seen above you can get a sense of the answer but just want to share that sometimes the LLM will point out (see below) that it does not have enough data to answer your question.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhblwqg8timxyc16b80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhblwqg8timxyc16b80.png" alt="I removed a #4 from a previous edit but this is the first answer until I cleaned up some data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is another example I like to share with people&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihgprbao1f37pvkcb24e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihgprbao1f37pvkcb24e.png" alt="It will not hallucinate!"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just more “evidence” of what a good RAG system can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;That is really how “easy” it is to get a RAG system going. LaraLlama.io has a ton more details that you can see but this is a very simple code base I share in this article.&lt;/p&gt;

&lt;p&gt;The next post will be tools/functions, extending this code further. But there are so many ways to use this in applications, I list a bunch of use cases here &lt;a href="https://docs.larallama.io/use-cases.html" rel="noopener noreferrer"&gt;https://docs.larallama.io/use-cases.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is all here &lt;a href="https://github.com/alnutile/laravelrag" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag&lt;/a&gt; you can work through the branches with the last one being &lt;a href="https://github.com/alnutile/laravelrag/tree/chat_with_data" rel="noopener noreferrer"&gt;https://github.com/alnutile/laravelrag/tree/chat_with_data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to follow me on YouTube &lt;a href="https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9" rel="noopener noreferrer"&gt;https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And the list below or more ways to stay in touch!
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;📺 YouTube Channel — &lt;a href="https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9" rel="noopener noreferrer"&gt;https://youtube.com/@alfrednutile?si=M6jhYvFWK1YI1hK9&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📖 The Docs — &lt;a href="https://docs.larallama.io/" rel="noopener noreferrer"&gt;https://docs.larallama.io/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚀 The Site — &lt;a href="https://www.larallama.io" rel="noopener noreferrer"&gt;https://www.larallama.io&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🫶🏻 &lt;a href="https://patreon.com/larallama" rel="noopener noreferrer"&gt;https://patreon.com/larallama&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧑🏻‍💻 The Code — &lt;a href="https://github.com/LlmLaraHub/laralamma" rel="noopener noreferrer"&gt;https://github.com/LlmLaraHub/laralamma&lt;/a&gt;&lt;br&gt;
n&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📰 The NewsLetter — &lt;a href="https://sundance-solutions.mailcoach.app/larallama-app" rel="noopener noreferrer"&gt;https://sundance-solutions.mailcoach.app/larallama-app&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🖊️ Medium — &lt;a href="https://medium.com/@alnutile" rel="noopener noreferrer"&gt;https://medium.com/@alnutile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🤝🏻 LinkedIn — &lt;a href="https://www.linkedin.com/in/alfrednutile/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/alfrednutile/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📺 YouTube Playlist — &lt;a href="https://www.youtube.com/watch?v=KM7AyRHx0jQ&amp;amp;list=PLL8JVuiFkO9I1pGpOfrl-A8-09xut-fDq" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=KM7AyRHx0jQ&amp;amp;list=PLL8JVuiFkO9I1pGpOfrl-A8-09xut-fDq&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💬 Discussions — &lt;a href="https://github.com/orgs/LlmLaraHub/discussions" rel="noopener noreferrer"&gt;https://github.com/orgs/LlmLaraHub/discussions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravel</category>
      <category>llm</category>
      <category>ollama</category>
      <category>rag</category>
    </item>
    <item>
      <title>Past Year and a Half of Tech Used and Loved and Stuff Built</title>
      <dc:creator>Alfred Nutile</dc:creator>
      <pubDate>Mon, 18 Sep 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/alnutile/past-year-and-a-half-of-tech-used-and-loved-and-stuff-built-5hid</link>
      <guid>https://dev.to/alnutile/past-year-and-a-half-of-tech-used-and-loved-and-stuff-built-5hid</guid>
      <description>&lt;p&gt;tldr: this is about my past year a half becoming a full time developer again and the different technologies I used and experience with them. I will share the tech I like the most still and the projects I used them on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Was updating my resume and my list of experience and just wanted to share here.  I will write more on the topic later about the "Developer to Director back to Developer" path since I think the first part of this "growth" happens often and not always for the right reasons as seen in the "Peter Principle"&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Peter Principle states that, if you perform well in your job, you will likely be promoted to the next level of your organization's hierarchy. You will continue to rise up the ladder until you reach the point where you can no longer perform well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Anyways more on that later for now a look at my time since April 2022 and becoming a full time Developer again. And now as I look back I also want to share the tech that has resonated the most with my workflow and thinking. Btw some of that time was spent at a full time job building a product for them so I did "lose" some time to that but I did learn a ton at that one both about tech and myself.&lt;/p&gt;

&lt;p&gt;All these stacks use Github Actions for CI/CD &lt;a href="https://medium.com/@alnutile/ci-github-actions-and-laravel-part-1-of-3-547d5515b116"&gt;https://medium.com/@alnutile/ci-github-actions-and-laravel-part-1-of-3-547d5515b116&lt;/a&gt; honestly it is just one file and done. And &lt;a href="https://envoyer.io"&gt;https://envoyer.io&lt;/a&gt; to deploy with "zero" downtime. And then there is &lt;a href="https://jetstream.laravel.com/introduction.html"&gt;https://jetstream.laravel.com/introduction.html&lt;/a&gt; providing not only the foundation but some great starter components and patterns for Vue and Intertia. &lt;/p&gt;

&lt;h2&gt;
  
  
  2022-2023 Tech Stack Winners for me 🏆
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Inertia &lt;a href="https://inertiajs.com/"&gt;https://inertiajs.com/&lt;/a&gt; the form api makes life sooooo easy&lt;/li&gt;
&lt;li&gt;Vite &lt;a href="https://vitejs.dev/"&gt;https://vitejs.dev/&lt;/a&gt; what a game changer for me and the dev workflow&lt;/li&gt;
&lt;li&gt;Forge + DigitalOcean &lt;a href="https://forge.laravel.com/"&gt;https://forge.laravel.com/&lt;/a&gt; gets the Ops out of the way so I can focus on the Dev&lt;/li&gt;
&lt;li&gt;Meilisearch &lt;a href="https://www.meilisearch.com/"&gt;https://www.meilisearch.com/&lt;/a&gt; wow saved my 🥓 on a large database &lt;/li&gt;
&lt;li&gt;Tailwind Yup Shane &lt;a href="https://www.linkedin.com/in/shane-rosenthal/"&gt;https://www.linkedin.com/in/shane-rosenthal/&lt;/a&gt; was right about me going with this over Bootstrap never looked back&lt;/li&gt;
&lt;li&gt;JetStream &lt;a href="https://jetstream.laravel.com/introduction.html"&gt;https://jetstream.laravel.com/introduction.html&lt;/a&gt; just what I need to get started on any project&lt;/li&gt;
&lt;li&gt;OpenAi PHP &lt;a href="https://github.com/openai-php"&gt;https://github.com/openai-php&lt;/a&gt; my TODO list for 2023-2024 is to make a "Storage" like driver so we can use any LLM but for now this is a great option!&lt;/li&gt;
&lt;li&gt;RapidAPI &lt;a href="https://rapidapi.com/hub"&gt;https://rapidapi.com/hub&lt;/a&gt; getting going quickly with some APIs like LinkedIn or Twitter and more like text to audio! Even a great webscrapper.&lt;/li&gt;
&lt;li&gt;PHPVector &lt;a href="https://github.com/pgvector/pgvector"&gt;https://github.com/pgvector/pgvector&lt;/a&gt; wow so nice I could just enter this new LLM phase&lt;/li&gt;
&lt;li&gt;Miro &lt;a href="https://miro.com"&gt;https://miro.com&lt;/a&gt; remote whiteboard &lt;/li&gt;
&lt;li&gt;Balsamiq &lt;a href="https://balsamiq.com/"&gt;https://balsamiq.com/&lt;/a&gt; the downloaded version to keep MVP goals clear and simple.&lt;/li&gt;
&lt;li&gt;Envoy.io &lt;a href="https://envoyer.io"&gt;https://envoyer.io&lt;/a&gt; just works!&lt;/li&gt;
&lt;li&gt;Horizon all things &lt;a href="https://laravel.com/docs/10.x/horizon"&gt;https://laravel.com/docs/10.x/horizon&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2022-2023 Tech I appreciate but not using ❤️
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Livewire, &lt;a href="https://laravel-livewire.com/"&gt;https://laravel-livewire.com/&lt;/a&gt; nice to have this option great work&lt;/li&gt;
&lt;li&gt;Vapor &lt;a href="https://vapor.laravel.com/"&gt;https://vapor.laravel.com/&lt;/a&gt; so glad this exists especially when/if I get a client that needs this level of scaling.&lt;/li&gt;
&lt;li&gt;Nova &lt;a href="https://nova.laravel.com/"&gt;https://nova.laravel.com/&lt;/a&gt; glad it is here &lt;/li&gt;
&lt;li&gt;Spark &lt;a href="https://spark.laravel.com/"&gt;https://spark.laravel.com/&lt;/a&gt; glad it is here as well just a bit too "simplistic" for most of my customers so far but still looking for a place for it.&lt;/li&gt;
&lt;li&gt;Statamic &lt;a href="https://statamic.com/"&gt;https://statamic.com/&lt;/a&gt; very glad they are doing the work just did not fit my style. Great editor. &lt;/li&gt;
&lt;li&gt;PEST &lt;a href="https://pestphp.com/"&gt;https://pestphp.com/&lt;/a&gt; glad this is here to help push our thinking about testing more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2023-2024 Tech I want to try 🚀
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Filament &lt;a href="https://filamentphp.com/"&gt;https://filamentphp.com/&lt;/a&gt; soon. I have so many Inertia Components to use at this point but this looks sooooo nice&lt;/li&gt;
&lt;li&gt;Alternative to Pusher.com though it has been the most affordable reliable service for the past 10 years it would be nice to use the newer free options the Laravel  Docs s talk about.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built internal system using People Data Labs
&lt;/h2&gt;

&lt;p&gt;System using People Data Labs to more effectively use that API to build reports as needed. (top secret stuff)&lt;br&gt;
URL Is private&lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Livewire 2, Tailwind, Inertia, Meilisearch&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Wow my first use of Livewire and Tailwind! Love Tailwind now but at first I did tooooo much adding of classes to get it "right" and not enough removing. As far as Livewire it just did not work with my brain. I like how 3 works since it has a Vue like Component feel.  Later on I moved new features to Inertia and it is going well. As far as Meilisearch wow! Using Forge I set it up with one click and the 3 million row database is just sooooooo fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gallery Anywhere
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://galleryanywhere.com/"&gt;https://galleryanywhere.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Personal idea to have local art in AirBnb's did not go anywhere 🤷🏻‍♂️&lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Laravel, Nova, Tailwind, Inertia&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Fell in love with Inertia and Vue just was more inline with what I was use to. Nova was fine, but for me it took some of the fun out of Laravel.  &lt;/p&gt;

&lt;h2&gt;
  
  
  RecallsNow (use to be TotalRecalls)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://recallsnow.com/"&gt;https://recallsnow.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fun project and "scratch my own itch". Lots of background api stuff and web scraping. &lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Laravel, OpenAi API for Tagging, Tailwind and Inertia, OpenAi API Finetuning&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Just wanted one idea of mine to go from start to end (subscriptions - just me at this point🙂 )&lt;br&gt;
The stack was great, still learning Tailwind as you can see in the CSS. &lt;br&gt;
Some of the APIs I used lacked good tags or data so I used OpenAi Finetuning and some tags I had to train it on what a recall might be tagged if it lacked a tag. &lt;a href="https://platform.openai.com/docs/guides/fine-tuning"&gt;https://platform.openai.com/docs/guides/fine-tuning&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  My Blog
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://alfrednutile.info/"&gt;https://alfrednutile.info/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We all have to do this! Really wanted to "show off" my Github stats.&lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Statamic &lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Like Livewire I appreciate the community and effort. As a long time Laravel dev I feel it was too different. I suck at design🙂 &lt;/p&gt;

&lt;h2&gt;
  
  
  Well Registration Site and Clean water search site (Two of them)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cleanwater.kaweahwater.org/start"&gt;https://cleanwater.kaweahwater.org/start&lt;/a&gt;&lt;br&gt;
SECOND URL is in QA mode&lt;/p&gt;

&lt;p&gt;Working with a partner in California to build clean water / well registration sites. The internal part of the site is actually quite complex.&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean, Postgres and GIS plugin.&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Found my stack and it has been working really well. This site just helped me to grow in this skill and then appreciate GIS, Postgres, and MapBox, Twilio for SMS&lt;/p&gt;

&lt;h2&gt;
  
  
  BountifulTravel
&lt;/h2&gt;

&lt;p&gt;URL still closed other than a few standalone payment and management areas.&lt;/p&gt;

&lt;p&gt;This client site is moving a HUGE Wordpress site to Laravel that manages Group Trips.&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Vapor!, Integration to Monday.com , Plaid (great tech stack for payments imo) and AFS for other payments.&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Overall it reminded me of why I need to work with the customer and not a person between me and the customer. No matter how good we all are at our jobs and intentions communication is key and building the right thing really means the developer in constant contact (jargon) with the customer. The tech stack is good but this was my last Vapor and I would move it if I could back to Forge and DO. I like Vapor and glad I know it is there when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Book Creation and Ai/LLM
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextchapters.com/"&gt;https://nextchapters.com/&lt;/a&gt;  (this is the sister site that leads into the site I made)&lt;/p&gt;

&lt;p&gt;Lot's of work on a sister site still in QA to help authors write books using OpenAi as an assistant. Really fun stuff.&lt;br&gt;
Kind of like GitHub but for a community of writers.&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
The stack just works and delivers. I am getting to the point where I have a nice folder of components to share with all my projects. Using Echo and Pusher the user gets a very dynamic experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  LaraChain Open Ai LLM Integration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://larachain.io/"&gt;https://larachain.io/&lt;/a&gt; and &lt;a href="https://www.youtube.com/playlist?list=PLL8JVuiFkO9I1EgzjPKKZNz-ZVbcBJWF9"&gt;https://www.youtube.com/playlist?list=PLL8JVuiFkO9I1EgzjPKKZNz-ZVbcBJWF9&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This an open-source project with a focus on use LLMs and "blocks" to quickly make a backend for any person or company take advantage of Vector databases and OpenAi API (or other APIs).&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean, OpenAi PHP &lt;a href="https://github.com/openai-php"&gt;https://github.com/openai-php&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Amazing the tech stack and libraries we have as PHP developers. I was really worried with the rise of &lt;a href="https://www.langchain.com/"&gt;https://www.langchain.com/&lt;/a&gt;  if PHP and Laravel could keep up. After my deep dive into this it is clear we can 🥂&lt;/p&gt;

&lt;h2&gt;
  
  
  TOP SECRET PROJECT
&lt;/h2&gt;

&lt;p&gt;One other OpenAi API LLM Project that was MVP and did not make it past that stage but was a great try. I learned a lot and worked with someone who showed me a lot about building the right product, and staying focused on the MVP and just how to think about solutions.&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean, OpenAi PHP &lt;a href="https://github.com/openai-php"&gt;https://github.com/openai-php&lt;/a&gt; &lt;br&gt;
RapidAPI &lt;a href="https://rapidapi.com/hub"&gt;https://rapidapi.com/hub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Again Horizon, Batches etc all lead us to make some amazing tools. And RapidAPI allowed me to get some data quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLMAssistant
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/alnutile/llm-assistant"&gt;https://github.com/alnutile/llm-assistant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is my big effort to scratch my own itch of storing articles I like, helping me with my day to day work planning (Bullet Journal like) and since it knows a lot about me it can reply to Job Posts in a sensible way!&lt;/p&gt;

&lt;p&gt;Tech: &lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean, OpenAi PHP &lt;a href="https://github.com/openai-php"&gt;https://github.com/openai-php&lt;/a&gt; &lt;br&gt;
RapidAPI &lt;a href="https://rapidapi.com/hub"&gt;https://rapidapi.com/hub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
The RapidApi webscrapper is solid &lt;a href="https://rapidapi.com/proxycrawl/api/proxycrawl-scraper"&gt;https://rapidapi.com/proxycrawl/api/proxycrawl-scraper&lt;/a&gt; I tried PHPRoach but ended up with this and was done in under and hour.  Otherwise it is a Forum like system (eg me and the LLM) to go back and forth about a topic like a reply to a job posting, or an article I want to tag. You can see more about that here &lt;a href="https://medium.com/@alnutile/openai-function-calls-and-laravel-a2f8db9b0f97"&gt;https://medium.com/@alnutile/openai-function-calls-and-laravel-a2f8db9b0f97&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Colosseum Crew Gaming Tournament site
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://colosseumcrew.com/"&gt;https://colosseumcrew.com/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is one of my favorites. It was me trying to be a VC type partner but putting up my dev time for later payment. 🤞&lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Nova (but moved away from this), Tailwind, Inertia, API integration to &lt;a href="https://challonge.com/"&gt;https://challonge.com/&lt;/a&gt; and Spark at first but moved away from this as well to Paypal&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Nova was a good start but the data is quite complex so I moved away from it and just built a ui.&lt;br&gt;
Spark too just was not flexible enough so I moved to Paypal (which fit some of their needs better)&lt;br&gt;
Very very hard site for me, the domain was all new and the data structure was harder than I imagined with matches, teams, players, stats etc. Really a good bunch of people to work with too looking forward to it being used an being an enjoyable system for the users to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  SimpleRecipes (personal project)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://simplerecipes.io/"&gt;https://simplerecipes.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the LLM Craze I had to try some ideas. &lt;/p&gt;

&lt;p&gt;Tech:&lt;br&gt;
Interia, Tailwind, Forge, DigitalOcean, OpenAi PHP &lt;a href="https://github.com/openai-php"&gt;https://github.com/openai-php&lt;/a&gt;, Nova&lt;/p&gt;

&lt;p&gt;Thoughts:&lt;br&gt;
Nova worked here cause I stayed within the lines of what it can do. And did not push it to a place where I had to learn a ton with Nova. The art is all Dalle-2 with a prompt that focuses on food in watercolor style. Still never certain what you will get! Fun project.&lt;/p&gt;

&lt;p&gt;There are others items, like one customer used Larachain as a backend and we are still integrating some chat and doc search into their frontend etc.  But this is the list of tech used, stuff built and the happy path back to a developer.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
