xref: /plugin/aichat/Model/Generic/AbstractGenericModel.php (revision 2e22aefbcc83da0bc9ab21a2175b8ada4ba79826)
1<?php
2
3namespace dokuwiki\plugin\aichat\Model\Generic;
4
5use dokuwiki\plugin\aichat\Model\AbstractModel;
6use dokuwiki\plugin\aichat\Model\ChatInterface;
7use dokuwiki\plugin\aichat\Model\EmbeddingInterface;
8use dokuwiki\plugin\aichat\Model\ModelException;
9
10/**
11 * Abstract OpenAI-compatible Model
12 *
13 * This class provides a basic interface to the OpenAI API as implemented by many other providers.
14 * It implements chat and embedding interfaces.
15 */
16abstract class AbstractGenericModel extends AbstractModel implements ChatInterface, EmbeddingInterface
17{
18    /** @var string The API base URL */
19    protected $apiurl = '';
20
21
22    /** @inheritdoc */
23    public function __construct(string $name, array $config)
24    {
25        parent::__construct($name, $config);
26
27        $apiKey = $this->getFromConf($config, 'apikey');
28        $this->http->headers['Authorization'] = 'Bearer ' . $apiKey;
29
30        if($this->apiurl === '') {
31            $this->apiurl = $this->getFromConf($config, 'apiurl');
32        }
33        $this->apiurl = rtrim($this->apiurl, '/');
34    }
35
36    /**
37     * Send a request to the OpenAI API
38     *
39     * @param string $endpoint
40     * @param array $data Payload to send
41     * @return array API response
42     * @throws \Exception
43     */
44    protected function request($endpoint, $data)
45    {
46        $url = $this->apiurl . '/' . $endpoint;
47        return $this->sendAPIRequest('POST', $url, $data);
48    }
49
50    /** @inheritdoc */
51    protected function parseAPIResponse($response)
52    {
53        if (isset($response['usage'])) {
54            if(isset($response['usage']['prompt_tokens'])) {
55                $this->inputTokensUsed += $response['usage']['prompt_tokens'];
56            } elseif ($response['usage']['total_tokens']) {
57                // on embedding models, prompt_tokens is not available
58                $this->inputTokensUsed += $response['usage']['total_tokens'];
59            }
60            $this->outputTokensUsed += $response['usage']['completion_tokens'] ?? 0;
61        }
62
63        if (isset($response['error'])) {
64            throw new ModelException('API error: ' . $response['error']['message'], 3002);
65        }
66
67        return $response;
68    }
69
70    /** @inheritdoc */
71    public function getAnswer(array $messages): string
72    {
73        $data = [
74            'messages' => $messages,
75            'model' => $this->getModelName(),
76            'max_completion_tokens' => null,
77            'stream' => false,
78            'n' => 1, // number of completions
79            $data['temperature'] = 0.0
80        ];
81
82        $response = $this->request('chat/completions', $data);
83        return $response['choices'][0]['message']['content'];
84    }
85
86    /** @inheritdoc */
87    public function getEmbedding($text): array
88    {
89        $data = [
90            'model' => $this->getModelName(),
91            'input' => [$text],
92        ];
93        $response = $this->request('embeddings', $data);
94
95        return $response['data'][0]['embedding'];
96    }
97
98    /**
99     * @internal for checking available models
100     */
101    public function listUpstreamModels()
102    {
103        $url = $this->apiurl . '/models';
104        return $this->http->get($url);
105    }
106}
107