Просмотр исходного кода

#5406 AI Agents revamp: content seach tool

Aleksandr Trofimov 6 месяцев назад
Родитель
Сommit
528a4ff8aa

+ 7 - 1
inc/classes/BxDolAIToolContentChange.php

@@ -19,10 +19,16 @@ class BxDolAIToolContentChange extends Tool
     {
         parent::__construct(
             'content_change',
-            'Use this tool ]to add, update or delete content. Never pass empty values for data field for "update" and "add" actions, always pass all required fields. Always user "content_structure" tool if it\'s available to get knowledge about content modules fields for add and update actions.',
+            'Use this tool to add, update or delete content. Never pass empty values for data field for "update" and "add" actions, always pass all required fields. Always user "content_structure" tool if it\'s available to get knowledge about content modules fields for add and update actions. Available modules: ' . self::getModules() . '.',
         );
     }
     
+    static public function getModules(): string
+    {
+        $aModules = $aContentModules = bx_srv('system', 'modules_list', [true], 'TemplServiceContent');
+        return implode(',', array_keys($aModules));
+    }
+
     protected function properties(): array
     {
         return [

+ 4 - 3
inc/classes/BxDolAIToolContentGet.php

@@ -39,12 +39,13 @@ class BxDolAIToolContentGet extends Tool
         ];
     }
 
-    public function __invoke(string $module, int $content_id): string
+    public function __invoke(string $module, int $content_id): array
     {
+echoDbgLog("Invoking content get tool with module: {$module}, content_id: {$content_id}");
         $a = bx_srv('system', 'get_info', [$module, $content_id], 'TemplServiceContent');
         if (!$a)
-            return "Error: content item was not found";
+            return ["error" => "Content item was not found"];
 
-        return json_encode($a);
+        return $a;
     }
 }

+ 66 - 0
inc/classes/BxDolAIToolContentSearch.php

@@ -0,0 +1,66 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+use NeuronAI\Tools\PropertyType;
+use NeuronAI\Tools\Tool;
+use NeuronAI\Tools\ToolProperty;
+use NeuronAI\Tools\ArrayProperty;
+
+class BxDolAIToolContentSearch extends Tool
+{
+    public function __construct()
+    {
+        parent::__construct(
+            'content_search',
+            'Search for content items based on a keyword and optional sections. If needed, prepend base URL to links: ' . BX_DOL_URL_ROOT . '. Available sections: ' . self::getSections() . '.',
+        );
+    }
+    
+    public static function getSections()
+    {
+        $aSections = BxDolSearch::getSections();
+        return implode(',', array_keys($aSections));
+    }
+    protected function properties(): array
+    {
+        return [
+            new ToolProperty(
+                name: 'keyword',
+                type: PropertyType::STRING,
+                description: 'The keyword to search for.',
+                required: true
+            ),
+            new ArrayProperty(
+                name: 'sections',
+                description: 'List of sections to search in.',
+                required: false,
+                items: new ToolProperty(
+                    name: 'section',
+                    type: PropertyType::STRING,
+                    description: 'Section name.',
+                )
+            ),
+            new ToolProperty(
+                name: 'limit',
+                type: PropertyType::INTEGER,
+                description: 'The maximum number of results to return. Default is 5.',
+                required: false
+            )
+        ];
+    }
+
+    public function __invoke(string $keyword, array $sections, ?int $limit = 5): array
+    {
+echoDbgLog("Invoking content search tool with keyword: {$keyword}, sections: " . implode(',', $sections) . ", limit: {$limit}");
+        $a = bx_srv('system', 'search', [$keyword, $sections, $limit], 'TemplServiceContent');
+        if (!$a)
+            return ["error" => "No content items found"];
+        return $a;
+    }
+}

+ 3 - 0
inc/classes/BxDolAIToolFactory.php

@@ -50,6 +50,9 @@ class BxDolAIToolFactory extends BxDolFactory
             case 'content_change':
                 $o = BxDolAIToolContentChange::make();
                 break;
+            case 'content_search':
+                $o = BxDolAIToolContentSearch::make();
+                break;
             default:
                 bx_log('sys_agents', "Tool type {$a['type']} is not supported", BX_LOG_ERR);
                 throw new Exception("Tool type {$a['type']} is not supported");

+ 33 - 12
inc/classes/BxDolSearch.php

@@ -38,7 +38,8 @@ define('BX_DOL_SEARCH_KEYWORD_PAGE', 'site-search-page');
 class BxDolSearch extends BxDol
 {
     protected $_bIsApi;
-
+    protected $_bAssitantForLiveSearch = true;
+        
     protected $aClasses = array(); ///< array of all search classes
     protected $aChoice  = array(); ///< array of current search classes which were choosen in search area
     protected $_bRawProcessing = false; ///< display search results without design box and paginate
@@ -61,16 +62,7 @@ class BxDolSearch extends BxDol
 
         $this->_bIsApi = bx_is_api();
 
-        $this->aClasses = BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
-           'SELECT `ID` as `id`,
-                   `Title` as `title`,
-                   `ClassName` as `class`,
-                   `ClassPath` as `file`,
-                   `ObjectName`,
-                   `GlobalSearch`
-            FROM `sys_objects_search`
-            ORDER BY `Order` ASC', 'ObjectName'
-        );
+        $this->aClasses = self::getSections();
 
         if (is_array($aChoice) && !empty($aChoice)) {
             foreach ($aChoice as $sValue) {
@@ -82,6 +74,35 @@ class BxDolSearch extends BxDol
         }
     }
 
