Building Shelley

Presenter Notes

GPT on the command line with PHP

  • Why this project?
    • Best way to learn about something interesting is either to teach it or start a project
    • I live on the command line most of the time
    • If you don't have it then build it
  • Why PHP?
    • Fast for development
    • Always around for me, easily available for others either via Docker or directly
  • Why not PHP?
    • better AI infrastructure in Python and JS
  • What's Shelley?
    • A very simple CLI tool for calling an OpenAI LLM
      • Large Language Model

Presenter Notes

What is GPT?

USER > Please provide five bullet points for a slide in a presentation about building a chat client. The title of the slide is "What is GPT?"

ASSISTANT >

  1. Generative Pre-trained Transformer (GPT): GPT is an advanced language model developed by OpenAI, designed to understand and generate human-like text based on given input.
  2. Large-scale Training: GPT learns grammar, facts, and reasoning from vast amounts of diverse text data.
  3. Transformer Architecture: Efficiently processes long-range dependencies and scales up to handle large datasets.
  4. Fine-tuning: Train GPT on domain-specific data for tasks like chatbot responses or content generation.
  5. Innovative Applications: GPT's capabilities make it ideal for building engaging and context-aware chat clients.

Presenter Notes

OpenAI with PHP

Presenter Notes

The OpenAI API

  • OpenAI provides a relatively simple REST API
  • Also recommends PHP libraries:
    • https://platform.openai.com/docs/libraries/community-libraries
      • orhanerday/open-ai
      • tectalic/openai
      • openai-php/client

Presenter Notes

Hello, Hal (Curl edition)

 1$url = "https://api.openai.com/v1/chat/completions";
 2
 3$headers = [
 4    'Authorization: Bearer ' . getenv('OPENAI_API_KEY'),
 5    'Content-Type: application/json'
 6];
 7
 8$data = [
 9    'model' => "gpt-3.5-turbo",
10    "messages" => [["role" => "user",   "content" => "What was the first known mechanical calculating machine?"]],
11    'temperature' => 0.5
12];
13
14$ch = curl_init();
15curl_setopt($ch, CURLOPT_URL, $url);
16curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
17curl_setopt($ch, CURLOPT_POST, true);
18curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
19curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
20$response = curl_exec($ch);
21curl_close($ch);
22print json_encode(json_decode($response), \JSON_PRETTY_PRINT);

Presenter Notes

Curl output

 1{
 2    "id": "chatcmpl-7BomkiWG1Ml0ZcZMjRrPgTYu0IX4r",
 3    "object": "chat.completion",
 4    "created": 1683051182,
 5    "model": "gpt-3.5-turbo-0301",
 6    "usage": {
 7        "prompt_tokens": 26,
 8        "completion_tokens": 59,
 9        "total_tokens": 85
10    },
11    "choices": [
12        {
13            "message": {
14                "role": "assistant",
15                "content": "The first known mechanical calculating machine is the abacus, which dates back to ancient times and was used by many civilizations, including the Greeks, Romans, and Chinese. However, the first machine that could perform mathematical calculations automatically was the Pascaline, invented by Blaise Pascal in 1642."
16            },
17            "finish_reason": "stop",
18            "index": 0
19        }
20    ]
21}

Presenter Notes

Hello, Hal (library edition)

 1use Orhanerday\OpenAi\OpenAi;
 2
 3$open_ai = new OpenAi(getenv("OPENAI_API_KEY"));
 4$reply = $open_ai->chat([
 5    'model' => "gpt-3.5-turbo",
 6    'messages' => [
 7        ["role" => "system", "content" => "You are a helpful assistant"],
 8        ["role" => "user",   "content" => "What was the first known mechanical calculating machine"],
 9    ],
10    'temperature' => 0.5,
11    'max_tokens' => 1000,
12    'frequency_penalty' => 0,
13    'presence_penalty' => 0.6,
14]);
15
16$ret = json_decode($reply, true);
17print $ret['choices'][0]['message']['content'];

Presenter Notes

Library output

1$ php demo/001_first.php

