Skip to content

Commit

Permalink
Adds an ability to inject interceptors into Agent executor
Browse files Browse the repository at this point in the history
  • Loading branch information
butschster committed Sep 5, 2024
1 parent 6899e0e commit 6386800
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 16 deletions.
190 changes: 189 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,192 @@
# CLI Chat
# LLM Agents CLI Chat

This cool package gives you a CLI chat interface to chat with LLM agents. It's built on top of Symfony Console, so you
know it's gonna be smooth sailing.

## What's this all about?

- Start chat sessions with different agents
- Send messages and get responses
- Handle tool calls and their results
- Display chat history

It's perfect for testing your agents or building CLI-based chatbots.

## Installation

You can install the package via Composer:

```bash
composer require llm-agents/cli-chat
```

## Configuration

This package doesn't require much configuration. The main things you'll need to set up are:

- An implementation of `LLM\Agents\Chat\ChatServiceInterface`
- An implementation of `LLM\Agents\Chat\ChatHistoryRepositoryInterface`
- An implementation of `LLM\Agents\Chat\SessionInterface`
- Your agent registry
- Your tool registry

## Usage

To use this package, you need to create two console commands in your application:

- A command to start a chat session
- A command to display chat history

Here's how you can set these up using Spiral framework:

> **Note:** You can use any framework or library that supports Symfony Console commands.
### Chat Session Command

This command starts a new chat session:

```php
<?php

declare(strict_types=1);

namespace App\Endpoint\Console;

use LLM\Agents\Agent\AgentRegistryInterface;
use LLM\Agents\Chat\ChatHistoryRepositoryInterface;
use LLM\Agents\Chat\ChatServiceInterface;
use LLM\Agents\Chat\Console\ChatSession;
use LLM\Agents\Tool\ToolRegistryInterface;
use Ramsey\Uuid\Uuid;
use Spiral\Console\Attribute\AsCommand;
use Spiral\Console\Command;
use Spiral\Console\Console;
use Symfony\Component\Console\Cursor;

#[AsCommand(
name: 'chat',
description: 'Chat session'
)]
final class ChatCommand extends Command
{
public function __invoke(
AgentRegistryInterface $agents,
ChatServiceInterface $chat,
Console $console,
ChatHistoryRepositoryInterface $chatHistory,
ToolRegistryInterface $tools,
): int {
$cursor = new Cursor($this->output);
$cursor->clearScreen();
$console->run(command: 'agent:list', output: $this->output);

$chat = new ChatSession(
input: $this->input,
output: $this->output,
agents: $agents,
chat: $chat,
chatHistory: $chatHistory,
tools: $tools,
);

$chat->run(accountUuid: Uuid::fromString('00000000-0000-0000-0000-000000000000'));

return self::SUCCESS;
}
}
```

### Chat History Command

This command displays the chat history for a specific session:

```php
<?php

declare(strict_types=1);

namespace App\Endpoint\Console;

use LLM\Agents\Chat\ChatHistoryRepositoryInterface;
use LLM\Agents\Chat\ChatServiceInterface;
use LLM\Agents\Chat\Console\ChatHistory;
use Ramsey\Uuid\Uuid;
use Spiral\Console\Attribute\Argument;
use Spiral\Console\Attribute\AsCommand;
use Spiral\Console\Command;

#[AsCommand(
name: 'chat:session',
description: 'Chat session'
)]
final class ChatWindowCommand extends Command
{
#[Argument(name: 'session_uuid')]
public string $sessionUuid;

public function __invoke(
ChatHistoryRepositoryInterface $chatHistory,
ChatServiceInterface $chatService,
): int {
$chatWindow = new ChatHistory(
input: $this->input,
output: $this->output,
chatHistory: $chatHistory,
chat: $chatService,
);

$chatWindow->run(Uuid::fromString($this->sessionUuid));

return self::SUCCESS;
}
}
```

To use these commands, you'll need to register them with your application's console kernel or command loader, depending
on your framework.

## Class Diagram

Here's a quick look at how everything fits together:

