Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7c199d0
doc
ilvalerione Sep 8, 2025
98bf3ac
doc
ilvalerione Sep 8, 2025
fbbed7f
doc
ilvalerione Sep 8, 2025
b038ef9
Tool payload mapper
ilvalerione Sep 8, 2025
14b8f07
Tool payload mapper
ilvalerione Sep 8, 2025
8db82ee
Tool payload mapper
ilvalerione Sep 8, 2025
b490b22
Tool payload mapper
ilvalerione Sep 8, 2025
f18a081
Tool payload mapper
ilvalerione Sep 8, 2025
d9c57cf
Tool payload mapper, provider tool
ilvalerione Sep 8, 2025
ba5204e
Tool payload mapper, provider tool
ilvalerione Sep 8, 2025
9863b67
Tool payload mapper, provider tool
ilvalerione Sep 8, 2025
280e2e4
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
b34a4e8
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
e05a185
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
6e27d78
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
69af23c
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
e77ecfd
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
0d3a809
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
47f6a83
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
4ce57a5
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
0198abe
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
3543493
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
f9d9b92
Tool payload mapper, provider tool, openai responses
ilvalerione Sep 8, 2025
fc9d195
Apply PHP-CS-Fixer changes
ilvalerione Sep 8, 2025
335fb7a
Merge branch 'main' into openai-responses-api
ilvalerione Sep 9, 2025
2bd59d9
fix Gemini tool mapping
ilvalerione Sep 9, 2025
0677e22
Apply PHP-CS-Fixer changes
ilvalerione Sep 9, 2025
5a2885d
fix Gemini tool mapping
ilvalerione Sep 9, 2025
d3c6fb5
Merge remote-tracking branch 'origin/openai-responses-api' into opena…
ilvalerione Sep 9, 2025
ed4df58
Merge branch 'main' into openai-responses-api
ilvalerione Sep 9, 2025
66cc7fe
Merge branch 'main' into openai-responses-api
ilvalerione Sep 9, 2025
7a242e7
Merge branch 'main' into openai-responses-api
ilvalerione Sep 9, 2025
a0963ea
Merge branch 'main' into openai-responses-api
ilvalerione Sep 10, 2025
ac12ace
Merge branch 'main' into openai-responses-api
ilvalerione Sep 11, 2025
7434a33
Merge branch 'main' into openai-responses-api
ilvalerione Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Chat/Messages/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public function addMetadata(string $key, string|array|null $value): Message
return $this;
}

public function getMetadata(string $key): mixed
{
return $this->meta[$key] ?? null;
}

/**
* @return array<string, mixed>
*/
Expand All @@ -106,7 +111,7 @@ public function jsonSerialize(): array
'content' => $this->getContent()
];

