Explorar o código

#5406 AI Agents revamp: content change tool

Aleksandr Trofimov hai 6 meses
pai
achega
5ddb73d863

+ 16 - 5
inc/classes/BxDolAIToolContentChange.php

@@ -19,7 +19,7 @@ class BxDolAIToolContentChange extends Tool
     {
         parent::__construct(
             'content_change',
-            'Use this tool to add, update or delete content.  If needed, use info from "content_structure" tool 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.',
         );
     }
     
@@ -41,7 +41,7 @@ class BxDolAIToolContentChange extends Tool
             new ToolProperty(
                 name: 'content_id',
                 type: PropertyType::INTEGER,
-                description: 'The ID of the content item to delete or update, use 0 for creating new content.',
+                description: 'The ID of the content item to delete or update, use 0 for creating new content, never pass 0 for update and delete actions.',
                 required: true
             ),
             new ToolProperty(
@@ -49,13 +49,22 @@ class BxDolAIToolContentChange extends Tool
                 type: PropertyType::OBJECT,
                 description: 'The data to update the content item with, as key-value pairs, where key is the field name and value is the value. Skip this parameter for delete action, for create and update actions this is required.',
                 required: false
+            ),
+            new ToolProperty(
+                name: 'profile_id',
+                type: PropertyType::INTEGER,
+                description: 'The ID of the profile to perform the action on behalf of.',
+                required: false
             )
         ];
     }
 
-    public function __invoke(string $action, string $module, ?int $content_id = 0, ?array $data = null): string
+    public function __invoke(string $action, string $module, ?int $content_id = 0, ?array $data = null, ?int $profile_id = 0): string
     {
-        echoDbgLog("content_change tool called with action: {$action}, module: {$module}, content_id: {$content_id}, data: " . json_encode($data));
+        if ($profile_id) {
+            $GLOBALS['glForceCurrentProfileId'] = $profile_id;
+        }
+
         switch ($action) {
             case 'update':
                 $a = bx_srv('system', 'update', [$module, $content_id, $data], 'TemplServiceContent');
@@ -67,9 +76,11 @@ class BxDolAIToolContentChange extends Tool
                 $a = bx_srv('system', 'delete', [$module, $content_id], 'TemplServiceContent');
                 break;
             default:
-                return "Error: Invalid action";
+                $a = ['code' => 400, 'error' => 'Error: Invalid action'];
         }
 
+        $GLOBALS['glForceCurrentProfileId'] = 0;
+
         return json_encode($a);
     }
 }

+ 5 - 4
inc/classes/BxDolAIToolContentStructure.php

@@ -34,9 +34,7 @@ class BxDolAIToolContentStructure extends Tool
     }
 
     public function __invoke(string $module): string
