Skip to content

Commit 2d730af

Browse files
committed
wip
1 parent 153161b commit 2d730af

32 files changed

+543
-227
lines changed

scratchpad.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@
8181
new UserMessage('What is the capital of France?'),
8282
]);
8383

84+
// Run an agent with only input parameters
85+
$agent = Cortex::agent('joke_generator')
86+
->prompt('You are a joke generator. You generate jokes about {topic}.')
87+
->llm('ollama:gpt-oss:20b')
88+
->build();
89+
90+
$result = $agent->invoke(input: [
91+
'topic' => 'programming',
92+
]);
93+
8494
// Run a task
8595
$jokeGenerator = Cortex::task('joke_generator')
8696
->llm('ollama', 'llama3.2')

src/Agents/AbstractAgent.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cortex\Agents;
6+
7+
use Cortex\Agents\Agent;
8+
use Cortex\LLM\Contracts\LLM;
9+
use Cortex\LLM\Data\ChatResult;
10+
use Cortex\LLM\Data\ChatStreamResult;
11+
use Cortex\JsonSchema\Types\ObjectSchema;
12+
use Cortex\Agents\Contracts\Agent as AgentContract;
13+
14+
abstract class AbstractAgent implements AgentContract
15+
{
16+
public function llm(): ?LLM
17+
{
18+
return null;
19+
}
20+
21+
public function tools(): array
22+
{
23+
return [];
24+
}
25+
26+
public function output(): ObjectSchema|string|null
27+
{
28+
return null;
29+
}
30+
31+
public function maxSteps(): int
32+
{
33+
return 5;
34+
}
35+
36+
public function strict(): bool
37+
{
38+
return false;
39+
}
40+
41+
public function initialPromptVariables(): array
42+
{
43+
return [];
44+
}
45+
46+
/**
47+
* Build the agent instance using the methods defined in this class.
48+
*/
49+
public static function make(array $parameters = []): Agent
50+
{
51+
$builder = app(static::class, $parameters);
52+
53+
return new Agent(
54+
name: $builder->name(),
55+
prompt: $builder->prompt(),
56+
llm: $builder->llm(),
57+
tools: $builder->tools(),
58+
output: $builder->output(),
59+
initialPromptVariables: $builder->initialPromptVariables(),
60+
maxSteps: $builder->maxSteps(),
61+
strict: $builder->strict(),
62+
);
63+
}
64+
65+
/**
66+
* Convenience method to invoke the built agent instance..
67+
*
68+
* @param array<int, \Cortex\LLM\Contracts\Message> $messages
69+
* @param array<string, mixed> $input
70+
*/
71+
public static function invoke(array $messages = [], array $input = []): ChatResult
72+
{
73+
return static::make()->invoke($messages, $input);
74+
}
75+
76+
/**
77+
* Convenience method to stream from the built agent instance.
78+
*
79+
* @param array<int, \Cortex\LLM\Contracts\Message> $messages
80+
* @param array<string, mixed> $input
81+
*/
82+
public static function stream(array $messages = [], array $input = []): ChatStreamResult
83+
{
84+
return static::make()->stream($messages, $input);
85+
}
86+
}

src/Agents/Agent.php

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
use Cortex\Support\Utils;
1010
use Cortex\LLM\Data\Usage;
1111
use Cortex\Prompts\Prompt;
12+
use Cortex\Contracts\ToolKit;
1213
use Cortex\Memory\ChatMemory;
1314
use Cortex\Contracts\Pipeable;
15+
use Cortex\LLM\Data\ChatResult;
1416
use Cortex\LLM\Enums\ToolChoice;
17+
use Cortex\Memory\Contracts\Store;
1518
use Cortex\Agents\Stages\AppendUsage;
1619
use Cortex\LLM\Data\ChatStreamResult;
1720
use Cortex\Memory\Stores\InMemoryStore;
@@ -25,70 +28,48 @@
2528
use Cortex\Prompts\Builders\ChatPromptBuilder;
2629
use Cortex\LLM\Data\Messages\MessagePlaceholder;
2730
use Cortex\Prompts\Templates\ChatPromptTemplate;
31+
use Cortex\Contracts\ChatMemory as ChatMemoryContract;
2832