+    public static function getSections($bGlobal = null) 
+    {
+        $sWhere = '';
+        if ($bGlobal != null)
+            $sWhere = ' AND `GlobalSearch` = ' . ($bGlobal ? '1' : '0');
+
+        return BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
+           'SELECT `ID` as `id`,
+                   `Title` as `title`,
+                   `ClassName` as `class`,
+                   `ClassPath` as `file`,
+                   `ObjectName`,
+                   `GlobalSearch`
+            FROM `sys_objects_search`
+            WHERE 1 ' . $sWhere . '
+            ORDER BY `Order` ASC', 'ObjectName'
+        );
+    }
+
+    public function setApiOutput($bIsApi)
+    {
+        $this->_bIsApi = $bIsApi;
+    }   
+
+    public function setAssistantForLiveSearch($bAssistantForLiveSearch)
+    {
+        $this->_bAssitantForLiveSearch = $bAssistantForLiveSearch;
+    }  
+
     /**
      * create units for all classes and calling their processing methods
      */
@@ -89,7 +110,7 @@ class BxDolSearch extends BxDol
     {
         $sCode = $this->_bDataProcessing ? [] : '';
 
-        if($this->_bLiveSearch && ($iAssistant = BxDolAI::getAssistantForLiveSearch()) != 0) {
+        if($this->_bAssitantForLiveSearch && $this->_bLiveSearch && ($iAssistant = BxDolAI::getAssistantForLiveSearch()) != 0) {
             $sKeyword = '';
             if(($sKeyword = bx_get('keyword')) !== false)
                 $sKeyword = bx_process_input($sKeyword);

+ 20 - 1
inc/utils.inc.php

@@ -2907,6 +2907,22 @@ function bx_mem_cache_set(string $sKey, mixed $mixedData): mixed
     return $GLOBALS['glMemCache'][$sKey] = $mixedData;
 }
 
+function bx_get_search_class_name(): string
+{
+    $sClass = 'BxTemplSearch';
+
+    $sElsName = 'bx_elasticsearch';
+    $sElsMethod = 'is_configured';
+    if (BxDolRequest::serviceExists($sElsName, $sElsMethod) && BxDolService::call($sElsName, $sElsMethod)) {
+         $oModule = BxDolModule::getInstance($sElsName);
+
+         bx_import('Search', $oModule->_aModule);
+         $sClass = 'BxElsSearch';
+    }
+
+    return $sClass;
+}
+
 function bx_ai_process_agents_call_queue($bFinishRequest = true, $bExit = true)
 {
     if ($bFinishRequest) {
@@ -2924,7 +2940,10 @@ function bx_ai_process_agents_call_queue($bFinishRequest = true, $bExit = true)
         foreach ($GLOBALS['glAgentsCallQueue'] as $r) 
         {
             $sMessage = $oAi->callAgent($r['type'], $r['agent'], $r['params']);
-
+            if (null == $sMessage) {
+                // TODO: maybe reply with some empty message
+                continue;
+            }
             $oParsedown = new Parsedown();
             $oParsedown->setSafeMode(true);
             $sMessageHtml = $oParsedown->text($sMessage);

+ 4 - 3
install/sql/system.sql

@@ -7276,12 +7276,13 @@ CREATE TABLE IF NOT EXISTS `sys_agents_tools` (
 );
 
 INSERT INTO `sys_agents_tools` (`type`, `title`, `docs`, `params`, `params_user`, `duplicate`, `changed`, `active`) VALUES
-('mysql_schema', 'MySQL Schema', 'This tool allows agents to understand the structure of the database, enabling them to construct intelligent queries without requiring you to hardcode table structures or relationships into the prompts. This tool essentially gives your agent the equivalent of a database administrator’s understanding of your schema, allowing it to craft queries that respect your data model and take advantage of existing indexes.  \r\nIt reads all tables by default, but it\'s possible to limit it to a certain number of tables by providing the list in the `tables` parameter. By limiting the schema scope, you can create specialized agents that focus on specific areas of the site.', '{\"tables\":[]}', NULL, 0, 0, 1),
-('mysql_select', 'MySQL Select', 'Use this tool to make your agent able to run SELECT query against the database. It\'s like read-only access without ability to change anything in the DB. It works the best in par of MySQL Schema tool.  \r\nNo parameters are needed for this tool.', '{}', NULL, 0, 0, 1),
+('mysql_schema', 'MySQL Schema', 'This tool allows agents to understand the structure of the database, enabling them to construct intelligent queries without requiring you to hardcode table structures or relationships into the prompts. This tool essentially gives your agent the equivalent of a database administrator''s understanding of your schema, allowing it to craft queries that respect your data model and take advantage of existing indexes.  \r\nIt reads all tables by default, but it''s possible to limit it to a certain number of tables by providing the list in the `tables` parameter. By limiting the schema scope, you can create specialized agents that focus on specific areas of the site.', '{\"tables\":[]}', NULL, 0, 0, 1),
+('mysql_select', 'MySQL Select', 'Use this tool to make your agent able to run SELECT query against the database. It''s like read-only access without ability to change anything in the DB. It works the best in par of MySQL Schema tool.  \r\nNo parameters are needed for this tool.', '{}', NULL, 0, 0, 1),
 ('mysql_write', 'MySQL Write', 'Use this tool to make your agent able to performs write operations against the database (INSERT, UPDATE, DELETE). It works the best in par of MySQL Schema tool.   \r\nNo parameters are needed for this tool.  \r\n**USE WITH CAUTION**', '{}', NULL, 0, 0, 0),
 ('content_structure', 'Content modules structure', 'This tool allows agents to understand the structure of content modules fields,  allowing them to call content adding and editing with correct data. It provide knowledge of what content modules are available and what the fields in them, event if fields are changed in fields builder.', '{}', NULL, 0, 0, 1),
 ('content_get', 'Content modules get', 'This tool allows agents to get info about specific content by module name and content id.', '{}', NULL, 0, 0, 1),
-('content_change', 'Content modules change', 'This tool allows agents to add, update or delete content. It works with the "content_structure" tool to get knowledge about content modules fields for add and update actions.', '{}', NULL, 0, 0, 1);
+('content_change', 'Content modules change', 'This tool allows agents to add, update or delete content. It works with the "content_structure" tool to get knowledge about content modules fields for add and update actions.', '{}', NULL, 0, 0, 1),
+('content_search', 'Content modules search', 'This tool allows agents to search for content items based on a keyword and optional sections.', '{}', NULL, 0, 0, 1);
 
 CREATE TABLE IF NOT EXISTS `sys_agents_chat_history` (
   `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,

+ 1 - 10
searchKeywordContent.php

@@ -13,16 +13,7 @@ require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
 if (bx_get('keyword')){
     bx_import('BxDolLanguages');
 
-    $sClass = 'BxDolSearch';
-
-    $sElsName = 'bx_elasticsearch';
-    $sElsMethod = 'is_configured';
-    if(BxDolRequest::serviceExists($sElsName, $sElsMethod) && BxDolService::call($sElsName, $sElsMethod)) {
-         $oModule = BxDolModule::getInstance($sElsName);
-
-         bx_import('Search', $oModule->_aModule);
-         $sClass = 'BxElsSearch';
-    }
+    $sClass = bx_get_search_class_name();
 
     $o = new $sClass(bx_get('section'));
     $o->setLiveSearch(bx_get('live_search') ? 1 : 0);

+ 42 - 0
template/scripts/BxBaseServiceContent.php

@@ -286,6 +286,48 @@ class BxBaseServiceContent extends BxDol
             return $o->getContentInfoAPI($iContentId, false);
     }
 
+    /**
+     * @page service Service Calls
+     * @section bx_system_general System Services 
+     * @subsection bx_system_general-content-objects Content Objects
+     * @subsubsection bx_system_general_cnt-search Search content
+     * 
+     * @code bx_srv('system', 'search', ["test"], 'TemplServiceContent'); @endcode
+     * @code {{~system:search:TemplServiceContent["test"]~}} @endcode
+     *
+     * Search for posts containing the keyword "test":
+     * @code curl -s --cookie "memberSession=SESSIONIDHERE" -H "Authorization: Bearer APIKEYHERE" "http://example.com/api.php?r=system/search/TemplServiceContent&params[]=test" @endcode
+     * 
+     * @param $sKeyword keyword to search for
+     * @param $mixedSections content sections to search in, for example ['bx_posts', 'bx_persons'], if empty - search in all sections
+     * @param $per_page number of results per page
+     * @param $start start showing from result number
+     * @return content info array on success or empty array on error or not found
+     * 
+     * @see BxBaseServiceContent::serviceSearch
+     */
+    /** 
+     * @ref bx_system_general_cnt-search "Search content"
+     */
+    public function serviceSearch (string $sKeyword, array|string $mixedSections = '', ?int $per_page = 5, ?int $start = 0): array
+    {
+        $sClass = bx_get_search_class_name();
+        $o = new $sClass($mixedSections);
+
+        $o->setApiOutput(true);
+        $o->setLiveSearch(true);
+        $o->setDataProcessing(true);
+        $o->setCustomSearchCondition(['keyword' => $sKeyword]);
+        $o->setCustomCurrentCondition([
+            'paginate' => [
+                'forceStart' => $start,
+                'perPage' => $per_page,
+            ]
+        ]);
+
+        return $o->response();
+    }
+
     /**
      * @page service Service Calls
      * @section bx_system_general System Services