Skip to content

Commit 956e515

Browse files
authored
Merge pull request #16 from MaestroError/structured-output-support-for-chat-command
Structured output in console for agent:chat command
2 parents 9cd79dd + b7335d5 commit 956e515

22 files changed

+115
-71
lines changed

.github/workflows/fix-php-code-style-issues.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55
paths:
66
- '**.php'
7-
branches-ignore:
7+
branches:
88
- 'main'
99

1010
permissions:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ protected $responseSchema = [
808808
'description' => 'Temperature in degrees'
809809
],
810810
],
811-
'required' => ['temperature']
811+
'required' => ['temperature'],
812812
'additionalProperties' => false,
813813
],
814814
'strict' => true,
@@ -847,7 +847,7 @@ public function structuredOutput()
847847
'description' => '5-day forecast'
848848
]
849849
],
850-
'required' => ['temperature', 'conditions']
850+
'required' => ['temperature', 'conditions'],
851851
'additionalProperties' => false,
852852
],
853853
'strict' => true,
@@ -936,7 +936,7 @@ Driver that can be used with any OpenAI-compatible provider. For example configu
936936
'ollama' => [
937937
'name' => 'ollama-local',
938938
'driver' => \LarAgent\Drivers\OpenAi\OpenAiCompatible::class,
939-
'api_key' => '-',
939+
'api_key' => 'ollama',
940940
'api_url' => "http://localhost:11434/v1",
941941
'default_context_window' => 50000,
942942
'default_max_completion_tokens' => 100,

src/Agent.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ class Agent
109109

110110
public function __construct($key)
111111
{
112+
$this->setupProviderData();
112113
$this->setChatSessionId($key);
113-
$this->setup();
114+
$this->setupChatHistory();
114115
$this->onInitialize();
115116
}
116117

@@ -352,7 +353,7 @@ public function getChatKey(): string
352353

353354
/**
354355
* Get all chat keys associated with this agent class
355-
*
356+
*
356357
* @return array Array of chat keys filtered by agent class name
357358
*/
358359
public function getChatKeys(): array
@@ -361,7 +362,7 @@ public function getChatKeys(): array
361362
$agentClass = class_basename(static::class);
362363

363364
return array_filter($keys, function ($key) use ($agentClass) {
364-
return str_starts_with($key, $agentClass . '_');
365+
return str_starts_with($key, $agentClass.'_');
365366
});
366367
}
367368

@@ -616,12 +617,6 @@ protected function registerEvents(): void
616617
});
617618
}
618619

619-
protected function setup(): void
620-
{
621-
$this->setupProviderData();
622-
$this->setupChatHistory();
623-
}
624-
625620
protected function setupBeforeRespond(): void
626621
{
627622
$this->setupAgent();
@@ -634,7 +629,6 @@ protected function setupChatHistory(): void
634629
$this->setChatHistory($chatHistory);
635630
}
636631

637-
638632
/**
639633
* Builds tools from methods annotated with #[Tool] attribute
640634
* Example:

src/Commands/AgentChatClearCommand.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function handle()
2020
$agentClass = "\\App\\Agents\\{$agentName}";
2121
if (! class_exists($agentClass)) {
2222
$this->error("Agent not found: {$agentName}");
23+
2324
return 1;
2425
}
2526
}
@@ -28,7 +29,7 @@ public function handle()
2829
$agent = $agentClass::for('temp');
2930
$chatKeys = $agent->getChatKeys();
3031

31-
if (!empty($chatKeys)) {
32+
if (! empty($chatKeys)) {
3233
// Clear each chat history
3334
foreach ($chatKeys as $key) {
3435
// Create new chat history with save_chat_keys disabled
@@ -39,6 +40,7 @@ public function handle()
3940
}
4041

4142
$this->info("Successfully cleared chat history for agent: {$agentName}");
43+
4244
return 0;
4345
}
4446
}

src/Commands/AgentChatCommand.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,54 @@ public function handle()
4545
try {
4646
$response = $agent->respond($message);
4747
$this->line("\n<comment>{$agentName}:</comment>");
48-
$this->line($response."\n");
48+
$this->formatResponse($response);
49+
$this->line("\n");
4950
} catch (\Exception $e) {
5051
$this->error('Error: '.$e->getMessage());
5152

5253
return 1;
5354
}
5455
}
5556
}
57+
58+
/**
59+
* Format and display the agent's response
60+
*
61+
* @param mixed $response
62+
*/
63+
protected function formatResponse($response): void
64+
{
65+
if (is_array($response)) {
66+
// Check if it's a single array with a key containing a list
67+
if (count($response) === 1 && isset(array_values($response)[0]) && is_array(array_values($response)[0])) {
68+
$key = array_key_first($response);
69+
$values = array_values($response)[0];
70+
71+
if (array_is_list($values)) {
72+
// If the first item is an object/array, use its keys as headers
73+
if (! empty($values) && is_array($values[0])) {
74+
$headers = array_keys((array) $values[0]);
75+
$rows = array_map(function ($item) {
76+
return array_map(function ($value) {
77+
return is_array($value) ? json_encode($value) : (string) $value;
78+
}, (array) $item);
79+
}, $values);
80+
$this->info($key.':');
81+
$this->table($headers, $rows);
82+
} else {
83+
// For simple arrays, show numbered list
84+
$this->info($key.':');
85+
$this->table(['#', $key], array_map(fn ($i, $item) => [$i + 1, $item], array_keys($values), $values));
86+
}
87+
88+
return;
89+
}
90+
}
91+
92+
// Otherwise format as JSON with proper indentation
93+
$this->line(json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
94+
} else {
95+
$this->line($response);
96+
}
97+
}
5698
}