2933
class Agent implements Pipeable
3034
{
3135
protected LLMContract $llm;
3236

3337
protected ChatPromptTemplate $prompt;
3438

35-
protected ChatMemory $memory;
39+
protected ChatMemoryContract $memory;
3640

3741
protected Usage $usage;
3842

3943
/**
4044
* @param class-string|\Cortex\JsonSchema\Types\ObjectSchema $output
41-
* @param array<int, \Cortex\LLM\Contracts\Tool|\Closure|string> $tools
45+
* @param array<int, \Cortex\LLM\Contracts\Tool|\Closure|string>|\Cortex\Contracts\ToolKit $tools
4246
* @param array<string, mixed> $initialPromptVariables
4347
*/
4448
public function __construct(
4549
protected string $name,
4650
ChatPromptTemplate|ChatPromptBuilder|string|null $prompt = null,
4751
LLMContract|string|null $llm = null,
4852
protected ?string $description = null,
49-
protected array $tools = [],
53+
protected array|ToolKit $tools = [],
5054
protected ToolChoice|string $toolChoice = ToolChoice::Auto,
5155
protected ObjectSchema|string|null $output = null,
56+
protected ?Store $memoryStore = null,
5257
protected array $initialPromptVariables = [],
5358
protected int $maxSteps = 5,
5459
protected bool $strict = true,
5560
) {
56-
if ($prompt !== null) {
57-
$this->prompt = match (true) {
58-
is_string($prompt) => Prompt::builder('chat')
59-
->messages([new SystemMessage($prompt)])
60-
->strict($this->strict)
61-
->initialVariables($this->initialPromptVariables)
62-
->build(),
63-
$prompt instanceof ChatPromptBuilder => $prompt->build(),
64-
default => $prompt,
65-
};
66-
67-
$this->prompt->addMessage(new MessagePlaceholder('messages'));
68-
} else {
69-
$this->prompt = new ChatPromptTemplate([
70-
new MessagePlaceholder('messages'),
71-
], $this->initialPromptVariables);
72-
}
73-
74-
$this->memory = new ChatMemory(new InMemoryStore($this->prompt->messages->withoutPlaceholders()));
61+
$this->prompt = self::buildPromptTemplate($prompt, $strict, $initialPromptVariables);
62+
$this->memory = self::buildMemory($this->prompt, $this->memoryStore);
63+
$this->llm = self::buildLLM(
64+
$this->prompt,
65+
$this->name,
66+
$llm,
67+
$this->tools,
68+
$this->toolChoice,
69+
$this->output,
70+
$this->strict,
71+
);
7572
$this->usage = Usage::empty();
76-
77-
$this->llm = $llm !== null
78-
? Utils::toLLM($llm)
79-
: $this->prompt->metadata?->toLLM() ?? Utils::toLLM($llm);
80-
81-
if ($this->tools !== []) {
82-
$this->llm->withTools($this->tools, $this->toolChoice);
83-
}
84-
85-
if ($this->output !== null) {
86-
$this->llm->withStructuredOutput(
87-
output: $this->output,
88-
name: $this->name,
89-
strict: $this->strict,
90-
);
91-
}
9273
}
9374

9475
public function pipeline(bool $shouldParseOutput = true): Pipeline
@@ -124,7 +105,7 @@ public function executionPipeline(bool $shouldParseOutput = true): Pipeline
124105
* @param array<int, \Cortex\LLM\Contracts\Message> $messages
125106
* @param array<string, mixed> $input
126107
*/
127-
public function invoke(array $messages = [], array $input = []): mixed
108+
public function invoke(array $messages = [], array $input = []): ChatResult
128109
{
129110
// $this->id ??= $this->generateId();
130111
$this->memory->setVariables([
@@ -135,10 +116,13 @@ public function invoke(array $messages = [], array $input = []): mixed
135116
$messages = $this->memory->getMessages()->merge($messages);
136117
$this->memory->setMessages($messages);
137118

138-
return $this->pipeline()->invoke([
119+
/** @var \Cortex\LLM\Data\ChatResult $result */
120+
$result = $this->pipeline()->invoke([
139121
...$input,
140122
'messages' => $this->memory->getMessages(),
141123
]);
124+
125+
return $result;
142126
}
143127

144128
/**
@@ -185,7 +169,7 @@ public function handlePipeable(mixed $payload, Closure $next): mixed
185169
default => throw new PipelineException('Invalid input for agent.'),
186170
};
187171

188-
return $next($this->invoke($payload));
172+
return $next($this->invoke(input: $payload));
189173
}
190174

191175
public function getName(): string
@@ -208,7 +192,9 @@ public function getPrompt(): ChatPromptTemplate
208192
*/
209193
public function getTools(): array
210194
{
211-
return $this->tools;
195+
return $this->tools instanceof ToolKit
196+
? $this->tools->getTools()
197+
: $this->tools;
212198
}
213199

214200
public function getLLM(): LLMContract
@@ -225,4 +211,74 @@ public function getUsage(): Usage
225211
{
226212
return $this->usage;
227213
}
214+
215+
/**
216+
* Build the prompt template for the agent.
217+
*/
218+
protected static function buildPromptTemplate(
219+
ChatPromptTemplate|ChatPromptBuilder|string|null $prompt = null,
220+
bool $strict = true,
221+
array $initialPromptVariables = [],
222+
): ChatPromptTemplate {
223+
$promptTemplate = match (true) {
224+
$prompt === null => new ChatPromptTemplate([], $initialPromptVariables),
225+
is_string($prompt) => Prompt::builder('chat')
226+
->messages([new SystemMessage($prompt)])
227+
->strict($strict)
228+
->initialVariables($initialPromptVariables)
229+
->build(),
230+
$prompt instanceof ChatPromptBuilder => $prompt->build(),
231+
default => $prompt,
232+
};
233+
234+
$promptTemplate->addMessage(new MessagePlaceholder('messages'));
235+
236+
return $promptTemplate;
237+
}
238+
239+
/**
240+
* Build the memory instance for the agent.
241+
*/
242+
protected static function buildMemory(ChatPromptTemplate $prompt, ?Store $memoryStore = null): ChatMemoryContract
243+
{
244+
$store = $memoryStore ?? new InMemoryStore();
245+
$store->setMessages($prompt->messages->withoutPlaceholders());
246+
247+
return new ChatMemory($store);
248+
}
249+
250+
/**
251+
* Build the LLM instance for the agent.
252+
*/
253+
protected static function buildLLM(
254+
ChatPromptTemplate $prompt,
255+
string $name,
256+
?LLMContract $llm = null,
257+
array $tools = [],
258+
ToolChoice|string $toolChoice = ToolChoice::Auto,
259+
ObjectSchema|string|null $output = null,
260+
bool $strict = true,
261+
): LLMContract {
262+
$llm = $llm !== null
263+
? Utils::llm($llm)
264+
: $prompt->metadata?->llm() ?? Utils::llm($llm);
265+
266+
// The LLM instance will already contain the configuration from
267+
// the prompt metadata if it was provided.
268+
// Below those can be overridden.
269+
270+
if ($tools !== []) {
271+
$llm->withTools($tools, $toolChoice);
272+
}
273+
274+
if ($output !== null) {
275+
$llm->withStructuredOutput(
276+
output: $output,
277+
name: $name,
278+
strict: $strict,
279+
);
280+
}
281+
282+
return $llm;
283+
}
228284
}

src/Agents/Contracts/Agent.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cortex\Agents\Contracts;
6+
7+
use Cortex\LLM\Contracts\LLM;
8+
use Cortex\JsonSchema\Types\ObjectSchema;
9+
use Cortex\Prompts\Builders\ChatPromptBuilder;
10+
use Cortex\Prompts\Templates\ChatPromptTemplate;
11+
12+
interface Agent
13+
{
14+
/**
15+
* Specify the name of the agent.
16+
*/
17+
public function name(): string;
18+
19+
/**
20+
* Specify the prompt for the agent.
21+
*/
22+
public function prompt(): ChatPromptTemplate|ChatPromptBuilder|string;
23+
24+
/**
25+
* Specify the LLM for the agent.
26+
*/
27+
public function llm(): ?LLM;
28+
29+
/**
30+
* Specify the tools for the agent.
31+
*
32+
* @return array<int, \Cortex\LLM\Contracts\Tool|\Closure|class-string<\Cortex\LLM\Contracts\Tool>>
33+
*/
34+
public function tools(): array;
35+
36+
/**
37+
* Specify the output schema or class string that the LLM should output.
38+
*
39+
* @return class-string<\BackedEnum>|class-string|Cortex\JsonSchema\Types\ObjectSchema|null
40+
*/
41+
public function output(): ObjectSchema|string|null;
42+
43+
/**
44+
* Specify the maximum number of steps the agent should take.
45+
*/
46+
public function maxSteps(): int;
47+
48+
/**
49+
* Specify whether the agent should be strict about the input and output.
50+
*/
51+
public function strict(): bool;
52+
53+
/**
54+
* Specify the initial prompt variables.
55+
*/
56+
public function initialPromptVariables(): array;
57+
}

0 commit comments

Comments
 (0)