diff --git a/ai/classes/aiclient.php b/ai/classes/aiclient.php index 21cb025a6809..fd1d6375e9e9 100644 --- a/ai/classes/aiclient.php +++ b/ai/classes/aiclient.php @@ -21,11 +21,11 @@ public function __construct( } public function get_embeddings_url(): string { - return $this->provider->get('baseurl') . $this->provider->get('embeddings'); + return $this->provider->get('baseurl') . $this->provider->get('embeddingsurl'); } public function get_chat_completions_url(): string { - return $this->provider->get('baseurl') . $this->provider->get('completions'); + return $this->provider->get('baseurl') . $this->provider->get('completionsurl'); } /** diff --git a/ai/classes/aiprovider.php b/ai/classes/aiprovider.php index 8496d55309f8..5eb9b28a0e80 100644 --- a/ai/classes/aiprovider.php +++ b/ai/classes/aiprovider.php @@ -153,7 +153,10 @@ public function get_settings_for_user($user) { $courseids = []; if ($this->get('contextid') == self::CONTEXT_ALL_MY_COURSES) { $courseids = array_keys($mycourseids); + } else if ($this->get('contextid') == 0) { + $context = \context_system::instance(); } else { + $context = \context::instance_by_id($this->get('contextid')); if ($context->contextlevel == CONTEXT_COURSE) { // Check that the specific course is also in the user's list of courses. @@ -179,7 +182,7 @@ public function get_settings_for_user($user) { // TODO token counting. /** * We're overriding this whilst we don't have a real DB table. - * @param $filters + * @param $filters This can have the contexts, but also can have 'excludesystem' set to not include system AI Providers. * @param $sort * @param $order * @param $skip @@ -188,7 +191,6 @@ public function get_settings_for_user($user) { */ public static function get_records($filters = [], $sort = '', $order = 'ASC', $skip = 0, $limit = 0) { global $_ENV; - $records = parent::get_records($filters, $sort, $order, $skip, $limit); // $records = []; // $fake = new static(0, (object) [ // 'id' => 1, @@ -247,6 +249,13 @@ public static function get_records($filters = [], $sort = '', $order = 'ASC', $s } else { $targetcontext = \context::instance_by_id($targetcontextid); } + $systemproviders = []; + if (!isset($filters['excludesystem'])) { + $systemfilter = $filters; + $systemfilter['contextid'] = 0; + $systemproviders = parent::get_records($systemfilter, $sort, $order, $skip, $limit); + } + $records = parent::get_records($filters, $sort, $order, $skip, $limit); $records = array_filter($records, function($record) use ($filters, $targetcontext) { $result = true; foreach($filters as $key => $value) { @@ -255,7 +264,7 @@ public static function get_records($filters = [], $sort = '', $order = 'ASC', $s if ($providercontextid == self::CONTEXT_ALL_MY_COURSES) { // More problematic. $result = $result & true; - } else if ($providercontextid == null) { + } else if ($providercontextid == 0) { // System provider so always matches. $result = $result & true; } else { @@ -263,7 +272,7 @@ public static function get_records($filters = [], $sort = '', $order = 'ASC', $s $providercontextid ); $ischild = $targetcontext->is_child_of($providercontext, true); - debugging("IS child ". (int)$ischild, DEBUG_DEVELOPER); +// debugging("IS child ". (int)$ischild, DEBUG_DEVELOPER); $result = $result & $ischild; } }else { @@ -275,7 +284,7 @@ public static function get_records($filters = [], $sort = '', $order = 'ASC', $s } return $result; }); - + $records = array_merge($systemproviders, $records); return $records; } diff --git a/ai/classes/api.php b/ai/classes/api.php index c4352199e6fe..9b024dfb8e15 100644 --- a/ai/classes/api.php +++ b/ai/classes/api.php @@ -36,12 +36,13 @@ public static function get_provider(int $id): AIProvider { * @return array */ public static function get_providers($contextid = null, $allowchat = null, $allowembeddings = null) { - $requirements = ['contextid','allowchat', 'allowembeddings']; + $requirements = ['contextid', 'allowchat', 'allowembeddings']; // Filtering AI providers that are available to $contextid, walking up the // tree when we only have the contextid the AIProvider is set *on* is going to take // more work. $filters = []; foreach($requirements as $req) { + $reqparam = ${$req}; // Null means we don't consider it. if (!is_null($reqparam)) { @@ -50,7 +51,7 @@ public static function get_providers($contextid = null, $allowchat = null, $allo $filters[$req] = $reqparam; } } - //debugging(print_r($filters,1), DEBUG_DEVELOPER); + debugging(print_r($filters,1), DEBUG_DEVELOPER); $providers = aiprovider::get_records($filters); return array_values($providers); } diff --git a/ai/classes/form/openaiapiprovider.php b/ai/classes/form/openaiapiprovider.php index 3ce2bd34346a..a3c5f7a63cd3 100644 --- a/ai/classes/form/openaiapiprovider.php +++ b/ai/classes/form/openaiapiprovider.php @@ -25,8 +25,8 @@ public function definition() { $mform = $this->_form; $provider = $this->get_persistent(); - $mform->addElement('html','intro', 'hello'); - +// $mform->addElement('html','intro', 'hello'); + $mform->addElement('header', 'features', get_string('general', 'ai')); // Name. $mform->addElement('text', 'name', get_string('providername', 'ai')); $mform->addRule('name', null, 'required', null, 'client'); @@ -34,6 +34,7 @@ public function definition() { $mform->addHelpButton('name', 'providername', 'ai'); $mform->addElement('advcheckbox', 'enabled', get_string('enabled', 'ai')); $mform->addHelpButton('enabled', 'enabled', 'ai'); + $mform->setDefault('enabled', true); // Client Secret. $mform->addElement('text','baseurl', get_string('baseurl', 'ai')); $mform->setType('baseurl', PARAM_URL); diff --git a/ai/classes/logger.php b/ai/classes/logger.php new file mode 100644 index 000000000000..574532ea781a --- /dev/null +++ b/ai/classes/logger.php @@ -0,0 +1,19 @@ +logpath = $logdir . '/' . $identifier . '.log'; + } + public function write($message) { + $ts = microtime(true); + $f = fopen($this->logpath, 'a'); + if(flock($f, LOCK_EX | LOCK_NB)) { + fwrite($f, "{$ts} - {$message}\n"); + flock($f, LOCK_UN); + } + fclose($f); + } +} diff --git a/ai/cli/query.php b/ai/cli/query.php new file mode 100644 index 000000000000..61e70eb59f64 --- /dev/null +++ b/ai/cli/query.php @@ -0,0 +1,104 @@ +libdir.'/clilib.php'); +use core_ai\api; +use core_ai\aiclient; +use core_ai\aiprovider; +[$options, $unrecognized] = cli_get_params( + [ + 'help' => false, + 'prompt' => false, + 'providerid' => 1, + 'courses' => [2], + 'contexts' => [], + 'limit' => 3, + 'userid' => false + ],[ + 'h' => 'help', + 'p' => 'providerid', + 'l' => 'limit' + ] + +); + +if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); +} +$help = <<userprompt = $options['prompt']; + $formdata->contextids = []; + $formdata->mycoursesonly = false; + $formdata->courseids = $options['courses']; + + $settings = $provider->get_settings_for_user($user); + $settings['userquery'] = $formdata->userprompt; + $settings['courseids'] = $options['courses']; + var_dump($settings); + $vector = $client->embed_query($formdata->userprompt); + $settings['vector'] = $vector; + + $limitcourseids = $manager->build_limitcourseids($formdata); + $limitcontextids = $formdata->contextids; + $contextinfo = $manager->get_areas_user_accesses($limitcourseids, $limitcontextids); + + mtrace("Manager settings"); + mtrace("Restrict to courses: "); + var_dump($limitcourseids); + mtrace("Contextinfo returned: "); + var_dump($contextinfo); + + mtrace('Performing search'); + $docs = $manager->search((object)$settings); + var_dump($docs); + +} diff --git a/ai/index.php b/ai/index.php index d64cb6bf9029..d89d02c6ba94 100644 --- a/ai/index.php +++ b/ai/index.php @@ -54,7 +54,8 @@ $mform = new \core_ai\form\openaiapiprovider(null, [ 'persistent' => $provider, 'type' => $type, - 'contextid' => $incontextid + 'contextid' => $incontextid, + 'enabled' => true, ]); } @@ -64,20 +65,24 @@ } else if ($action == api::ACTION_EDIT_PROVIDER) { // Handle edit. if ($mform->is_cancelled()) { - echo 'cancelled'; + redirect(new moodle_url('/ai/index.php')); } - if ($mform->is_submitted()) { - echo 'submitted'; - } - echo 'validated '. (int)$mform->is_validated(); if ($data = $mform->get_data()) { var_dump($data); - if (!empty($data->id)) { - core_ai\api::update_provider($data); - } else { - core_ai\api::create_provider($data); + try { + if (!empty($data->id)) { + core_ai\api::update_provider($data); + } else { + core_ai\api::create_provider($data); + } + redirect(new moodle_url('/ai/index.php')); + } + catch (moodle_exception $e) { + throw $e; } + + exit(); } else { echo $OUTPUT->header(); diff --git a/ai/test.php b/ai/test.php new file mode 100644 index 000000000000..c62791ed4051 --- /dev/null +++ b/ai/test.php @@ -0,0 +1,19 @@ +table_exists($table)) { // Define table aiprovider to be created. - $table = new xmldb_table('aiprovider'); + // Adding fields to table aiprovider. $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); diff --git a/search/engine/solrrag/classes/engine.php b/search/engine/solrrag/classes/engine.php index 5fb1850ac0fd..c7c56d9696e2 100644 --- a/search/engine/solrrag/classes/engine.php +++ b/search/engine/solrrag/classes/engine.php @@ -3,6 +3,7 @@ namespace search_solrrag; use core_ai\api; +use core_ai\logger; use search_solrrag\document; use search_solrrag\schema; //require_once($CFG->dirroot . "/search/engine/solrrag/lib.php"); @@ -20,7 +21,7 @@ class engine extends \search_solr\engine { */ protected ?AIClient $aiclient = null; protected ?AIProvider $aiprovider = null; - + protected ?logger $logger = null; public function __construct(bool $alternateconfiguration = false) { parent::__construct($alternateconfiguration); @@ -345,7 +346,9 @@ public function execute_query($filters, $accessinfo, $limit = 0) { // We may get accessinfo, but we actually should determine our own ones to apply too // But we can't access the "manager" class' get_areas_user_accesses function, and // that's already been called based on the configuration / data from the user - return $this->execute_similarity_query($filters, $accessinfo, $limit); + $docs = $this->execute_similarity_query($filters, $accessinfo, $limit); + var_dump($docs); + return $docs; } else { // debugging("Running regular search", DEBUG_DEVELOPER); // print_r($filters); @@ -489,7 +492,10 @@ public function execute_similarity_query(\stdClass $filters, \stdClass $accessin $requesturl = $this->get_connection_url('/select'); $requesturl->param('fl', 'id,areaid,score,content'); $requesturl->param('wt', 'xml'); - $requesturl->param('fq', implode("&", $filterqueries)); + foreach($filterqueries as $fq) { + $requesturl->param('fq', $fq); + } +// $requesturl->param('fq', implode("&", $filterqueries)); $params = [ "query" => $requestbody, @@ -510,7 +516,7 @@ public function execute_similarity_query(\stdClass $filters, \stdClass $accessin debugging($message, DEBUG_DEVELOPER); } else if (isset($info['http_code']) && ($info['http_code'] !== 200)) { // Unexpected HTTP response code. - $message = 'Error while indexing file with document id ' ; + $message = 'Error while querying for documents ' ; // Try to get error message out of msg or title if it exists. if (preg_match('|]*name="msg"[^>]*>(.*?)|i', $result, $matches)) { $message .= ': ' . $matches[1];