Skip to content

Commit 5435767

Browse files
Merge pull request #21 from carmelosantana/chore_cleanup-sse-stream
Improves SseStreamParser for handling various SSE scenarios
2 parents 951c9fc + 209c7ba commit 5435767

25 files changed

+1018
-547
lines changed

README.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ graph LR
3030
- **Streaming + tool calls** — all providers support streaming with assembled tool call deltas
3131
- **Structured output** — extract typed data from LLMs via JSON mode (OpenAI) or tool-use trick (Anthropic)
3232
- **Image input** — send images to vision models via base64, URL, or file path (auto-converts between provider formats; URLs pre-downloaded for providers that don't support them natively)
33-
- **Bundled agents**`FileAgent`, `WebAgent`, `CodeAgent` ready to use out of the box
33+
- **Bundled agents**`FileAgent`, `WebAgent`, `CodeAgent` for quick prototyping *(deprecated — use `AbstractAgent` with toolkits for production)*
3434
- **Composable toolkits** — filesystem, web, shell, and memory toolkits that snap onto any agent
3535
- **Context window management** — automatic conversation pruning when approaching token limits
3636
- **Observer pattern** — attach `SplObserver` to watch agent lifecycle events in real time
3737
- **Security by default** — path traversal protection, SSRF blocking, shell injection detection
3838
- **OpenClaw config** — centralized model routing with aliases, fallbacks, and per-provider settings
39+
- **PSR-3 logging** — optional `LoggerInterface` on all providers for diagnostic visibility
3940
- **Zero framework coupling** — depends only on `symfony/http-client` and `psr/log`
4041

4142
## Provider Feature Matrix
@@ -78,26 +79,44 @@ declare(strict_types=1);
7879

7980
require 'vendor/autoload.php';
8081

81-
use CarmeloSantana\PHPAgents\Agent\FileAgent;
82+
use CarmeloSantana\PHPAgents\Agent\AbstractAgent;
83+
use CarmeloSantana\PHPAgents\Contract\ProviderInterface;
8284
use CarmeloSantana\PHPAgents\Provider\OllamaProvider;
85+
use CarmeloSantana\PHPAgents\Toolkit\FilesystemToolkit;
8386
use CarmeloSantana\PHPAgents\Message\UserMessage;
8487

85-
$provider = new OllamaProvider(model: 'llama3.2');
88+
// Create a simple file agent using AbstractAgent + FilesystemToolkit
89+
final class MyFileAgent extends AbstractAgent
90+
{
91+
public function __construct(ProviderInterface $provider, string $rootPath)
92+
{
93+
parent::__construct($provider);
94+
$this->addToolkit(new FilesystemToolkit($rootPath, readOnly: true));
95+
}
8696

87-
$agent = new FileAgent(
88-
provider: $provider,
89-
rootPath: getcwd(),
90-
readOnly: true,
91-
);
97+
public function instructions(): string
98+
{
99+
return 'You are a helpful file assistant. Read and summarize files when asked.';
100+
}
101+
102+
public function name(): string
103+
{
104+
return 'FileAgent';
105+
}
106+
}
92107

108+
$provider = new OllamaProvider(model: 'llama3.2');
109+
$agent = new MyFileAgent($provider, getcwd());
93110
$output = $agent->run(new UserMessage('Summarize the README.md file.'));
94111

95112
echo $output->content . "\n";
96113
```
97114

98115
> Make sure Ollama is running: `ollama serve` and a model is pulled: `ollama pull llama3.2`
99116
100-
## Bundled Agents
117+
## Bundled Agents (Deprecated)
118+
119+
> **Note:** Bundled agents are deprecated and will be removed in 1.0. Use `AbstractAgent` with composable toolkits instead (see [Creating Custom Agents](#creating-custom-agents)).
101120
102121
| Agent | Description | Toolkits |
103122
| ----------- | ------------------------------------------------------------------ | ------------------------------------ |

composer.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@
4747
"minimum-stability": "stable",
4848
"prefer-stable": true,
4949
"extra": {
50-
"php-agents": {
51-
"agents": [
52-
"CarmeloSantana\\PHPAgents\\Agent\\FileAgent",
53-
"CarmeloSantana\\PHPAgents\\Agent\\WebAgent",
54-
"CarmeloSantana\\PHPAgents\\Agent\\CodeAgent"
55-
]
56-
}
50+
"php-agents": {}
5751
}
5852
}

docs/AGENTS.md

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,39 +102,42 @@ flowchart TD
102102
LOOP --> NOTIFY_DONE[notify: agent.done]
103103
```
104104
105-
## Built-in Agents
105+
## Built-in Agents (Deprecated)
106106
107-
### FileAgent
107+
> **Deprecated.** `FileAgent`, `WebAgent`, and `CodeAgent` will be removed in 1.0. Use `AbstractAgent` with explicit toolkits instead — this gives you full control over which tools are available.
108108
109-
Pre-loaded with `FilesystemToolkit`. Good for file manipulation tasks.
109+
### FileAgent
110110
111111
```php
112-
use CarmeloSantana\PHPAgents\Agent\FileAgent;
113-
114-
$agent = new FileAgent(provider: $provider);
115-
// Has: read_file, write_file, list_directory, search_files, file_info, create_directory, delete_file
112+
// DEPRECATED — use AbstractAgent + FilesystemToolkit instead:
113+
$agent = new class($provider) extends AbstractAgent {
114+
public function name(): string { return 'File Agent'; }
115+
public function instructions(): string { return 'You manage files.'; }
116+
};
117+
$agent->addToolkit(new FilesystemToolkit('/path/to/root'));
116118
```
117119
118120
### WebAgent
119121
120-
Pre-loaded with `WebToolkit`. Good for web research and API interaction.
121-
122122
```php
123-
use CarmeloSantana\PHPAgents\Agent\WebAgent;
124-
125-
$agent = new WebAgent(provider: $provider);
126-
// Has: http_request, web_search
123+
// DEPRECATED — use AbstractAgent + WebToolkit instead:
124+
$agent = new class($provider) extends AbstractAgent {
125+
public function name(): string { return 'Web Agent'; }
126+
public function instructions(): string { return 'You research the web.'; }
127+
};
128+
$agent->addToolkit(new WebToolkit());
127129
```
128130
129131
### CodeAgent
130132
131-
Pre-loaded with `FilesystemToolkit` and `ShellToolkit`. Good for coding tasks.
132-
133133
```php
134-
use CarmeloSantana\PHPAgents\Agent\CodeAgent;
135-
136-
$agent = new CodeAgent(provider: $provider);
137-
// Has: file tools + execute_command
134+
// DEPRECATED — use AbstractAgent + FilesystemToolkit + ShellToolkit instead:
135+
$agent = new class($provider) extends AbstractAgent {
136+
public function name(): string { return 'Code Agent'; }
137+
public function instructions(): string { return 'You write and execute code.'; }
138+
};
139+
$agent->addToolkit(new FilesystemToolkit('/path/to/root'));
140+
$agent->addToolkit(new ShellToolkit());
138141
```
139142
140143
## Adding Tools and Toolkits
@@ -145,7 +148,7 @@ $agent->addTool($myTool);
145148
146149
// Add a toolkit (registers all its tools + injects guidelines)
147150
$agent->addToolkit(new WebToolkit());
148-
$agent->addToolkit(new MemoryToolkit(memory: $memory));
151+
$agent->addToolkit(new MemoryToolkit(memory: $memory)); // Note: MemoryToolkit is deprecated — use a database-backed implementation
149152
```
150153
151154
Tools from toolkits are merged with directly-added tools. Guidelines from all toolkits are concatenated and appended to the system prompt.
@@ -218,7 +221,7 @@ final class TimeoutToken implements CancellationTokenInterface
218221
}
219222
}
220223
221-
$agent = new FileAgent(
224+
$agent = new MyAgent(
222225
provider: $provider,
223226
cancellationToken: new TimeoutToken(30),
224227
);
@@ -254,7 +257,7 @@ final class QueuedInputProvider implements PendingInputProviderInterface
254257
}
255258
256259
$inputProvider = new QueuedInputProvider();
257-
$agent = new FileAgent(
260+
$agent = new MyAgent(
258261
provider: $provider,
259262
pendingInputProvider: $inputProvider,
260263
);
@@ -277,7 +280,7 @@ $contextWindow = new ContextWindow(
277280
reservedTokens: 4_000, // Space for the response
278281
);
279282

280-
$agent = new FileAgent(
283+
$agent = new MyAgent(
281284
provider: $provider,
282285
contextWindow: $contextWindow,
283286
);

docs/ARCHITECTURE.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ graph TB
1313
subgraph "php-agents Framework"
1414
subgraph "Agent Layer"
1515
AA[AbstractAgent]
16-
FA[FileAgent]
17-
WA[WebAgent]
18-
CA[CodeAgent]
16+
FA[FileAgent<br/><i>deprecated</i>]
17+
WA[WebAgent<br/><i>deprecated</i>]
18+
CA[CodeAgent<br/><i>deprecated</i>]
1919
FA --> AA
2020
WA --> AA
2121
CA --> AA
@@ -38,7 +38,7 @@ graph TB
3838
FST[FilesystemToolkit]
3939
WT[WebToolkit]
4040
ST[ShellToolkit]
41-
MT[MemoryToolkit]
41+
MT[MemoryToolkit<br/><i>deprecated</i>]
4242
T --> TI
4343
FST --> TK
4444
WT --> TK
@@ -130,11 +130,14 @@ flowchart TD
130130
Agents are composed from providers, toolkits, and policies rather than inheriting complex behavior:
131131

132132
```php
133-
$agent = new FileAgent(
133+
$agent = new class(
134134
provider: new OllamaProvider(model: 'llama3.2'),
135135
maxIterations: 10,
136136
executionPolicy: new MyPolicy(),
137-
);
137+
) extends AbstractAgent {
138+
public function name(): string { return 'My Agent'; }
139+
public function instructions(): string { return 'You help users.'; }
140+
};
138141
```
139142

140143
### Interface-Driven Extensibility
@@ -150,7 +153,7 @@ Every major component has an interface contract. You can replace any layer:
150153
| `CancellationTokenInterface` | Cooperative cancellation | `NullCancellationToken` |
151154
| `PendingInputProviderInterface` | External input injection | `NullPendingInputProvider` |
152155
| `ContextWindowInterface` | Token budget tracking | `ContextWindow` |
153-
| `MemoryInterface` | Persistent memory | `FileMemory` |
156+
| `MemoryInterface` | Persistent memory | `FileMemory` *(deprecated)* |
154157
| `VectorStoreInterface` | Similarity search | `InMemoryVectorStore` |
155158
| `EmbeddingProviderInterface` | Text → vector | Ollama, OpenAI |
156159
| `TokenCounterInterface` | Token counting | `HeuristicCounter`, `TiktokenCounter` |
@@ -379,12 +382,12 @@ flowchart LR
379382
```mermaid
380383
graph TB
381384
subgraph "Agent-Facing"
382-
MTK[MemoryToolkit]
385+
MTK[MemoryToolkit<br/><i>deprecated</i>]
383386
end
384387
385388
subgraph "Storage Layer"
386389
MI[MemoryInterface]
387-
FM[FileMemory<br/>Markdown file]
390+
FM[FileMemory<br/><i>deprecated</i>]
388391
end
389392
390393
subgraph "Vector Search"

docs/GETTING-STARTED.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@ composer require hkulekci/qdrant
4141

4242
require __DIR__ . '/vendor/autoload.php';
4343

44-
use CarmeloSantana\PHPAgents\Agent\FileAgent;
44+
use CarmeloSantana\PHPAgents\Agent\AbstractAgent;
4545
use CarmeloSantana\PHPAgents\Message\UserMessage;
4646
use CarmeloSantana\PHPAgents\Provider\OllamaProvider;
47+
use CarmeloSantana\PHPAgents\Toolkit\FilesystemToolkit;
4748

48-
$agent = new FileAgent(
49-
provider: new OllamaProvider(model: 'llama3.2'),
50-
);
49+
$agent = new class(provider: new OllamaProvider(model: 'llama3.2')) extends AbstractAgent {
50+
public function name(): string { return 'File Agent'; }
51+
public function instructions(): string { return 'You manage files in the workspace.'; }
52+
};
53+
$agent->addToolkit(new FilesystemToolkit(__DIR__));
5154

5255
$output = $agent->run(new UserMessage('List the files in the current directory'));
5356
echo $output->content;
@@ -100,14 +103,16 @@ declare(strict_types=1);
100103

101104
require __DIR__ . '/vendor/autoload.php';
102105

103-
use CarmeloSantana\PHPAgents\Agent\FileAgent;
106+
use CarmeloSantana\PHPAgents\Agent\AbstractAgent;
104107
use CarmeloSantana\PHPAgents\Message\UserMessage;
105108
use CarmeloSantana\PHPAgents\Provider\OllamaProvider;
109+
use CarmeloSantana\PHPAgents\Toolkit\FilesystemToolkit;
106110

107-
$agent = new FileAgent(
108-
provider: new OllamaProvider(model: 'llama3.2'),
109-
maxIterations: 15,
110-
);
111+
$agent = new class(provider: new OllamaProvider(model: 'llama3.2'), maxIterations: 15) extends AbstractAgent {
112+
public function name(): string { return 'File Agent'; }
113+
public function instructions(): string { return 'You manage files in the workspace.'; }
114+
};
115+
$agent->addToolkit(new FilesystemToolkit(__DIR__));
111116

112117
$output = $agent->run(
113118
new UserMessage('Create a file called hello.txt with the contents "Hello, World!"'),
@@ -117,7 +122,7 @@ echo $output->content;
117122
// Agent creates the file using FilesystemToolkit tools, then responds
118123
```
119124

120-
The `FileAgent` comes pre-loaded with `FilesystemToolkit` (file read/write/list/search/delete) tools. The agent loop works like this:
125+
The agent is pre-loaded with `FilesystemToolkit` (file read/write/list/search/delete) tools. The agent loop works like this:
121126

122127
1. Your message is sent to the LLM with tool definitions
123128
2. The LLM decides which tool to call (e.g., `write_file`)

docs/MEMORY.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Memory
22

3+
> **Deprecated.** `FileMemory`, `MemoryToolkit`, and the `MemoryInterface` in php-agents will be removed in 1.0. For production use, implement a database-backed memory store (e.g. Coqui's SQLite-backed `MemoryStore`). The vector store layer (`VectorStoreInterface`, `InMemoryVectorStore`, embedding providers) is **not** deprecated.
4+
35
php-agents provides a layered memory system: simple key-value storage, persistent file-backed memory, and vector similarity search for semantic retrieval.
46

57
## Architecture
@@ -58,7 +60,9 @@ interface MemoryInterface
5860
}
5961
```
6062

61-
## FileMemory
63+
## FileMemory (Deprecated)
64+
65+
> **Deprecated.** Use a database-backed implementation instead. Will be removed in 1.0.
6266
6367
Persists memories to a Markdown file. Each entry is a third-level heading with the key as the title and the value as the body:
6468

@@ -117,7 +121,9 @@ sequenceDiagram
117121

118122
`FileMemory::search()` performs simple substring matching on keys and values. For semantic search, use a vector store.
119123

120-
## MemoryToolkit
124+
## MemoryToolkit (Deprecated)
125+
126+
> **Deprecated.** Use a database-backed memory toolkit instead. Will be removed in 1.0.
121127
122128
Exposes memory operations as tools the agent can use:
123129

docs/PROVIDERS.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,61 @@ Both URL and base64 data URI images are supported.
469469
|----------|---------|
470470
| `MISTRAL_API_KEY` | `MistralProvider` |
471471

472+
## Shared Provider Utilities
473+
474+
### PSR-3 Logging
475+
476+
All providers accept an optional `Psr\Log\LoggerInterface` via their constructor. When provided, errors in non-critical paths (model listing, health checks, image downloads) are logged instead of silently swallowed:
477+
478+
```php
479+
use Monolog\Logger;
480+
use Monolog\Handler\StreamHandler;
481+
482+
$logger = new Logger('providers');
483+
$logger->pushHandler(new StreamHandler('php://stderr'));
484+
485+
$provider = new OllamaProvider(
486+
model: 'llama3.2',
487+
logger: $logger,
488+
);
489+
```
490+
491+
### SseStreamParser
492+
493+
Shared SSE line-buffering parser used by all streaming providers. Handles chunk boundary splits, `[DONE]` sentinels, and malformed JSON gracefully:
494+
495+
```php
496+
use CarmeloSantana\PHPAgents\Provider\SseStreamParser;
497+
498+
$parser = new SseStreamParser($httpClient, $response);
499+
500+
foreach ($parser->events() as $payload) {
501+
// $payload is a decoded associative array from each `data: {...}` line
502+
}
503+
```
504+
505+
Custom providers can use `SseStreamParser` directly instead of implementing their own SSE buffering logic.
506+
507+
### SchemaUtils
508+
509+
Static helpers for JSON Schema normalization. Each method operates on a single schema node — providers handle their own recursion:
510+
511+
| Method | Purpose |
512+
|--------|---------|
513+
| `stripKeywords(array $schema, array $keywords)` | Remove unsupported keywords from a schema node |
514+
| `flattenCombinator(array $schema, string $combinator)` | Replace `anyOf`/`oneOf`/`allOf` with the first non-null variant |
515+
| `demoteConstraints(array $schema, array $templates)` | Move constraint metadata (minLength, enum, etc.) into the description field |
516+
517+
```php
518+
use CarmeloSantana\PHPAgents\Provider\SchemaUtils;
519+
520+
// Strip keywords a provider doesn't support
521+
$schema = SchemaUtils::stripKeywords($schema, ['additionalProperties', '$schema', 'default']);
522+
523+
// Flatten anyOf to a single type (for providers that don't support union types)
524+
$schema = SchemaUtils::flattenCombinator($schema, 'anyOf');
525+
```
526+
472527
## Creating a Custom Provider
473528

474529
Implement `ProviderInterface` or extend `AbstractProvider`:

0 commit comments

Comments
 (0)