xref: /plugin/aichat/ModelFactory.php (revision 0de7e020fcc340c97acd36e48cdb20a9d43528b6)
1c2b7a1f7SAndreas Gohr<?php
2c2b7a1f7SAndreas Gohr
3c2b7a1f7SAndreas Gohrnamespace dokuwiki\plugin\aichat;
4c2b7a1f7SAndreas Gohr
5c2b7a1f7SAndreas Gohruse dokuwiki\plugin\aichat\Model\ChatInterface;
6c2b7a1f7SAndreas Gohruse dokuwiki\plugin\aichat\Model\EmbeddingInterface;
7c2b7a1f7SAndreas Gohr
8c2b7a1f7SAndreas Gohrclass ModelFactory
9c2b7a1f7SAndreas Gohr{
10c2b7a1f7SAndreas Gohr    /** @var array The plugin configuration */
11c2b7a1f7SAndreas Gohr    protected array $config;
12c2b7a1f7SAndreas Gohr
13c2b7a1f7SAndreas Gohr    public $chatModel;
14c2b7a1f7SAndreas Gohr    public $rephraseModel;
15c2b7a1f7SAndreas Gohr    public $embeddingModel;
16c2b7a1f7SAndreas Gohr
17c2b7a1f7SAndreas Gohr    protected $debug = false;
18c2b7a1f7SAndreas Gohr
19c2b7a1f7SAndreas Gohr    /**
20c2b7a1f7SAndreas Gohr     * @param array $config The plugin configuration
21c2b7a1f7SAndreas Gohr     */
22c2b7a1f7SAndreas Gohr    public function __construct(array $config)
23c2b7a1f7SAndreas Gohr    {
24c2b7a1f7SAndreas Gohr        $this->config = $config;
25c2b7a1f7SAndreas Gohr    }
26c2b7a1f7SAndreas Gohr
27c2b7a1f7SAndreas Gohr    /**
28*0de7e020SAndreas Gohr     * Update the configuration and reset the cached models
29*0de7e020SAndreas Gohr     *
30*0de7e020SAndreas Gohr     * @param array $config The new (partial) configuration
31*0de7e020SAndreas Gohr     */
32*0de7e020SAndreas Gohr    public function updateConfig(array $config)
33*0de7e020SAndreas Gohr    {
34*0de7e020SAndreas Gohr        $this->config = array_merge($this->config, $config);
35*0de7e020SAndreas Gohr        $this->chatModel = null;
36*0de7e020SAndreas Gohr        $this->rephraseModel = null;
37*0de7e020SAndreas Gohr        $this->embeddingModel = null;
38*0de7e020SAndreas Gohr    }
39*0de7e020SAndreas Gohr
40*0de7e020SAndreas Gohr    /**
41c2b7a1f7SAndreas Gohr     * Set the debug flag for all models
42c2b7a1f7SAndreas Gohr     *
43c2b7a1f7SAndreas Gohr     * @param bool $debug
44c2b7a1f7SAndreas Gohr     */
45c2b7a1f7SAndreas Gohr    public function setDebug(bool $debug = true)
46c2b7a1f7SAndreas Gohr    {
47c2b7a1f7SAndreas Gohr        $this->debug = $debug;
48c2b7a1f7SAndreas Gohr        $this->getChatModel()->setDebug($debug);
49c2b7a1f7SAndreas Gohr        $this->getRephraseModel()->setDebug($debug);
50c2b7a1f7SAndreas Gohr        $this->getEmbeddingModel()->setDebug($debug);
51c2b7a1f7SAndreas Gohr    }
52c2b7a1f7SAndreas Gohr
53c2b7a1f7SAndreas Gohr    /**
54c2b7a1f7SAndreas Gohr     * Access a cached Chat Model
55c2b7a1f7SAndreas Gohr     *
56c2b7a1f7SAndreas Gohr     * @return ChatInterface
57c2b7a1f7SAndreas Gohr     * @throws \Exception
58c2b7a1f7SAndreas Gohr     */
59c2b7a1f7SAndreas Gohr    public function getChatModel()
60c2b7a1f7SAndreas Gohr    {
61c2b7a1f7SAndreas Gohr        if ($this->chatModel instanceof ChatInterface) {
62c2b7a1f7SAndreas Gohr            return $this->chatModel;
63c2b7a1f7SAndreas Gohr        }
64c2b7a1f7SAndreas Gohr        $this->chatModel = $this->loadModel('chat', $this->config['chatmodel']);
65*0de7e020SAndreas Gohr        $this->chatModel->setDebug($this->debug);
66c2b7a1f7SAndreas Gohr        return $this->chatModel;
67c2b7a1f7SAndreas Gohr    }
68c2b7a1f7SAndreas Gohr
69c2b7a1f7SAndreas Gohr    /**
70c2b7a1f7SAndreas Gohr     * Access a cached Rephrase Model
71c2b7a1f7SAndreas Gohr     *
72c2b7a1f7SAndreas Gohr     * @return ChatInterface
73c2b7a1f7SAndreas Gohr     * @throws \Exception
74c2b7a1f7SAndreas Gohr     */
75c2b7a1f7SAndreas Gohr    public function getRephraseModel()
76c2b7a1f7SAndreas Gohr    {
77c2b7a1f7SAndreas Gohr        if ($this->rephraseModel instanceof ChatInterface) {
78c2b7a1f7SAndreas Gohr            return $this->rephraseModel;
79c2b7a1f7SAndreas Gohr        }
80c2b7a1f7SAndreas Gohr        $this->rephraseModel = $this->loadModel('chat', $this->config['chatmodel']);
81*0de7e020SAndreas Gohr        $this->rephraseModel->setDebug($this->debug);
82c2b7a1f7SAndreas Gohr        return $this->rephraseModel;
83c2b7a1f7SAndreas Gohr    }
84c2b7a1f7SAndreas Gohr
85c2b7a1f7SAndreas Gohr    /**
86c2b7a1f7SAndreas Gohr     * Access a cached Embedding Model
87c2b7a1f7SAndreas Gohr     *
88c2b7a1f7SAndreas Gohr     * @return EmbeddingInterface
89c2b7a1f7SAndreas Gohr     */
90c2b7a1f7SAndreas Gohr    public function getEmbeddingModel()
91c2b7a1f7SAndreas Gohr    {
92c2b7a1f7SAndreas Gohr        if ($this->embeddingModel instanceof EmbeddingInterface) {
93c2b7a1f7SAndreas Gohr            return $this->embeddingModel;
94c2b7a1f7SAndreas Gohr        }
95c2b7a1f7SAndreas Gohr        $this->embeddingModel = $this->loadModel('embedding', $this->config['embedmodel']);
96*0de7e020SAndreas Gohr        $this->embeddingModel->setDebug($this->debug);
97c2b7a1f7SAndreas Gohr        return $this->embeddingModel;
98c2b7a1f7SAndreas Gohr    }
99c2b7a1f7SAndreas Gohr
100c2b7a1f7SAndreas Gohr    /**
101c2b7a1f7SAndreas Gohr     * Get all known models
102c2b7a1f7SAndreas Gohr     *
103c2b7a1f7SAndreas Gohr     * A (new) instance is returned for each model that is available through the current configuration.
104c2b7a1f7SAndreas Gohr     *
105c2b7a1f7SAndreas Gohr     * @param bool $availableOnly Only return models that are available
106c2b7a1f7SAndreas Gohr     * @param string $typeOnly Only return models of this type ('chat' or 'embedding')
107c2b7a1f7SAndreas Gohr     * @return array
108c2b7a1f7SAndreas Gohr     */
109c2b7a1f7SAndreas Gohr    public function getModels($availableOnly = false, $typeOnly = '')
110c2b7a1f7SAndreas Gohr    {
111c2b7a1f7SAndreas Gohr        $result = [
112c2b7a1f7SAndreas Gohr            'chat' => [],
113c2b7a1f7SAndreas Gohr            'embedding' => [],
114c2b7a1f7SAndreas Gohr        ];
115c2b7a1f7SAndreas Gohr
116c2b7a1f7SAndreas Gohr        $jsons = glob(__DIR__ . '/Model/*/models.json');
117c2b7a1f7SAndreas Gohr        foreach ($jsons as $json) {
118c2b7a1f7SAndreas Gohr            $models = json_decode(file_get_contents($json), true);
119c2b7a1f7SAndreas Gohr            foreach ($models as $type => $model) {
120c2b7a1f7SAndreas Gohr                $namespace = basename(dirname($json));
121c2b7a1f7SAndreas Gohr                foreach ($model as $name => $info) {
122c2b7a1f7SAndreas Gohr                    try {
123c2b7a1f7SAndreas Gohr                        $info['instance'] = $this->loadModel($type, "$namespace $name");
124c2b7a1f7SAndreas Gohr                        $info['instance']->setDebug($this->debug);
125c2b7a1f7SAndreas Gohr                    } catch (\Exception $e) {
126c2b7a1f7SAndreas Gohr                        if ($availableOnly) continue;
127c2b7a1f7SAndreas Gohr                        $info['instance'] = false;
128c2b7a1f7SAndreas Gohr                    }
129c2b7a1f7SAndreas Gohr
130c2b7a1f7SAndreas Gohr                    $result[$type]["$namespace $name"] = $info;
131c2b7a1f7SAndreas Gohr                }
132c2b7a1f7SAndreas Gohr            }
133c2b7a1f7SAndreas Gohr        }
134c2b7a1f7SAndreas Gohr
135c2b7a1f7SAndreas Gohr        return $typeOnly ? $result[$typeOnly] : $result;
136c2b7a1f7SAndreas Gohr    }
137c2b7a1f7SAndreas Gohr
138c2b7a1f7SAndreas Gohr
139c2b7a1f7SAndreas Gohr    /**
140c2b7a1f7SAndreas Gohr     * Initialize a model by config name
141c2b7a1f7SAndreas Gohr     *
142c2b7a1f7SAndreas Gohr     * @param string $type 'chat' or 'embedding'
143c2b7a1f7SAndreas Gohr     * @param string $name The full model name including provider
144c2b7a1f7SAndreas Gohr     * @return ChatInterface|EmbeddingInterface
145c2b7a1f7SAndreas Gohr     * @throws \Exception
146c2b7a1f7SAndreas Gohr     */
147c2b7a1f7SAndreas Gohr    public function loadModel(string $type, string $name)
148c2b7a1f7SAndreas Gohr    {
149c2b7a1f7SAndreas Gohr        $type = ucfirst(strtolower($type));
150c2b7a1f7SAndreas Gohr        $prefix = '\\dokuwiki\\plugin\\aichat\\Model\\';
151c2b7a1f7SAndreas Gohr        $cname = $type . 'Model';
152c2b7a1f7SAndreas Gohr        $interface = $prefix . $type . 'Interface';
153c2b7a1f7SAndreas Gohr
154c2b7a1f7SAndreas Gohr
155c2b7a1f7SAndreas Gohr        [$namespace, $model] = sexplode(' ', $name, 2, '');
156c2b7a1f7SAndreas Gohr        $class = $prefix . $namespace . '\\' . $cname;
157c2b7a1f7SAndreas Gohr
158c2b7a1f7SAndreas Gohr        if (!class_exists($class)) {
159c2b7a1f7SAndreas Gohr            throw new \Exception("No $cname found for $namespace");
160c2b7a1f7SAndreas Gohr        }
161c2b7a1f7SAndreas Gohr
162c2b7a1f7SAndreas Gohr        try {
163c2b7a1f7SAndreas Gohr            $instance = new $class($model, $this->config);
164c2b7a1f7SAndreas Gohr        } catch (\Exception $e) {
165c2b7a1f7SAndreas Gohr            throw new \Exception("Failed to initialize $cname for $namespace: " . $e->getMessage(), 0, $e);
166c2b7a1f7SAndreas Gohr        }
167c2b7a1f7SAndreas Gohr
168c2b7a1f7SAndreas Gohr        if (!($instance instanceof $interface)) {
169c2b7a1f7SAndreas Gohr            throw new \Exception("$cname for $namespace does not implement $interface");
170c2b7a1f7SAndreas Gohr        }
171c2b7a1f7SAndreas Gohr
172c2b7a1f7SAndreas Gohr        return $instance;
173c2b7a1f7SAndreas Gohr    }
174c2b7a1f7SAndreas Gohr}
175