From d919fb4cf5785ba1305b38475f467c07ae822e4f Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Wed, 22 Oct 2025 04:12:27 -0400 Subject: [PATCH 1/4] Remove inputs, add Agent_Runner --- inc/inputs.php | 7 ---- lib/agents/class-abstract-llm-agent.php | 15 ++++---- .../class-agent-not-found-exception.php | 7 ++++ .../class-input-not-registered-exception.php | 7 ---- lib/exceptions/class-skip-agent-exception.php | 7 ---- lib/inputs/class-abstract-input.php | 20 ---------- lib/inputs/class-post-updated-input.php | 32 ---------------- lib/providers/class-open-ai-provider.php | 4 +- lib/providers/class-provider-interface.php | 2 +- lib/services/class-agent-manager.php | 38 +++++-------------- lib/services/class-input-manager.php | 27 ------------- lib/system/class-agent-runner.php | 26 +++++++++++++ wp-agents.php | 19 ++++------ 13 files changed, 60 insertions(+), 151 deletions(-) delete mode 100644 inc/inputs.php create mode 100644 lib/exceptions/class-agent-not-found-exception.php delete mode 100644 lib/exceptions/class-input-not-registered-exception.php delete mode 100644 lib/exceptions/class-skip-agent-exception.php delete mode 100644 lib/inputs/class-abstract-input.php delete mode 100644 lib/inputs/class-post-updated-input.php delete mode 100644 lib/services/class-input-manager.php create mode 100644 lib/system/class-agent-runner.php diff --git a/inc/inputs.php b/inc/inputs.php deleted file mode 100644 index a5a6805..0000000 --- a/inc/inputs.php +++ /dev/null @@ -1,7 +0,0 @@ -actions; - } - public function filters(): array { return $this->filters; } - public function run( mixed $input, Provider_Interface $provider ): mixed { - return $provider->complete( $input, $this ); + public function prompt( mixed $input ): Agent_Runner { + return new Agent_Runner( + $input, + $this + ); } public function get_model(): string { diff --git a/lib/exceptions/class-agent-not-found-exception.php b/lib/exceptions/class-agent-not-found-exception.php new file mode 100644 index 0000000..3aba256 --- /dev/null +++ b/lib/exceptions/class-agent-not-found-exception.php @@ -0,0 +1,7 @@ +priority; - } - - public function get_accepted_args(): int { - return $this->accepted_args; - } -} diff --git a/lib/inputs/class-post-updated-input.php b/lib/inputs/class-post-updated-input.php deleted file mode 100644 index e3004a5..0000000 --- a/lib/inputs/class-post-updated-input.php +++ /dev/null @@ -1,32 +0,0 @@ -post_type ) { - throw new Skip_Agent_Exception(); - } - - $categories = wp_get_post_categories( $post->ID, array( 'fields' => 'names' ) ); - $tags = wp_get_post_tags( $post->ID, array( 'fields' => 'names' ) ); - - $category_list = empty( $categories ) ? 'none' : implode( ', ', $categories ); - $tag_list = empty( $tags ) ? 'none' : implode( ', ', $tags ); - - return <<post_title} - -Post content: {$post->post_content} - -Existing category: {$category_list} -Existing tags: {$tag_list} -PROMPT; - } -} diff --git a/lib/providers/class-open-ai-provider.php b/lib/providers/class-open-ai-provider.php index f9feac9..e4cffef 100644 --- a/lib/providers/class-open-ai-provider.php +++ b/lib/providers/class-open-ai-provider.php @@ -13,7 +13,7 @@ public function __construct( string $api_key ) { $this->api_key = $api_key; } - public function complete( string $prompt, Abstract_Llm_Agent $agent ): string { + public function chat( string $input, Abstract_Llm_Agent $agent ): string { $client = \OpenAI::client( $this->api_key ); $tools = $agent->tools(); @@ -24,7 +24,7 @@ public function complete( string $prompt, Abstract_Llm_Agent $agent ): string { ), array( 'role' => 'user', - 'content' => $prompt, + 'content' => $input, ), ); $parameters = array( diff --git a/lib/providers/class-provider-interface.php b/lib/providers/class-provider-interface.php index 880df5c..1c54cba 100644 --- a/lib/providers/class-provider-interface.php +++ b/lib/providers/class-provider-interface.php @@ -6,5 +6,5 @@ interface Provider_Interface { - public function complete( string $prompt, Abstract_Llm_Agent $agent ): string; + public function chat( string $input, Abstract_Llm_Agent $agent ): string; } diff --git a/lib/services/class-agent-manager.php b/lib/services/class-agent-manager.php index ead679d..5281be2 100644 --- a/lib/services/class-agent-manager.php +++ b/lib/services/class-agent-manager.php @@ -3,42 +3,22 @@ namespace Wp_Agents\Services; use Wp_Agents\Agents\Abstract_Llm_Agent; -use Wp_Agents\Exceptions\Skip_Agent_Exception; +use Wp_Agents\Exceptions\Agent_Not_Found_Exception; class Agent_Manager { protected static array $agents = array(); - public static function register( string $name, Abstract_Llm_Agent $agent ): void { - self::$agents[ $name ] = $agent; - - foreach ( $agent->actions() as $action ) { - $input = Input_Manager::make( $action ); - - add_action( - $action, - function ( ...$args ) use ( $agent, $input, $name, $action ) { - Recursion_Guard::run( - "{$name}:{$action}", - function () use ( $input, $args, $agent ) { - try { - $built_input = $input->build( ...$args ); - if ( ! empty( $built_input ) ) { - $provider = Provider_Manager::get( $agent->get_provider() ); - $answer = $agent->run( $built_input, $provider ); - $agent->handle_response( $answer, $args ); - } - } catch ( Skip_Agent_Exception $e ) { - wp_agents_logger()->error( $e->getMessage() ); - } - } - ); - }, - $input->get_priority(), - $input->get_accepted_args() - ); + public static function register( string $name, string $agent_class ): void { + self::$agents[ $name ] = new $agent_class(); + } + public static function get( string $name ): Abstract_Llm_Agent { + if ( ! isset( static::$agents[ $name ] ) ) { + throw new Agent_Not_Found_Exception(); } + + return static::$agents[ $name ]; } public static function boot(): void { diff --git a/lib/services/class-input-manager.php b/lib/services/class-input-manager.php deleted file mode 100644 index 9914df8..0000000 --- a/lib/services/class-input-manager.php +++ /dev/null @@ -1,27 +0,0 @@ -input = $input; + $this->agent = $agent; + $this->provider = Provider_Manager::get( $agent->get_provider() ); + } + + public function chat(): mixed { + return $this->provider->chat( $this->input, $this->agent ); + } +} diff --git a/wp-agents.php b/wp-agents.php index 1d1355f..ea9d813 100644 --- a/wp-agents.php +++ b/wp-agents.php @@ -36,33 +36,30 @@ function wp_agents_logger() { if ( ! function_exists( 'wp_agents_register' ) ) { function wp_agents_register( string $name, string $agent_class ) { - \Wp_Agents\Services\Agent_Manager::register( $name, new $agent_class() ); + \Wp_Agents\Services\Agent_Manager::register( $name, $agent_class ); } } - if ( ! function_exists( 'wp_agents_register_input' ) ) { + if ( ! function_exists( 'wp_agents_register_provider' ) ) { - function wp_agents_register_input( string $name, string $input_class ) { - \Wp_Agents\Services\Input_Manager::register( $name, $input_class ); + function wp_agents_register_provider( string $name, callable $callback ) { + \Wp_Agents\Services\Provider_Manager::register( $name, $callback ); } - require_once __DIR__ . '/inc/inputs.php'; + require_once __DIR__ . '/inc/providers.php'; } - if ( ! function_exists( 'wp_agents_register_provider' ) ) { + if ( ! function_exists( 'wp_agents_get' ) ) { - function wp_agents_register_provider( string $name, callable $callback ) { - \Wp_Agents\Services\Provider_Manager::register( $name, $callback ); + function wp_agents_get( string $name ) { + return \Wp_Agents\Services\Agent_Manager::get( $name ); } - require_once __DIR__ . '/inc/providers.php'; - } add_action( 'init', array( \Wp_Agents\Services\Provider_Manager::class, 'boot' ) ); - add_action( 'init', array( \Wp_Agents\Services\Input_Manager::class, 'boot' ) ); add_action( 'init', array( \Wp_Agents\Services\Agent_Manager::class, 'boot' ) ); } } From 5108e3bd7dbba6421ccbc71101719f24c30ca069 Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Wed, 22 Oct 2025 04:58:13 -0400 Subject: [PATCH 2/4] Refactor messages to use Message_Stack --- lib/providers/class-open-ai-provider.php | 54 ++++++++++++++-------- lib/providers/class-provider-interface.php | 3 +- lib/system/class-agent-runner.php | 4 +- lib/system/class-message-stack.php | 45 ++++++++++++++++++ lib/system/class-message.php | 49 ++++++++++++++++++++ lib/system/class-rest.php | 12 +++++ 6 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 lib/system/class-message-stack.php create mode 100644 lib/system/class-message.php create mode 100644 lib/system/class-rest.php diff --git a/lib/providers/class-open-ai-provider.php b/lib/providers/class-open-ai-provider.php index e4cffef..a0c7e4a 100644 --- a/lib/providers/class-open-ai-provider.php +++ b/lib/providers/class-open-ai-provider.php @@ -3,6 +3,8 @@ namespace Wp_Agents\Providers; use Wp_Agents\Agents\Abstract_Llm_Agent; +use Wp_Agents\System\Message; +use Wp_Agents\System\Message_Stack; use Wp_Agents\Tools\Tool_Registry; class Open_Ai_Provider implements Provider_Interface { @@ -13,23 +15,20 @@ public function __construct( string $api_key ) { $this->api_key = $api_key; } - public function chat( string $input, Abstract_Llm_Agent $agent ): string { + public function chat( string $input, Abstract_Llm_Agent $agent ): Message_Stack { $client = \OpenAI::client( $this->api_key ); $tools = $agent->tools(); - $messages = array( + $messages = new Message_Stack( array( - 'role' => 'system', - 'content' => $agent->instructions(), - ), - array( - 'role' => 'user', - 'content' => $input, - ), + new Message( 'system', $agent->instructions() ), + new Message( 'user', $input ), + ) ); + $parameters = array( 'model' => $agent->get_model(), - 'messages' => $messages, + 'messages' => $messages->to_raw_array(), 'response_format' => array( 'type' => $agent->json() ? 'json_object' : 'text' ), ); @@ -40,8 +39,14 @@ public function chat( string $input, Abstract_Llm_Agent $agent ): string { $response = $client->chat()->create( $parameters ); - $message = $response->choices[0]->message; - $messages[] = $message->toArray(); + $message = $response->choices[0]->message; + $messages->add( + new Message( + $message->role, + $message->content, + $message->toArray() + ) + ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName if ( ! empty( $message->toolCalls ) ) { @@ -55,24 +60,35 @@ public function chat( string $input, Abstract_Llm_Agent $agent ): string { } $result = $tool_registry->execute( $function->name, $args ); - $messages[] = array( - 'role' => 'tool', - 'tool_call_id' => $call->id, - 'content' => wp_json_encode( $result ), + $messages->add( + new Message( + 'tool', + wp_json_encode( $result ), + array( + 'tool_call_id' => $call->id, + ) + ) ); } $follow = $client->chat()->create( array( 'model' => $agent->get_model(), - 'messages' => $messages, + 'messages' => $messages->to_raw_array(), 'response_format' => array( 'type' => $agent->json() ? 'json_object' : 'text' ), ) ); - return $follow->choices[0]->message->content ?? ''; + $message = $follow->choices[0]->message; + $messages->add( + new Message( + $message->role, + $message->content, + $message->toArray(), + ) + ); } - return $response->choices[0]->message->content ?? ''; + return $messages; } } diff --git a/lib/providers/class-provider-interface.php b/lib/providers/class-provider-interface.php index 1c54cba..f942f21 100644 --- a/lib/providers/class-provider-interface.php +++ b/lib/providers/class-provider-interface.php @@ -3,8 +3,9 @@ namespace Wp_Agents\Providers; use Wp_Agents\Agents\Abstract_Llm_Agent; +use Wp_Agents\System\Message_Stack; interface Provider_Interface { - public function chat( string $input, Abstract_Llm_Agent $agent ): string; + public function chat( string $input, Abstract_Llm_Agent $agent ): Message_Stack; } diff --git a/lib/system/class-agent-runner.php b/lib/system/class-agent-runner.php index 9931b92..709e0ba 100644 --- a/lib/system/class-agent-runner.php +++ b/lib/system/class-agent-runner.php @@ -21,6 +21,8 @@ public function __construct( string $input, Abstract_Llm_Agent $agent ) { } public function chat(): mixed { - return $this->provider->chat( $this->input, $this->agent ); + $message_stack = $this->provider->chat( $this->input, $this->agent ); + + return $message_stack->last()?->get_content(); } } diff --git a/lib/system/class-message-stack.php b/lib/system/class-message-stack.php new file mode 100644 index 0000000..255915c --- /dev/null +++ b/lib/system/class-message-stack.php @@ -0,0 +1,45 @@ +messages = $messages; + } + + public function getIterator(): Traversable { + return new \ArrayIterator( $this->messages ); + } + + public function count(): int { + return count( $this->messages ); + } + + public function first(): ?Message { + return $this->messages[0] ?? null; + } + + public function last(): ?Message { + $count = count( $this->messages ); + + return $count ? $this->messages[ $count - 1 ] : null; + } + + public function add( Message $message ) { + $this->messages[] = $message; + } + + public function to_raw_array(): array { + return array_map( + function ( Message $message ) { + return $message->get_raw_response(); + }, + $this->messages + ); + } +} diff --git a/lib/system/class-message.php b/lib/system/class-message.php new file mode 100644 index 0000000..ba21196 --- /dev/null +++ b/lib/system/class-message.php @@ -0,0 +1,49 @@ +role = $role; + $this->content = $content; + + if ( empty( $raw_response ) ) { + $this->raw_response = array( + 'role' => $role, + 'content' => $content, + ); + } else { + $this->raw_response = $raw_response; + } + } + + public function get_role(): string { + return $this->role; + } + + public function get_content(): string { + return $this->content; + } + + public function get_raw_response(): array { + return array( + 'role' => $this->role, + 'content' => $this->content, + ) + $this->raw_response; + } + + public function to_array(): array { + return array( + 'role' => $this->role, + 'content' => $this->content, + 'raw_response' => $this->raw_response, + ); + } +} diff --git a/lib/system/class-rest.php b/lib/system/class-rest.php new file mode 100644 index 0000000..9b003ca --- /dev/null +++ b/lib/system/class-rest.php @@ -0,0 +1,12 @@ + Date: Wed, 22 Oct 2025 05:09:44 -0400 Subject: [PATCH 3/4] Add basic REST endpoint --- lib/services/class-agent-manager.php | 4 ++-- lib/system/class-rest.php | 26 +++++++++++++++++++++++++- wp-agents.php | 1 + 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/services/class-agent-manager.php b/lib/services/class-agent-manager.php index 5281be2..72acf15 100644 --- a/lib/services/class-agent-manager.php +++ b/lib/services/class-agent-manager.php @@ -14,11 +14,11 @@ public static function register( string $name, string $agent_class ): void { } public static function get( string $name ): Abstract_Llm_Agent { - if ( ! isset( static::$agents[ $name ] ) ) { + if ( ! isset( self::$agents[ $name ] ) ) { throw new Agent_Not_Found_Exception(); } - return static::$agents[ $name ]; + return self::$agents[ $name ]; } public static function boot(): void { diff --git a/lib/system/class-rest.php b/lib/system/class-rest.php index 9b003ca..0f976b4 100644 --- a/lib/system/class-rest.php +++ b/lib/system/class-rest.php @@ -2,11 +2,35 @@ namespace Wp_Agents\System; +use Wp_Agents\Services\Agent_Manager; + class Rest { public static function register(): void { + register_rest_route( + 'wp-agents/v1', + '/chat', + array( + 'methods' => 'POST', + 'callback' => array( self::class, 'handle' ), + 'permission_callback' => '__return_true', + ) + ); } - public static function handle() { + public static function handle( \WP_REST_Request $request ) { + $agent = Agent_Manager::get( $request->get_param( 'agent' ) ); + + $message = $request->get_param( 'message' ); + $response = $agent + ->prompt( $message ) + ->chat(); + + return rest_ensure_response( + array( + 'message' => $message, + 'response' => $response, + ) + ); } } diff --git a/wp-agents.php b/wp-agents.php index ea9d813..c125249 100644 --- a/wp-agents.php +++ b/wp-agents.php @@ -61,6 +61,7 @@ function wp_agents_get( string $name ) { add_action( 'init', array( \Wp_Agents\Services\Provider_Manager::class, 'boot' ) ); add_action( 'init', array( \Wp_Agents\Services\Agent_Manager::class, 'boot' ) ); + add_action( 'rest_api_init', array( \Wp_Agents\System\Rest::class, 'register' ) ); } } ); From 839709e93e9984b0176b77d8c708ceb0a0d4556f Mon Sep 17 00:00:00 2001 From: Francis Santerre Date: Wed, 22 Oct 2025 05:16:08 -0400 Subject: [PATCH 4/4] Update README and let chat return the Message class --- README.md | 36 +++++++++++++++++++++++++++++++ lib/system/class-agent-runner.php | 4 ++-- lib/system/class-rest.php | 3 ++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 666b760..429906f 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,42 @@ cd wp-agents && composer install wp plugin activate wp-agents ``` +## REST API + +Each agent can be accessed programmatically through the WordPress REST API. + +This allows external systems or front-end applications to send a message to any registered agent and receive the model’s response in JSON format. + +**Endpoint** + +```bash +POST /wp-json/wp-agents/v1/chat +``` + +**Example** + +```bash +curl --location 'https://example.com/wp-json/wp-agents/v1/chat' \ +--header 'Content-Type: application/json' \ +--data '{ + "agent": "taxonomy_agent", + "message": "Hello" +}' +``` + +**Example response** + +```json +{ + "agent": "taxonomy_agent", + "message": "Hello", + "response": { + "role": "assistant", + "content": "Hello! How can I assist you today?" + } +} +``` + ## Demo https://github.com/santerref/wp-agents-demo diff --git a/lib/system/class-agent-runner.php b/lib/system/class-agent-runner.php index 709e0ba..7bbe6a2 100644 --- a/lib/system/class-agent-runner.php +++ b/lib/system/class-agent-runner.php @@ -20,9 +20,9 @@ public function __construct( string $input, Abstract_Llm_Agent $agent ) { $this->provider = Provider_Manager::get( $agent->get_provider() ); } - public function chat(): mixed { + public function chat(): Message { $message_stack = $this->provider->chat( $this->input, $this->agent ); - return $message_stack->last()?->get_content(); + return $message_stack->last(); } } diff --git a/lib/system/class-rest.php b/lib/system/class-rest.php index 0f976b4..cdfbbd5 100644 --- a/lib/system/class-rest.php +++ b/lib/system/class-rest.php @@ -28,8 +28,9 @@ public static function handle( \WP_REST_Request $request ) { return rest_ensure_response( array( + 'agent' => $request->get_param( 'agent' ), 'message' => $message, - 'response' => $response, + 'response' => $response->get_raw_response(), ) ); }