The first known mechanical calculating machine is the abacus, which dates back to ancient times. However, the first mechanical calculator that could perform arithmetic operations was invented by French mathematician and philosopher Blaise Pascal in the 17th century. It was called the Pascaline and used gears and cogs to add and subtract numbers.

Presenter Notes

Completion v chat

  • Completion models optimised for one off text tasks
    • Is possible to use them for chat with structured prompts
  • Chat models optimised for ongoing conversations
  • Because GPT 3.5 relatively cheap and advanced it's probably the Swiss Army knife for most situations

Presenter Notes

Completion: curl

 1$url = "https://api.openai.com/v1/completions";
 2
 3$headers = [
 4    'Authorization: Bearer ' . getenv('OPENAI_API_KEY'),
 5    'Content-Type: application/json'
 6];
 7
 8$data = [
 9    'model' => "text-davinci-003",
10    "prompt" => "What is the earliest historical reference to Brighton, England?",
11    "max_tokens" => 500
12];
13
14$ch = curl_init();
15curl_setopt($ch, CURLOPT_URL, $url);
16curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
17curl_setopt($ch, CURLOPT_POST, true);
18curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
19curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
20$response = curl_exec($ch);
21curl_close($ch);
22print json_encode(json_decode($response), \JSON_PRETTY_PRINT);

Presenter Notes

Curl output

 1{
 2    "id": "cmpl-7DzFed0zU3cLUXcC8GqIS1DyndeWL",
 3    "object": "text_completion",
 4    "created": 1683568070,
 5    "model": "text-davinci-003",
 6    "choices": [
 7        {
 8            "text": "\n\nThe earliest recorded historical reference to Brighton dates back to the 9th century AD when it was mentioned as \"Beorhthelm'stone\" in the Anglo-Saxon Chronicle.",
 9            "index": 0,
10            "logprobs": null,
11            "finish_reason": "stop"
12        }
13    ],
14    "usage": {
15        "prompt_tokens": 11,
16        "completion_tokens": 39,
17        "total_tokens": 50
18    }
19}

Presenter Notes

Completion: lib

 1use Orhanerday\OpenAi\OpenAi;
 2
 3$open_ai = new OpenAi(getenv("OPENAI_API_KEY"));
 4$reply = $open_ai->completion([
 5    'model' => "text-davinci-003",
 6    "prompt" => "What is the earliest historical reference to Brighton, England?",
 7    'temperature' => 0.5,
 8    'max_tokens' => 1000,
 9    'frequency_penalty' => 0,
10    'presence_penalty' => 0.6,
11]);
12
13$ret = json_decode($reply, true);
14print $ret['choices'][0]['text'];

Presenter Notes

Library output

1$ php simple-lib-example-completion.php

The earliest historical reference to Brighton, England dates back to the 6th century when it was known as Beorhthelm's Tun. It was mentioned in the Anglo-Saxon Chronicle of AD 477 as a landing place of the Saxons.

  • Note that in testing this I ran the code multiple times. GPT 3.5 provided different answers with dates varying from 477 to 1086!

Presenter Notes

Models

  • gpt-4
    • 8,192 tokens
    • $0.06 / 1K tokens
  • gpt-4-32k
    • 32,768 tokens
    • $0.12 / 1K tokens
  • gpt-3.5-turbo
    • 4,096 tokens
    • $0.002 / 1K tokens
  • text-davinci-003
    • 4,097 tokens
    • $0.02 / 1K tokens

Presenter Notes

Temperature

  • Determines "randomness and exploration during a model's decision-making process"
  • Often crudely related to creativity.
  • Usually range of 0 - 1
    • 0 - less variation
    • 1 - most variation
  • Typically, where accuracy important
    • 0 - 0.3
  • For tasks like ad copy or brainstorming
    • 0.5 - 0.7

Presenter Notes

Max tokens

  • Tokens are the units of langauge parsing
  • Will vary from model to model -- but often very close to word count. In fact sometimes words and tokens coincide.
  • Every model has a token capacity which you'll need to be aware of
  • Three aspects you need to remember about tokens
    • context
    • response (max_tokens)
    • model capacity
      • The previous two must fit within this.

Presenter Notes