src/Commands/AgentChatRemoveCommand.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function handle()
2020
$agentClass = "\\App\\Agents\\{$agentName}";
2121
if (! class_exists($agentClass)) {
2222
$this->error("Agent not found: {$agentName}");
23+
2324
return 1;
2425
}
2526
}
@@ -28,15 +29,15 @@ public function handle()
2829
$agent = $agentClass::for('temp');
2930
$chatKeys = $agent->getChatKeys();
3031

31-
if (!empty($chatKeys)) {
32+
if (! empty($chatKeys)) {
3233
// Remove each chat history
33-
$this->info("Found ".count($chatKeys)." chat histories to remove...");
34-
34+
$this->info('Found '.count($chatKeys).' chat histories to remove...');
35+
3536
foreach ($chatKeys as $key) {
3637
$this->line("Removing chat history: {$key}");
3738
$agent->chatHistory()->removeChatFromMemory($key);
3839
}
39-
40+
4041
$this->info("Successfully removed all chat histories for agent: {$agentName}");
4142
} else {
4243
$this->info("No chat histories found for agent: {$agentName}");

src/Core/Abstractions/ChatHistory.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function __construct(string $name, array $options = [])
2525
{
2626
$this->name = $name;
2727
$this->readFromMemory();
28-
28+
2929
$this->contextWindow = $options['context_window'] ?? 60000;
3030
$this->storeMeta = $options['store_meta'] ?? false;
3131
$this->saveChatKeys = $options['save_chat_keys'] ?? true;
@@ -132,7 +132,6 @@ abstract public function removeChatFromMemory(string $key): void;
132132

133133
abstract protected function removeChatKey(string $key): void;
134134

135-
136135
// Token management methods
137136
public function setContextWindow(int $tokens): void
138137
{

src/Drivers/OpenAi/OpenAiCompatible.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,11 @@ protected function validateJson(string $json): void
148148
protected function buildClient(string $apiKey, string $baseUrl): mixed
149149
{
150150
$client = OpenAI::factory()
151-
->withApiKey($apiKey)
152-
->withBaseUri($baseUrl)
153-
->withHttpClient($httpClient = new \GuzzleHttp\Client([]))
154-
->make();
151+
->withApiKey($apiKey)
152+
->withBaseUri($baseUrl)
153+
->withHttpClient($httpClient = new \GuzzleHttp\Client([]))
154+
->make();
155+
155156
return $client;
156157
}
157158
}

src/History/CacheChatHistory.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
class CacheChatHistory extends ChatHistory implements ChatHistoryInterface
1010
{
1111
protected ?string $store;
12+
1213
protected string $keysKey = 'CacheChatHistory-keys';
1314

1415
public function __construct(string $name, array $options = [])
@@ -37,7 +38,7 @@ public function saveKeyToMemory(): void
3738
{
3839
$keys = $this->loadKeysFromMemory();
3940
$key = $this->getIdentifier();
40-
if (!in_array($key, $keys)) {
41+
if (! in_array($key, $keys)) {
4142
$keys[] = $key;
4243
if ($this->store) {
4344
Cache::store($this->store)->put($this->keysKey, $keys);
@@ -50,6 +51,7 @@ public function saveKeyToMemory(): void
5051
public function loadKeysFromMemory(): array
5152
{
5253
$keys = $this->store ? Cache::store($this->store)->get($this->keysKey, []) : Cache::get($this->keysKey, []);
54+
5355
return is_array($keys) ? $keys : [];
5456
}
5557

@@ -66,8 +68,8 @@ public function removeChatFromMemory(string $key): void
6668
protected function removeChatKey(string $key): void
6769
{
6870
$keys = $this->loadKeysFromMemory();
69-
$keys = array_filter($keys, fn($k) => $k !== $key);
70-
71+
$keys = array_filter($keys, fn ($k) => $k !== $key);
72+
7173
if ($this->store) {
7274
Cache::store($this->store)->put($this->keysKey, $keys);
7375
} else {

src/History/FileChatHistory.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public function saveKeyToMemory(): void
6767
$keys = $this->loadKeysFromMemory();
6868

6969
$key = $this->getIdentifier();
70-
if (!in_array($key, $keys)) {
70+
if (! in_array($key, $keys)) {
7171
$keys[] = $key;
7272
Storage::disk($this->disk)->put($keysPath, json_encode($keys, JSON_PRETTY_PRINT));
7373
}
@@ -81,11 +81,12 @@ public function loadKeysFromMemory(): array
8181
try {
8282
$keysPath = $this->folder.'/'.$this->keysFile;
8383

84-
if (!Storage::disk($this->disk)->exists($keysPath)) {
84+
if (! Storage::disk($this->disk)->exists($keysPath)) {
8585
return [];
8686
}
8787

8888
$content = Storage::disk($this->disk)->get($keysPath);
89+
8990
return json_decode($content, true) ?? [];
9091
} catch (\Exception $e) {
9192
return [];
@@ -127,8 +128,8 @@ public function removeChatFromMemory(string $key): void
127128
protected function removeChatKey(string $key): void
128129
{
129130
$keys = $this->loadKeysFromMemory();
130-
$keys = array_filter($keys, fn($k) => $k !== $key);
131-
131+
$keys = array_filter($keys, fn ($k) => $k !== $key);
132+
132133
$keysPath = $this->folder.'/'.$this->keysFile;
133134
Storage::disk($this->disk)->put($keysPath, json_encode($keys, JSON_PRETTY_PRINT));
134135
}

0 commit comments

Comments
 (0)