Skip to content

Commit bc7e9dd

Browse files
authored
implement ai agent connection and tools (#57)
* implement ai agent connection and tools * add action schema serializer * update deps * remove scripts * Revert "update deps" This reverts commit ba33e10. * add serializer and tools * add connection agent route * fix tests * fix style * fix test * add data sync migration * use agent message * update intro and fix null handling * fix test * fix agent tools * remove metadata * skip operations without description and skip mime content types * update action class description * add more hints * fix tool names * update use context * update prompt * use active context * mark as deprecated * ignore category * set operation * fix convert function tool call * use underscore for tool names * add agent chat table * add agent chat table, move logic to sender and add get and reset endpoint * rename agent table and fix tests * fix set result content * add intent concept and improve sender * add concrete add methods * allow null * decrease context * improve message serialization and rename type to origin * fix nesting * optimize uses * fix handle merge return * return array * fix test * update action intent * update intent * use collection format * set collection response schema * fix resolver arguments * update test * update schema * use code tags * return also messages * add more readonly tools * update tools * add JSON response format as message * update prompts * persist and load intent * convert intent * update response description * fix pass intent * update schema hint * add json serializer for more robust json handling * improve action template * fix remove markdown syntax * execute action in transaction and always rollback so that testing an action has no side effects * update description * fix test * fix set schema * set lower temperature * update hint * move serializer into folder * generate code instead of json * update prompt * update prompt * remove not needed serializer * update prompt * fix extract name * update prompt * remove action serializer * add rpc invoker and add ip and authorization header to context * add and reuse input schema builder * set token type * remove not needed reference handler interface * use mcp reference handler * add ai adapter * fix tests * add prompt loader * update prompt * fix return
1 parent 8b5daaa commit bc7e9dd

File tree

74 files changed

+3602
-1082
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3602
-1082
lines changed

composer.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@
1010
"homepage": "https://chrisk.app"
1111
}
1212
],
13-
"scripts": {
14-
"tests": "@php vendor/phpunit/phpunit/phpunit --configuration phpunit.xml tests",
15-
"psalm": "@php vendor/vimeo/psalm/psalm"
16-
},
1713
"require": {
1814
"php": ">=8.1",
1915
"psx/framework": "^8.0",
@@ -22,6 +18,7 @@
2218
"fusio/cli": "^4.0",
2319
"fusio/model": "^4.0",
2420
"fusio/engine": "^6.0",
21+
"fusio/adapter-ai": "^0.1",
2522
"fusio/adapter-amqp": "^6.0",
2623
"fusio/adapter-beanstalk": "^6.0",
2724
"fusio/adapter-cli": "^6.0",
@@ -45,8 +42,8 @@
4542
"symfony/doctrine-messenger": "^6.0|^7.0",
4643
"dragonmantank/cron-expression": "^3.3",
4744
"phpstan/phpdoc-parser": "^1.0|^2.0",
48-
"logiscape/mcp-sdk-php": "^1.2",
49-
"webonyx/graphql-php": "^15.24"
45+
"webonyx/graphql-php": "^15.24",
46+
"mcp/sdk": "^0.3"
5047
},
5148
"require-dev": {
5249
"phpunit/phpunit": "^12.0",
@@ -61,5 +58,10 @@
6158
"psr-4": {
6259
"Fusio\\Impl\\Tests\\": "tests/"
6360
}
61+
},
62+
"config": {
63+
"allow-plugins": {
64+
"php-http/discovery": false
65+
}
6466
}
6567
}