Testing tokens up front

  • gioni06/gpt3-tokenizer
    • Does not support gpt4 (obviously)
    • probably good enough
 1use Gioni06\Gpt3Tokenizer\Gpt3TokenizerConfig;
 2use Gioni06\Gpt3Tokenizer\Gpt3Tokenizer;
 3
 4function countTokens(string $str): int
 5{
 6    $config = new Gpt3TokenizerConfig();
 7    $tokenizer = new Gpt3Tokenizer($config);
 8    $numberOfTokens = $tokenizer->count($str);
 9    return $numberOfTokens;
10}
11
12print countTokens("Mary had, apparently, a little lamb!");
13// 9

Presenter Notes

The penalties

  • Typically both range between 0 and 1
  • frequency_penalty
    • A higher value discourages reuse of tokens
  • presence_penalty
    • A higher value discourages use of tokens from the query in output

Presenter Notes

a 16 line chat bot!

 1use Orhanerday\OpenAi\OpenAi;
 2
 3$open_ai = new OpenAi(getenv("OPENAI_API_KEY"));
 4$system = [["role" => "system", "content" => "You are helping out with a talk about writing AI clients"]];
 5while($line = readline("USER > ")) {
 6    $context[] = ["role" => "user", "content" => trim($line)];
 7    $completion = $open_ai->chat([
 8        'model' => "gpt-3.5-turbo",
 9        'messages' => $context, 
10    ]);
11    $ret = json_decode($completion, true);
12    $resp = trim($ret['choices'][0]['message']['content']);
13    print "ASSISTANT > {$resp}\n"; 
14    $context[] = ["role" => "assistant", "content" => $resp];
15    $context = array_slice($context, -4);
16}

Presenter Notes

Trying it out

USER > when were Fibers introduced to PHP?
ASSISTANT > Fibers were introduced to PHP in version 8.1, which was released on November 25, 2021.
USER > can you provide a very short definition?
ASSISTANT > Fibers are lightweight, cooperative, and non-preemptive threads in PHP that allow for asynchronous programming without the complexity of traditional multithreading or callbacks.
USER > are they used yet in any major projects?
ASSISTANT > As Fibers were just recently introduced in PHP 8.1, their adoption in major projects is still in the nascent stage. However, frameworks like Laravel have already started experimenting with Fibers to improve their event-driven programming capabilities. It is expected that their usage will gradually increase as more developers become familiar with their benefits and integrate them into their projects.

Presenter Notes

So we're done, right?

  • Still some problems to address
  • Token limits
  • The Memento effect -- providing context -- without tripping the token limit
  • Saving and resuming conversations
  • Interfacing to local data

Presenter Notes

Using Shelley

Presenter Notes

Enter Shelley

  • A simple command line chat app
  • Persistance
  • (Potentially) extensible
  • Manages multiple conversations
  • Summarises older messages to maximise context

Presenter Notes

The beginning

1$ php scripts/shelley.php

# starting or resuming 'default'

# no history

USER > what do you know about Silicon Brighton? # sending
ASSISTANT > Silicon Brighton is a community of tech entrepreneurs, startups, and businesses based in Brighton, UK. The community aims to connect and support individuals and companies in the tech industry by providing networking opportunities, events, and resources.

[...]

Overall, Silicon Brighton plays an important role in promoting and supporting the growth of the tech industry in Brighton and the wider UK.
# summarising... done

USER >

Presenter Notes

Resuming the default converation

1$ php scripts/shelley.php

# starting or resuming 'default'
# last conversation 2023-05-09T21:24:04+01:00

# recent summary

# ASSISTANT > Silicon Brighton is a community of tech entrepreneurs, startups, and businesses based in Brighton, U
# USER > Is Brighton known as a hub for tech startups?
# ASSISTANT > Yes, Brighton is known as a hub for tech startups in the UK. The city has a thriving tech scene with
# USER > what are Brighton's other key industries?
# ASSISTANT > Brighton has a diverse economy with several key industries, including:
#
# 1. Tourism: Brighton is a po

USER >

Presenter Notes

Resuming a named conversation

1$ php scripts/shelley.php php-chat

# starting or resuming 'php-chat'
# last conversation 2023-05-09T22:10:57+01:00

# recent summary

