1<?php
2
3use dokuwiki\plugin\aichat\AbstractCLI;
4use splitbrain\phpcli\Options;
5
6/**
7 * DokuWiki Plugin aichat (CLI Component)
8 *
9 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
10 * @author  Andreas Gohr <gohr@cosmocode.de>
11 */
12class cli_plugin_aichat_dev extends AbstractCLI
13{
14    /** @inheritDoc */
15    protected function setup(Options $options)
16    {
17        $options->setHelp('Helps with development of this plugin');
18
19        $options->registerCommand('update', 'Update the model data');
20    }
21
22    /** @inheritDoc */
23    protected function main(Options $options)
24    {
25        parent::main($options);
26
27        switch ($options->getCmd()) {
28
29            case 'update':
30                $this->updateModelData();
31                break;
32            default:
33                echo $options->help();
34        }
35    }
36
37    protected function updateModelData()
38    {
39
40        $http = new \dokuwiki\HTTP\DokuHTTPClient();
41        $url = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
42        $response = $http->get($url);
43        if ($response === false) {
44            $this->error('Failed to fetch model data');
45            return 1;
46        }
47        $models = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
48
49        $ourProviders = [
50            'anthropic' => [
51                'name' => 'Anthropic',
52            ],
53            'groq' => [
54                'name' => 'Groq',
55                'skip' => '/-preview$/'
56            ],
57            'mistral' => [
58                'name' => 'Mistral',
59                'skip' => '/-\d\d\d\d$/',
60            ],
61            'openai' => [
62                'name' => 'OpenAI',
63                'skip' => '/(-\d\d\d\d-\d\d-\d\d|-preview|-\d\d\d\d)$|^ft:/'
64            ],
65            'reka' => [
66                'name' => 'Reka',
67            ],
68            'voyage' => [
69                'name' => 'VoyageAI',
70                'skip' => '/-(01|02)(-|$)/', // outdated models
71            ],
72            'gemini' => [
73                'name' => 'Gemini',
74                'skip' => '/(-preview|-exp|-\d\d\d$)/'
75            ],
76        ];
77
78        // load existing models
79        foreach ($ourProviders as $provider => $data) {
80            $ourProviders[$provider]['models'] = json_decode(
81                file_get_contents(__DIR__ . '/../Model/' . $data['name'] . '/' . 'models.json'),
82                true
83            );
84        }
85
86        // update models
87        foreach ($models as $model => $data) {
88            if (!isset($ourProviders[$data['litellm_provider']])) continue;
89            if (!in_array($data['mode'], ['chat', 'embedding'])) continue;
90            $provider = $data['litellm_provider'];
91            $model = explode('/', $model);
92            $model = array_pop($model);
93
94            if (isset($ourProviders[$provider]['skip']) && preg_match($ourProviders[$provider]['skip'], $model)) {
95                $this->info('Skipping ' . $provider . ' ' . $model);
96                continue;
97            }
98            $this->success("$provider $model");
99
100            $oldmodel = $ourProviders[$provider]['models'][$data['mode']][$model] ?? [];
101            $newmodel = [
102                "description" => $oldmodel['description'] ?? $data['source'] ?? '',
103                "inputTokens" => $data['max_input_tokens'] ?? $data['max_tokens'],
104                "inputTokenPrice" => round($data['input_cost_per_token'] * 1_000_000, 2),
105            ];
106
107            if ($data['mode'] === 'chat') {
108                $newmodel['outputTokens'] = $data['max_output_tokens'];
109                $newmodel['outputTokenPrice'] = round($data['output_cost_per_token'] * 1_000_000, 2);
110            } else {
111                if (isset($oldmodel['dimensions'])) {
112                    $newmodel['dimensions'] = $oldmodel['dimensions'];
113                } else {
114                    $this->warning('No dimensions for ' . $provider . ' ' . $model . '. Check manually!');
115                    $newmodel['dimensions'] = 1536;
116                }
117            }
118            $ourProviders[$provider]['models'][$data['mode']][$model] = $newmodel;
119        }
120
121        // save models
122        foreach ($ourProviders as $data) {
123            file_put_contents(
124                __DIR__ . '/../Model/' . $data['name'] . '/' . 'models.json',
125                json_encode($data['models'], JSON_PRETTY_PRINT)
126            );
127        }
128
129        return 0;
130    }
131}
132