```mermaid
classDiagram
class ChatServiceInterface {
+getSession(UuidInterface) SessionInterface
+updateSession(SessionInterface) void
+startSession(UuidInterface, string) UuidInterface
+ask(UuidInterface, string|Stringable) UuidInterface
+closeSession(UuidInterface) void
}
class SessionInterface {
+getUuid() UuidInterface
+getAgentName() string
+updateHistory(array) void
+isFinished() bool
}
class ChatHistoryRepositoryInterface {
+clear(UuidInterface) void
+getMessages(UuidInterface) iterable
+addMessage(UuidInterface, object) void
}
class ChatSession {
-io: ChatStyle
-cursor: Cursor
+run(UuidInterface, string) void
}
class ChatHistory {
-io: ChatStyle
+run(UuidInterface) void
}
class StreamChunkCallback {
+__invoke(?string, bool, ?string) void
}
ChatServiceInterface -- SessionInterface
ChatSession -- ChatServiceInterface
ChatSession -- ChatHistoryRepositoryInterface
ChatHistory -- ChatHistoryRepositoryInterface
ChatHistory -- ChatServiceInterface
```

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"symfony/console": "^7.0",
"psr/event-dispatcher": "^1.0",
"ramsey/uuid": "^4.0",
"llm-agents/agents": "^1.2",
"llm-agents/agents": "^1.3",
"llm-agents/openai-client": "^1.0",
"llm-agents/json-schema-mapper": "^1.0"
},
Expand Down
44 changes: 30 additions & 14 deletions src/AgentExecutorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

namespace LLM\Agents\Chat;

use LLM\Agents\Agent\AgentExecutor;
use LLM\Agents\Agent\Exception\InvalidBuilderStateException;
use LLM\Agents\Agent\Execution;
use LLM\Agents\AgentExecutor\ExecutorInterceptorInterface;
use LLM\Agents\AgentExecutor\ExecutorInterface;
use LLM\Agents\LLM\OptionsFactoryInterface;
use LLM\Agents\LLM\OptionsInterface;
use LLM\Agents\LLM\Prompt\Chat\MessagePrompt;
Expand All @@ -22,9 +23,11 @@ final class AgentExecutorBuilder
private ?string $agentKey = null;
private PromptContextInterface $promptContext;
private OptionsInterface $options;
/** @var ExecutorInterceptorInterface[] */
private array $interceptors = [];

public function __construct(
private readonly AgentExecutor $executor,
private readonly ExecutorInterface $executor,
OptionsFactoryInterface $optionsFactory,
) {
$this->options = $optionsFactory->create();
Expand Down Expand Up @@ -84,6 +87,15 @@ public function withMessage(MessagePrompt $message): self
return $this;
}

public function withInterceptor(ExecutorInterceptorInterface ...$interceptors): self
{
$self = clone $this;

$self->interceptors = \array_merge($this->interceptors, $interceptors);

return $this;
}

public function ask(string|\Stringable $prompt): Execution
{
if ($this->agentKey === null) {
Expand All @@ -98,12 +110,14 @@ public function ask(string|\Stringable $prompt): Execution
);
}

$execution = $this->executor->execute(
agent: $this->agentKey,
prompt: $prompt,
options: $this->options,
promptContext: $this->promptContext,
);
$execution = $this->executor
->withInterceptor(...$this->interceptors)
->execute(
agent: $this->agentKey,
prompt: $prompt,
options: $this->options,
promptContext: $this->promptContext,
);

$this->prompt = $execution->prompt;

Expand All @@ -116,12 +130,14 @@ public function continue(): Execution
throw new InvalidBuilderStateException('Agent key is required');
}

$execution = $this->executor->execute(
agent: $this->agentKey,
prompt: $this->prompt,
options: $this->options,
promptContext: $this->promptContext,
);
$execution = $this->executor
->withInterceptor(...$this->interceptors)
->execute(
agent: $this->agentKey,
prompt: $this->prompt,
options: $this->options,
promptContext: $this->promptContext,
);

$this->prompt = $execution->prompt;

Expand Down

0 comments on commit 6386800

Please sign in to comment.