# USER > What is the best way set a date?
# ASSISTANT > In PHP, the best way to set a date is by using the DateTime class. Here's an example:
# USER > what are those format values?
# ASSISTANT > The format values used in the DateTime::format method are placeholders that represent various part
# USER > is it better to use the date functions or the DateTime class?
# ASSISTANT > Both the date function and the DateTime class can be used to work with dates and times in PHP. H

USER >

Presenter Notes

Setting the premise for a chat

USER > /premise You are an expert assistant specialising in the PHP programming language. You always use modern standards-compliant code in your replies.
# premise set

Presenter Notes

The premise sets the tone

USER > What is the best way set a date?
# sending
ASSISTANT > In PHP, the best way to set a date is by using the DateTime class. Here's an example:

```php
$date = new DateTime('2021-08-31');
echo $date->format('Y-m-d'); // Output: 2021-08-31
```
....

This method is recommended because it handles leap years, daylight saving time, and other time-related issues automatically.
# summarising... done

Presenter Notes

Viewing the premise

USER > /premise
# premise:
# You are an expert assistant specialising in the PHP programming language. You always use modern standards-compliant code in your replies.

Presenter Notes

Viewing the context

  • And, incidentally, seeing showing how premise is provided to the assistant

USER > /context
# Here is the current context:
SYSTEM > You are an expert assistant specialising in the PHP programming language. You always use modern standards-compliant code in your replies.
USER > You are an expert assistant specialising in the PHP programming language. You always use modern standards-compliant code in your replies. Do you understand this instruction?
ASSISTANT > I understand. I am ready.
USER > What is the best way set a date?
ASSISTANT > In PHP, the best way to set a date is by using the DateTime class. Here's an example:
[...]

Presenter Notes

Multiple line input

USER > Who wrote this fragment of poetry?/
/
/m
# multi-mode on use /e to send buffer
And both that morning equally lay
In leaves no step had trodden black.
/e
# sending
ASSISTANT > This fragment of poetry is from the first stanza of the poem "The Road Not Taken" by Robert Frost.
# summarising... done
USER > I believe it's from the the start of the third stanza but thanks for helping me track it down.
# sending
ASSISTANT > I apologize for the mistake. You are correct, the fragment of poetry "And both that morning equally lay In leaves no step had trodden black" is actually from the first line of the third stanza of Robert Frost's poem "The Road Not Taken". Thank you for bringing this to my attention.
# summarising... done
USER >

Presenter Notes

Reading files

USER > Please write a unit test for this class:/
/file src/control/ProcessUI.php
# got contents of 'src/control/ProcessUI.php (182 lines)'

# sending
ASSISTANT > Sure, here's an example unit test for the ProcessUI class:

```php
use PHPUnit\Framework\TestCase;
use getinstance\utils\aichat\control\ProcessUI;

class ProcessUITest extends TestCase
// [SNIP]
```

This test case checks the output of the initSummarise method with a mocked Runner object and empty Messages. You can add more test cases to cover other methods and scenarios.
# summarising... done
USER >

Presenter Notes

UI command overview

  • /buffer - show the current buffer (the query you are building)
  • /context - show the full context that will be sent to the assistant
  • /file <path> - read a file or URL and add to buffer. (warning HTML often trips the token limit)
  • /m - switch to multi-line mode (send buffer with /e on its own line)
  • /premise [new premise] - show or set the assistant's characteristics (depending upon whether argument supplied)
  • /redo - forget the current buffer

Presenter Notes

UI/Shelley to do

  • /? - help command!
  • Proper extensibility
  • Managing large files/HTML
  • Data connectors
  • History search
  • pagination
  • proper Readline support
    • Improved path completion
    • cursor for history
  • $EDITOR integration
  • Anything that itches

Presenter Notes

Shelley - Issues and Architecture

Presenter Notes

Architecture overview

Overview

Presenter Notes

The Context Problem (1)

Context1

Presenter Notes

The Context Problem (2)

Context2

Presenter Notes

Context / Summarisation solution

  • Messages
    • Store summaries and token counts in message data
    • Method to fit context within token count
      • switch to summary mode at threshold
  • Runner
    • Dedicated control Comms / Messages objects for summarisation
    • Summarisation method which generates and saves summaries
  • ProcessUI
    • Call Runner::summariseMostRecent() after each Assistant reply