provider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
return [
4+
\Fusio\Adapter\Ai\Adapter::class,
45
\Fusio\Adapter\Amqp\Adapter::class,
56
\Fusio\Adapter\Beanstalk\Adapter::class,
67
\Fusio\Adapter\Cli\Adapter::class,

resources/container.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use Fusio\Cli;
44
use Fusio\Engine\Action;
55
use Fusio\Engine\Adapter\ServiceBuilder;
6+
use Fusio\Engine\Agent\ToolsInterface;
67
use Fusio\Engine\ConnectorInterface;
78
use Fusio\Engine\DispatcherInterface;
89
use Fusio\Engine\Repository;
@@ -14,10 +15,13 @@
1415
use Fusio\Impl\Provider;
1516
use Fusio\Impl\Repository as ImplRepository;
1617
use Fusio\Impl\Service\Action\Producer;
18+
use Fusio\Impl\Service\Agent;
1719
use Fusio\Impl\Service\Event\Dispatcher;
20+
use Fusio\Impl\Service\Mcp\ReferenceHandler;
1821
use Fusio\Impl\Service\Tenant\LimiterInterface;
1922
use Fusio\Impl\Service\User\Captcha;
2023
use Fusio\Impl\Tenant\UnlimitedLimiter;
24+
use Mcp\Capability\Registry\ReferenceHandlerInterface;
2125
use Psr\Cache\CacheItemPoolInterface;
2226
use Psr\SimpleCache\CacheInterface;
2327
use PSX\Api;
@@ -30,10 +34,15 @@
3034
use PSX\Framework\Migration\DependencyFactoryFactory;
3135
use PSX\Http\Filter\UserAgentEnforcer;
3236
use PSX\Schema;
37+
use Symfony\AI\Agent\Toolbox\Toolbox;
38+
use Symfony\AI\Agent\Toolbox\ToolboxInterface;
39+
use Symfony\AI\Agent\Toolbox\ToolCallArgumentResolverInterface;
40+
use Symfony\AI\Agent\Toolbox\ToolFactoryInterface;
3341
use Symfony\Component\Cache\Psr16Cache;
3442
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
3543
use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
3644
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
45+
use function Symfony\Component\DependencyInjection\Loader\Configurator\tagged_iterator;
3746

3847
return static function (ContainerConfigurator $container) {
3948
$services = ServiceBuilder::build($container);
@@ -62,6 +71,9 @@
6271
$services->set(Dispatcher::class);
6372
$services->alias(DispatcherInterface::class, Dispatcher::class);
6473

74+
$services->set(Agent\Tools::class);
75+
$services->alias(ToolsInterface::class, Agent\Tools::class);
76+
6577
$services->alias('test_connector', ConnectorInterface::class)
6678
->public();
6779

@@ -112,6 +124,19 @@
112124
$services->set(SDKgenConfig::class);
113125
$services->alias(Api\Repository\SDKgen\ConfigInterface::class, SDKgenConfig::class);
114126

127+
$services->set(Agent\OperationToolFactory::class);
128+
$services->alias(ToolFactoryInterface::class, Agent\OperationToolFactory::class);
129+
130+
$services->set(Agent\OperationToolCallArgumentResolver::class);
131+
$services->alias(ToolCallArgumentResolverInterface::class, Agent\OperationToolCallArgumentResolver::class);
132+
133+
$services->set(Agent\OperationTool::class)
134+
->tag('fusio.ai.tool');
135+
136+
$services->set(Toolbox::class)
137+
->arg('$tools', tagged_iterator('fusio.ai.tool'));
138+
$services->alias(ToolboxInterface::class, Toolbox::class);
139+
115140
// psx
116141
$services->set(Framework\Loader\RoutingParser\DatabaseParser::class);
117142
$services->set(Framework\Loader\RoutingParser\CompositeParser::class);

src/Backend/Action/Action/Execute.php

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
namespace Fusio\Impl\Backend\Action\Action;
2222

23+
use Doctrine\DBAL\Connection;
2324
use Fusio\Engine\ActionInterface;
2425
use Fusio\Engine\ContextInterface;
2526
use Fusio\Engine\ParametersInterface;
@@ -31,6 +32,8 @@
3132
use PSX\Http\Environment\HttpResponseInterface;
3233
use PSX\Http\Response;
3334
use PSX\Http\Writer\WriterInterface;
35+
use stdClass;
36+
use Throwable;
3437

3538
/**
3639
* Execute
@@ -39,14 +42,12 @@
3942
* @license http://www.apache.org/licenses/LICENSE-2.0
4043
* @link https://www.fusio-project.org
4144
*/
42-
class Execute implements ActionInterface
45+
readonly class Execute implements ActionInterface
4346
{
44-
private Action\Executor $actionExecutorService;
4547
private Converter $exceptionConverter;
4648

47-
public function __construct(Action\Executor $actionExecutorService)
49+
public function __construct(private Action\Executor $actionExecutorService, private Connection $connection)
4850
{
49-
$this->actionExecutorService = $actionExecutorService;
5051
$this->exceptionConverter = new Converter(true);
5152
}
5253

@@ -56,6 +57,8 @@ public function handle(RequestInterface $request, ParametersInterface $configura
5657

5758
assert($body instanceof ActionExecuteRequest);
5859

60+
$this->connection->beginTransaction();
61+
5962
try {
6063
$response = $this->actionExecutorService->execute(
6164
$request->get('action_id'),
@@ -74,30 +77,34 @@ public function handle(RequestInterface $request, ParametersInterface $configura
7477
$body = (string) $tempResponse->getBody();
7578
}
7679

77-
return [
80+
$return = [
7881
'statusCode' => $response->getStatusCode(),
7982
'headers' => $headers,
8083
'body' => $body,
8184
];
8285
} else {
83-
return [
86+
$return = [
8487
'statusCode' => 200,
85-
'headers' => new \stdClass(),
88+
'headers' => new stdClass(),
8689
'body' => $response,
8790
];
8891
}
89-
} catch (\Throwable $e) {
92+
} catch (Throwable $e) {
9093
if ($e instanceof MessageException) {
9194
$body = $e->getPayload();
9295
} else {
9396
$body = $this->exceptionConverter->convert($e);
9497
}
9598

96-
return [
99+
$return = [
97100
'statusCode' => 500,
98-
'headers' => new \stdClass(),
101+
'headers' => new stdClass(),
99102
'body' => $body,
100103
];
101104
}
105+
106+
$this->connection->rollBack();
107+
108+
return $return;
102109
}
103110
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/*
3+
* Fusio - Self-Hosted API Management for Builders.
4+
* For the current version and information visit <https://www.fusio-project.org/>
5+
*
6+
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace Fusio\Impl\Backend\Action\Connection\Agent;
22+
23+
use Fusio\Engine\ActionInterface;
24+
use Fusio\Engine\Connector;
25+
use Fusio\Engine\RequestInterface;
26+
use Fusio\Impl\Service\System\FrameworkConfig;
27+
use PSX\Http\Exception as StatusCode;
28+
use PSX\Http\Exception\BadRequestException;
29+
use Symfony\AI\Agent\AgentInterface;
30+
31+
/**
32+
* AgentAbstract
33+
*
34+
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
35+
* @license http://www.apache.org/licenses/LICENSE-2.0
36+
* @link https://www.fusio-project.org
37+
*/
38+
abstract readonly class AgentAbstract implements ActionInterface
39+
{
40+
public function __construct(private Connector $connector, private FrameworkConfig $frameworkConfig)
41+
{
42+
}
43+
44+
protected function getConnection(int $connectionId): AgentInterface
45+
{
46+
$connection = $this->connector->getConnection($connectionId);
47+
if (!$connection instanceof AgentInterface) {
48+
throw new BadRequestException('Provided an invalid connection');
49+
}
50+
51+
return $connection;
52+
}
53+
54+
protected function assertConnectionEnabled(): void
55+
{
56+
if (!$this->frameworkConfig->isConnectionEnabled()) {
57+
throw new StatusCode\ServiceUnavailableException('Agent is not enabled, please change the setting "fusio_connection" at the configuration.php to "true" in order to activate the agent');
58+
}
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/*
3+
* Fusio - Self-Hosted API Management for Builders.
4+
* For the current version and information visit <https://www.fusio-project.org/>
5+
*
6+
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace Fusio\Impl\Backend\Action\Connection\Agent;
22+
23+
use Fusio\Engine\Connector;
24+
use Fusio\Engine\ContextInterface;
25+
use Fusio\Engine\ParametersInterface;
26+
use Fusio\Engine\RequestInterface;
27+
use Fusio\Impl\Backend\View;
28+
use Fusio\Impl\Service\Agent\Intent;
29+
use Fusio\Impl\Service\System\FrameworkConfig;
30+
use PSX\Http\Environment\HttpResponse;
31+
use PSX\Http\Exception\BadRequestException;
32+
33+
/**
34+
* Get
35+
*
36+
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
37+
* @license http://www.apache.org/licenses/LICENSE-2.0
38+
* @link https://www.fusio-project.org
39+
*/
40+
readonly class Get extends AgentAbstract
41+
{
42+
public function __construct(private View\Connection\Agent $view, Connector $connector, FrameworkConfig $frameworkConfig)
43+
{
44+
parent::__construct($connector, $frameworkConfig);
45+
}
46+
47+
public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed
48+
{
49+
$this->assertConnectionEnabled();
50+
51+
$connectionId = (int) $request->get('connection_id');
52+
if (empty($connectionId)) {
53+
throw new BadRequestException('Provided no connection');
54+
}
55+
56+
$intent = Intent::tryFrom($request->get('intent') ?? '');
57+
58+
return new HttpResponse(200, [], $this->view->getCollection($connectionId, $intent, $context));
59+
}
60+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
/*
3+
* Fusio - Self-Hosted API Management for Builders.
4+
* For the current version and information visit <https://www.fusio-project.org/>
5+
*
6+
* Copyright (c) Christoph Kappestein <christoph.kappestein@gmail.com>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
namespace Fusio\Impl\Backend\Action\Connection\Agent;
22+
23+
use Fusio\Engine\Connector;
24+
use Fusio\Engine\ContextInterface;
25+
use Fusio\Engine\ParametersInterface;
26+
use Fusio\Engine\RequestInterface;
27+
use Fusio\Impl\Service\System\FrameworkConfig;
28+
use Fusio\Impl\Table;
29+
use PSX\Http\Environment\HttpResponse;
30+
use PSX\Http\Exception\BadRequestException;
31+
32+
/**
33+
* Reset
34+
*
35+
* @author Christoph Kappestein <christoph.kappestein@gmail.com>
36+
* @license http://www.apache.org/licenses/LICENSE-2.0
37+
* @link https://www.fusio-project.org
38+
*/
39+
readonly class Reset extends AgentAbstract
40+
{
41+
public function __construct(private Table\Agent $agentTable, Connector $connector, FrameworkConfig $frameworkConfig)
42+
{
43+
parent::__construct($connector, $frameworkConfig);
44+
}
45+
46+
public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed
47+
{
48+
$this->assertConnectionEnabled();
49+
50+
$connectionId = (int) $request->get('connection_id');
51+
if (empty($connectionId)) {
52+
throw new BadRequestException('Provided no connection');
53+
}
54+
55+
$this->agentTable->reset($context->getUser()->getId(), $connectionId);
56+
57+
return new HttpResponse(200, [], [
58+
'success' => true,
59+
'message' => 'Chat successfully reset',
60+
]);
61+
}
62+
}

0 commit comments

Comments
 (0)