if ($this->getUsage() instanceof \NeuronAI\Chat\Messages\Usage) {
if ($this->getUsage() instanceof Usage) {
$data['usage'] = $this->getUsage()->jsonSerialize();
}

Expand Down
17 changes: 11 additions & 6 deletions src/HandleTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use NeuronAI\Observability\Events\ToolCalled;
use NeuronAI\Observability\Events\ToolCalling;
use NeuronAI\Observability\Events\ToolsBootstrapped;
use NeuronAI\Tools\ProviderToolInterface;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Tools\Toolkits\ToolkitInterface;

Expand All @@ -29,6 +30,9 @@ trait HandleTools
*/
protected array $toolsBootstrapCache = [];

/**
* Global max tries for all tools.
*/
protected int $tollMaxTries = 5;

/**
Expand All @@ -43,17 +47,17 @@ public function toolMaxTries(int $tries): Agent
}

/**
* Get the list of tools.
* Override to provide tools to the agent.
*
* @return ToolInterface[]|ToolkitInterface[]
* @return array<ToolInterface|ToolkitInterface|ProviderToolInterface>
*/
protected function tools(): array
{
return [];
}

/**
* @return ToolInterface[]|ToolkitInterface[]
* @return array<ToolInterface|ToolkitInterface|ProviderToolInterface>
*/
public function getTools(): array
{
Expand Down Expand Up @@ -121,15 +125,16 @@ public function bootstrapTools(): array
/**
* Add tools.
*
* @param ToolInterface|ToolkitInterface|ProviderToolInterface|array<ToolInterface|ToolkitInterface|ProviderToolInterface> $tools
* @throws AgentException
*/
public function addTool(ToolInterface|ToolkitInterface|array $tools): AgentInterface
public function addTool(ToolInterface|ToolkitInterface|ProviderToolInterface|array $tools): AgentInterface
{
$tools = \is_array($tools) ? $tools : [$tools];

foreach ($tools as $t) {
if (! $t instanceof ToolInterface && ! $t instanceof ToolkitInterface) {
throw new AgentException('Tools must be an instance of ToolInterface or ToolkitInterface');
if (! $t instanceof ToolInterface && ! $t instanceof ToolkitInterface && ! $t instanceof ProviderToolInterface) {
throw new AgentException('Tools must be an instance of ToolInterface, ToolkitInterface, or ProviderToolInterface');
}
$this->tools[] = $t;
}
Expand Down
12 changes: 6 additions & 6 deletions src/Observability/AgentMonitoring.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use NeuronAI\Chat\Messages\Message;
use NeuronAI\Observability\Events\AgentError;
use NeuronAI\RAG\RAG;
use NeuronAI\Tools\ProviderToolInterface;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Tools\Toolkits\ToolkitInterface;
use NeuronAI\Tools\ToolPropertyInterface;
Expand Down Expand Up @@ -225,12 +226,11 @@ protected function getContext(Agent $agent): array
'provider' => $agent->resolveProvider()::class,
'instructions' => $agent->resolveInstructions(),
],
'Tools' => \array_map(
fn (ToolInterface|ToolkitInterface $tool) => $tool instanceof ToolInterface
? $mapTool($tool)
: [$tool::class => \array_map($mapTool, $tool->tools())],
$agent->getTools()
),
'Tools' => \array_map(fn (ToolInterface|ToolkitInterface|ProviderToolInterface $tool) => match (true) {
$tool instanceof ToolInterface => $mapTool($tool),
$tool instanceof ToolkitInterface => [$tool::class => \array_map($mapTool, $tool->tools())],
default => $tool->jsonSerialize(),
}, $agent->getTools()),
//'Messages' => $agent->resolveChatHistory()->getMessages(),
];
}
Expand Down
9 changes: 7 additions & 2 deletions src/Observability/HandleToolEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use NeuronAI\Observability\Events\ToolCalled;
use NeuronAI\Observability\Events\ToolCalling;
use NeuronAI\Observability\Events\ToolsBootstrapped;
use NeuronAI\Tools\ProviderToolInterface;
use NeuronAI\Tools\ToolInterface;

trait HandleToolEvents
Expand All @@ -30,8 +31,12 @@ public function toolsBootstrapped(AgentInterface $agent, string $event, ToolsBoo
{
if (\array_key_exists($agent::class.'_tools_bootstrap', $this->segments) && $data->tools !== []) {
$segment = $this->segments[$agent::class.'_tools_bootstrap']->end();
$segment->addContext('Tools', \array_reduce($data->tools, function (array $carry, ToolInterface $tool): array {
$carry[$tool->getName()] = $tool->getDescription();
$segment->addContext('Tools', \array_reduce($data->tools, function (array $carry, ToolInterface|ProviderToolInterface $tool): array {
if ($tool instanceof ProviderToolInterface) {
$carry[$tool->getType()] = $tool->getOptions();
} else {
$carry[$tool->getName()] = $tool->getDescription();
}
return $carry;
}, []));
$segment->addContext('Guidelines', $data->guidelines);
Expand Down
5 changes: 5 additions & 0 deletions src/Providers/AIProviderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public function setTools(array $tools): AIProviderInterface;
*/
public function messageMapper(): MessageMapperInterface;

/**
* The component responsible for mapping the NeuronAI Tools to the AI provider format.
*/
public function toolPayloadMapper(): ToolPayloadMapperInterface;

/**
* Send a prompt to the AI agent.
*
Expand Down
49 changes: 10 additions & 39 deletions src/Providers/AWS/BedrockRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HandleWithTools;
use NeuronAI\Providers\MessageMapperInterface;
use NeuronAI\Providers\ToolPayloadMapperInterface;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Tools\ToolPropertyInterface;

class BedrockRuntime implements AIProviderInterface
{
Expand All @@ -23,6 +23,7 @@ class BedrockRuntime implements AIProviderInterface
protected ?string $system = null;

protected MessageMapperInterface $messageMapper;
protected ToolPayloadMapperInterface $toolPayloadMapper;

public function __construct(
protected BedrockRuntimeClient $bedrockRuntimeClient,
Expand All @@ -34,7 +35,6 @@ public function __construct(
public function systemPrompt(?string $prompt): AIProviderInterface
{
$this->system = $prompt;

return $this;
}

Expand All @@ -43,6 +43,11 @@ public function messageMapper(): MessageMapperInterface
return $this->messageMapper ?? $this->messageMapper = new MessageMapper();
}

public function toolPayloadMapper(): ToolPayloadMapperInterface
{
return $this->toolPayloadMapper ?? $this->toolPayloadMapper = new ToolPayloadMapper();
}

protected function createPayLoad(array $messages): array
{
$payload = [
Expand All @@ -57,49 +62,15 @@ protected function createPayLoad(array $messages): array
$payload['inferenceConfig'] = $this->inferenceConfig;
}

$toolSpecs = $this->generateToolsPayload();
$tools = $this->toolPayloadMapper()->map($this->tools);

if (\count($toolSpecs) > 0) {
$payload['toolConfig']['tools'] = $toolSpecs;
if ($tools !== []) {
$payload['toolConfig']['tools'] = $tools;
}

return $payload;
}

protected function generateToolsPayload(): array
{
return \array_map(function (ToolInterface $tool): array {
$payload = [
'toolSpec' => [
'name' => $tool->getName(),
'description' => $tool->getDescription(),
'inputSchema' => [
'json' => [
'type' => 'object',
'properties' => new \stdClass(),
'required' => [],
]
],
],
];

$properties = \array_reduce($tool->getProperties(), function (array $carry, ToolPropertyInterface $property): array {
$carry[$property->getName()] = $property->getJsonSchema();
return $carry;
}, []);

if (!empty($properties)) {
$payload['toolSpec']['inputSchema']['json'] = [
'type' => 'object',
'properties' => $properties,
'required' => $tool->getRequiredProperties(),
];
}

return $payload;
}, $this->tools);
}

/**
* @throws ProviderException
*/
Expand Down
20 changes: 9 additions & 11 deletions src/Providers/AWS/MessageMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,22 @@

class MessageMapper implements MessageMapperInterface
{
protected array $mapping = [];

public function map(array $messages): array
{
$this->mapping = [];
$mapping = [];

foreach ($messages as $message) {
match ($message::class) {
$mapping[] = match ($message::class) {
ToolCallResultMessage::class => $this->mapToolCallResult($message),
ToolCallMessage::class => $this->mapToolCall($message),
default => $this->mapMessage($message),
};
}

return $this->mapping;
return $mapping;
}

protected function mapToolCallResult(ToolCallResultMessage $message): void
protected function mapToolCallResult(ToolCallResultMessage $message): array
{
$toolContents = [];
foreach ($message->getTools() as $tool) {
Expand All @@ -46,13 +44,13 @@ protected function mapToolCallResult(ToolCallResultMessage $message): void
];
}

$this->mapping[] = [
return [
'role' => $message->getRole(),
'content' => $toolContents,
];
}

protected function mapToolCall(ToolCallMessage $message): void
protected function mapToolCall(ToolCallMessage $message): array
{
$toolCallContents = [];

Expand All @@ -66,15 +64,15 @@ protected function mapToolCall(ToolCallMessage $message): void
];
}

$this->mapping[] = [
return [
'role' => $message->getRole(),
'content' => $toolCallContents,
];
}

protected function mapMessage(Message $message): void
protected function mapMessage(Message $message): array
{
$this->mapping[] = [
return [
'role' => $message->getRole(),
'content' => [['text' => $message->getContent()]],
];
Expand Down
61 changes: 61 additions & 0 deletions src/Providers/AWS/ToolPayloadMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace NeuronAI\Providers\AWS;

use NeuronAI\Exceptions\ProviderException;
use NeuronAI\Providers\ToolPayloadMapperInterface;
use NeuronAI\Tools\ProviderToolInterface;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Tools\ToolPropertyInterface;

class ToolPayloadMapper implements ToolPayloadMapperInterface
{
public function map(array $tools): array
{
$mapping = [];

foreach ($tools as $tool) {
$mapping[] = match (true) {
$tool instanceof ToolInterface => $this->mapTool($tool),
$tool instanceof ProviderToolInterface => throw new ProviderException('Bedrock Runtime does not support Provider Tools'),
default => throw new ProviderException('Could not map tool type '.$tool::class),
};
}

return $mapping;
}

protected function mapTool(ToolInterface $tool): array
{
$payload = [
'toolSpec' => [
'name' => $tool->getName(),
'description' => $tool->getDescription(),
'inputSchema' => [
'json' => [
'type' => 'object',
'properties' => new \stdClass(),
'required' => [],
]
],
],
];

$properties = \array_reduce($tool->getProperties(), function (array $carry, ToolPropertyInterface $property): array {
$carry[$property->getName()] = $property->getJsonSchema();
return $carry;
}, []);

if (!empty($properties)) {
$payload['toolSpec']['inputSchema']['json'] = [
'type' => 'object',
'properties' => $properties,
'required' => $tool->getRequiredProperties(),
];
}

return $payload;
}
}
Loading