Presenter Notes

Context / Summarisation solution

Summarisation

Presenter Notes

UI Commands

Command

Presenter Notes

UI Commands: ProcessUI class

 1public function __construct(private Runner $runner)
 2{
 3    $this->saver = $runner->getSaver();
 4    $this->commands = [
 5        new RedoCommand($runner),
 6        new DisplayBufferCommand($runner),
 7        new FileCommand($runner),
 8        new ContextCommand($runner),
 9        new PremiseCommand($runner),
10        // Add other command classes here
11    ];
12}

Presenter Notes

Thank you!

  • matt@getinstance.com
  • https://getinstance.com
  • https://github.com/getinstancemz/shelley/

Presenter Notes

Appendix 1 - Comms

Presenter Notes

A simple Comms class

comms

Presenter Notes

A simple Comms class

 1class Comms
 2{
 3    public function __construct(private string $secretKey)
 4    {
 5    }
 6
 7    public function sendQuery(array $messages): string
 8    {
 9        $open_ai = new OpenAi($this->secretKey);
10        $completion = $open_ai->chat([
11            'model' => "gpt-3.5-turbo",
12            'messages' => $messages,
13            'temperature' => 0.5,
14            'max_tokens' => 1000,
15            'frequency_penalty' => 0,
16            'presence_penalty' => 0.6,
17        ]);
18
19        $ret = json_decode($completion, true);
20        return $ret['choices'][0]['message']['content'];
21    }
22}

Presenter Notes

Running Comms

1$comms = new Comms(getenv("OPENAI_API_KEY"));
2print $comms->sendQuery([
3    ["role" => "system", "content" => "You are a helpful assistant"],
4    ["role" => "user", "content" => "Which is the most popular PHP framework"]
5]);

Presenter Notes

Encapsulating Model data

model

Presenter Notes

The abstract Model class

 1abstract class Model
 2{
 3    abstract public function getName(): string;
 4    abstract public function getMaxTokens(): int;
 5    abstract public function getMode(): string;
 6
 7    public function getMaxResponse(): int
 8    {
 9        return ($this->getMaxTokens() * 0.2);
10    }
11
12    public function getMaxContext(): int
13    {
14        $contextmax = ($this->getMaxTokens() - $this->getMaxResponse());
15        return ($contextmax - ($contextmax * 0.05));
16    }
17}

Presenter Notes

The GPT35 class

 1class GPT35 extends Model
 2{
 3    public function getName(): string
 4    {
 5        return "gpt-3.5-turbo";
 6    }
 7
 8    public function getMaxTokens(): int
 9    {
10        return 4096;
11    }
12
13    public function getMode(): string
14    {
15        return "chat";
16    }
17}

Presenter Notes

Using the Model class

 1    public function sendQuery(array $messages): string
 2    {
 3        $open_ai = new OpenAi($this->secretKey);
 4        $completion = $open_ai->chat([
 5            'model' => $this->model->getName(),
 6            'messages' => $messages,
 7            'temperature' => 0.5,
 8            'max_tokens' => $this->model->getMaxResponse(),
 9            'frequency_penalty' => 0,
10            'presence_penalty' => 0.6,
11        ]);
12
13        $ret = json_decode($completion, true);
14        if (isset($ret['error'])) {
15            throw new \Exception($ret['error']['message']);
16        }
17        if (! isset($ret['choices'][0]['message']['content'])) {
18            throw new \Exception("Unknown error: " . $completion);
19        }
20        return $ret['choices'][0]['message']['content'];
21    }
22}

Presenter Notes

Appendix 2 - Messages and context

Presenter Notes

Constraining Messages

Messages

Presenter Notes

