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