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