The Messages class

 1class Messages
 2{
 3    private array $messages = [];
 4    public string $premise = "You are an interested, inquisitive and helpful assistant";
 5
 6    private function makeMessage(string $role, string $content): Message
 7    {
 8        $message = new Message(-1, $role, $content);
 9        return $message;
10    }
11
12    public function addNewMessage($role, $content): Message
13    {
14        return $this->addMessage($this->makeMessage($role, $content));
15    }
16
17    public function addMessage(Message $message): Message
18    {
19        $this->messages[] = $message;
20        return $message;
21    }

Presenter Notes

The Messages class (2)

 1    public function toArray($maxrows = 0, $maxtokens = 0)
 2    {
 3        $desc = [
 4            $this->makeMessage("system", $this->premise),
 5            $this->makeMessage("user", $this->premise . " Do you understand this instruction?"),
 6            $this->makeMessage("assistant", "I understand. I am ready.")
 7        ];
 8        $messages = $this->messages;
 9        if ($maxrows > 0) {
10            $messages = array_slice($this->messages, ($maxrows * -1));
11        }
12        return array_map(function ($val) {
13            return $val->getOutputArray();
14        }, $messages);
15    }
16}

Presenter Notes

Using Messages in Comms

 1public function sendQuery(Messages $messages): string
 2{
 3    $open_ai = new OpenAi($this->secretKey);
 4    $completion = $open_ai->chat([
 5        'model' => $this->model->getName(),
 6        'messages' => $messages->toArray(100),
 7        'temperature' => 0.5,
 8        'max_tokens' => $this->model->getMaxResponse(),
 9        'frequency_penalty' => 0,
10        'presence_penalty' => 0.6,
11    ]);
12
13    $ret = json_decode($completion, true);
14    return $ret['choices'][0]['message']['content'];
15}

Presenter Notes

Invoking the classes

1$comms = new Comms(new GPT35(), getenv("OPENAI_API_KEY"));
2$messages = new Messages();
3$messages->addNewMessage("user", "What were the principle points of dispute in the aesthetics of Plato and Aristotle?");
4print $comms->sendQuery($messages);
1$ php simpleshelley3/caller1.php

The principal points of dispute in the aesthetics of Plato and Aristotle were:

  1. The role of art: Plato believed that art was a mere imitation of reality and had no inherent value. He saw it as a distraction from the pursuit of truth and knowledge. Aristotle, on the other hand, believed that art had the power to reveal truths about the world and human nature.

...

Presenter Notes

Appendix 3 - Persistence

Presenter Notes

Adding persistance

ConvoSaver

Presenter Notes

Using ConvoSaver

 1$saver = new ConvoSaver(__DIR__."/data", "democonvo");
 2$convoconf = $saver->getConf();
 3$premise = $convoconf["premise"] ?? null;
 4$messages = new Messages($premise);
 5$comms = new Comms(new GPT35(), getenv("OPENAI_API_KEY"));
 6
 7// prime from storage
 8$dbmsgs = $saver->getMessages(100);
 9foreach ($dbmsgs as $dbmsg) {
10    $messages->addMessage($dbmsg);
11}
12
13// begin conversation
14while($line = readline("USER   > ")) {
15    $message = $messages->addNewMessage("user", trim($line));
16    $saver->saveMessage($message);
17
18    $resp = $comms->sendQuery($messages);
19
20    $message = $messages->addNewMessage("assistant", $resp);
21    $saver->saveMessage($message);
22    print "ASSISTANT > {$resp}\n";
23}

Presenter Notes

Persistence in practice

$ php simpleshelley3/caller2.php
USER > I am trying to remember a short story which ends with a man having his eyelids peeled off so that he can enjoy a life as a cowboy in a virtuality. Do you know the story?
ASSISTANT > Yes, the short story you are thinking of is "The Veldt" by Ray Bradbury. In the story, a family lives in a high-tech house that ...
USER > hmm.. I believe it is 'Spectator Sport' by John D MacDonald.
ASSISTANT > I apologize for the confusion. You are correct that "Spectator Sport" by John D. MacDonald is the short story...
USER >
$
$ # Oops... I killed the process
$
$ php simpleshelley3/caller2.php
USER > What story were we discussing?
ASSISTANT > We were discussing the short story that features a man having his eyelids peeled off so that he can enjoy a life as a cowboy in a virtual reality environment. Initially, I mentioned "The Veldt" by Ray Bradbury, but you corrected me and said that it was actually "Spectator Sport" by John D. MacDonald.
USER > Thank you, yes.