-    {        
-        echoDbgLog("content_structure tool called with module: {$module}");
-
+    {
         $aContentModules = bx_srv('system', 'modules_list', [true], 'TemplServiceContent');
         $s = $this->formatModulesOutputForLLM($aContentModules);
 
@@ -65,10 +63,13 @@ class BxDolAIToolContentStructure extends Tool
 
     protected function formatStructureOutputForLLM(array $aStructure): string
     {
-
         $s = "# Content modules fields structure\n\n";
 
         foreach ($aStructure as $sModule => $a) {
+            if ($sModule == 'bx_timeline') {
+                // TODO: timeline is broken with PHP error when trying to get fields
+                continue;
+            }
             $s .= "## " . $sModule . "\n\n";
             $s .= $a['db_table'] ? "DB table: " . $a['db_table'] . "\n" : "";
             $s .= $a['db_table_fields'] ? "DB table fields: " . implode(', ', $a['db_table_fields']) . "\n" : "";

+ 8 - 1
inc/classes/BxDolAiAgent.php

@@ -23,11 +23,18 @@ class BxDolAiAgent extends RAG
 
     protected function instructions(): string
     {
+        $aToolsUsage = !empty($this->aAgent['prompt_tools']) ? [$this->aAgent['prompt_tools']] : [];
+        $aToolsUsage[] = "For any tool that requires `profile_id`:
+1. Check if the user request contains an explicit profile_id.
+2. If yes — use the user-provided profile_id.
+3. If no — use {$this->aAgent['profile_id']}.
+Never omit profile_id when the tool supports it. Never use current profile or 0, unless it\'s explicitly requested.";
+
         $oPrompt = new NeuronAI\Agent\SystemPrompt(
             background: [$this->aAgent['prompt_system']],
             steps: !empty($this->aAgent['prompt_steps']) ? [$this->aAgent['prompt_steps']] : [],
             output: !empty($this->aAgent['prompt_output']) ? [$this->aAgent['prompt_output']] : [],
-            toolsUsage: !empty($this->aAgent['prompt_tools']) ? [$this->aAgent['prompt_tools']] : []
+            toolsUsage: $aToolsUsage
         );
         return (string) $oPrompt;
     }

+ 6 - 2
inc/classes/BxDolProfile.php

@@ -116,8 +116,12 @@ class BxDolProfile extends BxDolFactory implements iBxDolProfile
     {
         $oQuery = BxDolProfileQuery::getInstance();
 
-        if (!$mixedProfileId)
-            $mixedProfileId = $oQuery->getCurrentProfileByAccount(getLoggedId(), $bClearCache);
+        if (!$mixedProfileId) {
+            if (isset($GLOBALS['glForceCurrentProfileId']) && $GLOBALS['glForceCurrentProfileId'] > 0)
+                $mixedProfileId = $GLOBALS['glForceCurrentProfileId'];
+            else
+                $mixedProfileId = $oQuery->getCurrentProfileByAccount(getLoggedId(), $bClearCache);
+        }
 
         $aProfileInfo = $oQuery->getInfoById($mixedProfileId);
         if (empty($aProfileInfo['id']) || !BxDolModuleDb::getInstance()->isEnabledByName($aProfileInfo['type']))

+ 2 - 2
template/scripts/BxBaseAccountForms.php

@@ -182,7 +182,7 @@ class BxBaseAccountForms extends BxDolProfileForms
 
     }
 
-    public function createAccount ($aValues)
+    public function createAccount ($aValues, $iAction = BX_PROFILE_ACTION_MANUAL, $bNeedToLogin = true)
     {
         $oForm = $this->getObjectFormAdd ();
         if (!$oForm)
@@ -203,7 +203,7 @@ class BxBaseAccountForms extends BxDolProfileForms
         if (!$iAccountId)
             return array('code' => 500, 'error' => _t('_sys_txt_error_account_creation'));
 
-        $iProfileId = $this->onAccountCreated($iAccountId, $oForm->isSetPendingApproval());
+        $iProfileId = $this->onAccountCreated($iAccountId, $oForm->isSetPendingApproval(), $iAction, $bNeedToLogin);
 
         return [
             'account_id' => $iAccountId,

+ 79 - 36
template/scripts/BxBaseServiceContent.php

@@ -189,6 +189,7 @@ class BxBaseServiceContent extends BxDol
             'db_table' => 'sys_accounts',
             'db_table_fields' => BxDolDb::getInstance()->getFields('sys_accounts')['original'],
         ];
+        $a['system']['db_table_fields'][] = 'auto_send_confrmation_email';
 
         if ($sModule != 'all') {
             if (isset($a[$sModule])) {
@@ -350,19 +351,19 @@ class BxBaseServiceContent extends BxDol
         if ('sys_account' == $sContentObject) {
             $o = BxDolAccount::getInstance($iContentId);
             if (!$o)
-                return false;
+                return ['code' => 404, 'error' => _t('_sys_txt_not_found')];
             return $o->delete(isset($aParams['with_content']) ? $aParams['with_content'] : true, isset($aParams['scheduled']) ? $aParams['scheduled'] : false);
         }
         if (in_array($sContentObject, ['bx_persons', 'bx_organizations'])) {
             $o = BxDolProfile::getInstanceByContentAndType($iContentId, $sContentObject);
             if (!$o)
-                return false;
+                return ['code' => 404, 'error' => _t('_sys_txt_not_found')];
             return $o->delete(false, isset($aParams['with_content']) ? $aParams['with_content'] : true, isset($aParams['force']) ? $aParams['force'] : false);
         }
 
         $o = BxDolContentInfo::getObjectInstance($sContentObject);
         if (!$o)
-            return false;
+            return ['code' => 404, 'error' => _t('_sys_txt_not_found')];
         if ($sErrorMsg = $o->deleteContent($iContentId))
             return ['code' => 500, 'error' => $sErrorMsg];
         else
@@ -387,6 +388,8 @@ class BxBaseServiceContent extends BxDol
      * @param $sContentObject content object name
      * @param $iContentId content id
      * @param $aValues key value pairs to update
+     * @param $sDisplay form display name to use
+     * @param $iProfileId profile ID to perform action on behalf of
      * @return array with code = 0 on success, or array with code != 0 and error message
      * 
      * @see BxBaseServiceContent::serviceUpdate
@@ -394,31 +397,56 @@ class BxBaseServiceContent extends BxDol
     /** 
      * @ref bx_system_general_cnt-update "Update content"
      */
-    public function serviceUpdate ($sContentObject, $iContentId, $aValues, $sDisplay = false)
+    public function serviceUpdate ($sContentObject, $iContentId, $aValues, $sDisplay = false, $iProfileId = 0)
     {
+        if ($iProfileId) {
+            $GLOBALS['glForceCurrentProfileId'] = $iProfileId;
+        }
+        $a = null;
         if ('sys_account' == $sContentObject) {
             $o = BxDolAccount::getInstance($iContentId);
-            if (!$o)
-                return false;
-            $oQuery = BxDolAccountQuery::getInstance();
-            foreach ($aValues as $k => $v) {
-                if (!$oQuery->isFieldExists('sys_accounts', $k))
-                    return ['code' => 500, 'error' => _t('_sys_txt_forms_unknown_field_err', $k)];
+            if (!$o) {
+                $a = ['code' => 404, 'error' => _t('_sys_txt_not_found')];
             }
-            foreach ($aValues as $k => $v) {
-                if (!$oQuery->_updateField($o->id(), $k, $v))
-                    return ['code' => 500, 'error' => _t('_error occured')];
+            else {
+                $oQuery = BxDolAccountQuery::getInstance();
+                foreach ($aValues as $k => $v) {
+                    if (!$oQuery->isFieldExists('sys_accounts', $k)) {
+                        $a = ['code' => 500, 'error' => _t('_sys_txt_forms_unknown_field_err', $k)];
+                        break;
+                    }
+                }
+                if (!$a) {
+                    foreach ($aValues as $k => $v) {
+                        if (!$oQuery->_updateField($o->id(), $k, $v)) {
+                            $a = ['code' => 500, 'error' => _t('_error occured')];
+                            break;
+                        }
+                    }
+                }
+                if (!$a) {
+                    $a = ['code' => 0]; // success
+                }
+            }
+        }
+        else {
+            $o = BxDolContentInfo::getObjectInstance($sContentObject);
+            if (!$o) {
+                $a = ['code' => 404, 'error' => _t('_sys_txt_not_found')];
+            }
+            else {
+                if ($sErrorMsg = $o->updateContent($iContentId, $aValues, $sDisplay))
+                    $a = ['code' => 500, 'error' => $sErrorMsg];
+                else
+                    $a = ['code' => 0]; // success
             }
-            return true;
         }
 
-        $o = BxDolContentInfo::getObjectInstance($sContentObject);
-        if (!$o)
-            return false;
-        if ($sErrorMsg = $o->updateContent($iContentId, $aValues, $sDisplay))
-            return ['code' => 500, 'error' => $sErrorMsg];
-        else
-            return ['code' => 0];
+        if ($iProfileId) {
+            $GLOBALS['glForceCurrentProfileId'] = 0;
+        }
+
+        return $a;
     }
 
     /**
@@ -436,6 +464,7 @@ class BxBaseServiceContent extends BxDol
      * 
      * @param $sContentObject content object name
      * @param $aValues key value pairs to add
+     * @param $iProfileId profile ID to perform action on behalf of
      * @return array with code = 0 on success, or array with code != 0 and error message
      * 
      * @see BxBaseServiceContent::serviceAdd
@@ -443,11 +472,19 @@ class BxBaseServiceContent extends BxDol
     /** 
      * @ref bx_system_general_cnt-add "Add content"
      */
-    public function serviceAdd ($sContentObject, $aValues)
+    public function serviceAdd ($sContentObject, $aValues, $iProfileId = 0)
     {
-        if ('sys_account' == $sContentObject) {
+        if ($iProfileId) {
+            $GLOBALS['glForceCurrentProfileId'] = $iProfileId;
+        }
+                
+        $a = [];
+        if (bx_get_logged_profile_id() == 0) {
+            $a = ['code' => 403, 'error' => 'Permission denied. Please login first.'];
+        }
+        elseif ('sys_account' == $sContentObject || 'system' == $sContentObject) {
             $o = new BxTemplAccountForms();
-            $a = $o->createAccount ($aValues);
+            $a = $o->createAccount ($aValues, BX_PROFILE_ACTION_EXTERNAL, false);
             if (isset($a['account_id']) && isset($aValues['email_confirmed'])) {
                 $o = BxDolAccount::getInstance($a['account_id']);
                 if ($o) {
@@ -460,21 +497,27 @@ class BxBaseServiceContent extends BxDol
                     $o->updatePhoneConfirmed($aValues['phone_confirmed']);
                 }
             }
-            return $a;
+        }
+        else {
+            $o = BxDolContentInfo::getObjectInstance($sContentObject);
+            if (!$o) {
+                $a = ['code' => 404, 'error' => 'Content object(module) not found'];
+            }
+            else {
+                $a = $o->addContent($aValues);
+                if (!(isset($a['code']) && !$a['code'] && isset($a['content']))) {
+                    $a['code'] = 500 + $a['code'];
+                    $a['error'] = $a['message'];
+                    $a['data'] = $a['errors'];
+                }
+            }
         }
 
-        $o = BxDolContentInfo::getObjectInstance($sContentObject);
-        if (!$o)
-            return false;
-        $a = $o->addContent($aValues);
-        if (isset($a['code']) && !$a['code'] && isset($a['content'])) {
-            return $a;
-        } else {
-            $a['code'] = 500 + $a['code'];
-            $a['error'] = $a['message'];
-            $a['data'] = $a['errors'];
-            return $a;
+        if ($iProfileId) {
+            $GLOBALS['glForceCurrentProfileId'] = 0;
         }
+
+        return $a;
     }
 
     /**