Presenter Notes

Appendix 4 - Control

Presenter Notes

A data-facing Runner

Runner

Presenter Notes

Querying and Persistance extracted

1$saver = new ConvoSaver(__DIR__."/data", "democonvo");
2$comms = new Comms(new GPT35(), getenv("OPENAI_API_KEY"));
3$runner = new Runner($comms, $saver);
4
5// begin conversation
6while($line = readline("USER   > ")) {
7    $resp = $runner->query($line);
8    print "ASSISTANT > {$resp}\n";
9}

Presenter Notes

Manage input and pass it on

ProcessUI

Presenter Notes

The ProcessUI class

 1class ProcessUI
 2{
 3    public function __construct(private Runner $runner)
 4    {
 5    }
 6
 7    public function run(): void
 8    {
 9        $msgs = $this->runner->messages;
10        $input = "";
11        while ($input = $this->process("USER      > ")) {
12            try {
13                print "# sending\n";
14                $resp = $this->runner->query($input);
15            } catch (\Exception $e) {
16                $resp = "** I'm sorry, I encountered an error:\n";
17                $resp .= $e->getMessage();
18            }
19            print "ASSISTANT > {$resp} \n";
20        }
21    }
22
23    private function process($prompt): string
24    {
25        $buffer = "";
26        while ($input = readline($prompt)) {
27            // do other things with input here -- or:
28            $buffer .= $input;
29            break;
30        }
31        return $buffer;
32    }
33}

Presenter Notes

The ProcessUI class (2)

 1    private function process($prompt): string
 2    {
 3        $buffer = "";
 4        while ($input = readline($prompt)) {
 5            // do other things with input here -- or:
 6            $buffer .= $input;
 7            break;
 8        }
 9        return $buffer;
10    }
11}

Presenter Notes

Appendix 5 - UI Commands

Presenter Notes

UI Commands

Command

Presenter Notes

UI Commands: ProcessUI class

 1private function process($prompt)
 2{
 3    $buffer = "";
 4    while ($input = readline($prompt)) {
 5        $prompt = "";
 6        if (! $this->invokeCommand($input, $buffer)) {
 7            $buffer .= $input;
 8            break;
 9        }
10    }
11
12    return $buffer;
13}
14
15private function invokeCommand(string $input, string &$buffer)
16{
17    foreach ($this->commands as $command) {
18        if ($command->matches($input)) {
19            $command->execute($buffer, $command->getLastMatchArgs());
20            return true;
21        }
22    }
23    return false;
24}

Presenter Notes

UI Commands: ProcessUI class

 1public function __construct(private Runner $runner)
 2{
 3    $this->saver = $runner->getSaver();
 4    $this->commands = [
 5        new RedoCommand($runner),
 6        new DisplayBufferCommand($runner),
 7        new FileCommand($runner),
 8        new ContextCommand($runner),
 9        new PremiseCommand($runner),
10        // Add other command classes here
11    ];
12}

Presenter Notes

UI Commands: AbstractCommand

1interface CommandInterface
2{
3    public function execute(string &$buffer, array $args): void;
4    public function getName(): string;
5    public function matches(string $input): bool;
6}
 1abstract class AbstractCommand implements CommandInterface
 2{
 3    private array $args = [];
 4    public function matches(string $input): bool
 5    {
 6        $ret = (bool)preg_match($this->getPattern(), $input, $matches);
 7        array_shift($matches);
 8        $this->args = $matches;
 9        return $ret;
10    }
11
12    protected function getPattern(): string
13    {
14        $trig = "(?:/|\\\\)";
15        return "&^{$trig}(?:{$this->getName()})\s*(.*)\s*$&";
16    }
17}

Presenter Notes

UI Commands: File

 1class FileCommand extends AbstractCommand
 2{
 3    public function execute(string &$buffer, array $args): void
 4    {
 5        $contents = file_get_contents($args[0]);
 6        $len = count(explode("\n", $contents));
 7        print "# got contents of '{$args[0]} ({$len} lines)'\n";
 8        $buffer .= $contents;
 9    }
10
11    public function getName(): string
12    {
13        return "file";
14    }
15}

Presenter Notes