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
17{
18
19    /** @inheritdoc */
20    protected function getHttpClient()
21    {
22        $http = parent::getHttpClient();
23
24        $apiKey = $this->getFromConf('apikey', ''); //auth is optional
25        if ($apiKey) {
26            $http->headers['Authorization'] = 'Bearer ' . $apiKey;
27        }
28        return $http;
29    }
30
31    /**
32     * Send a request to the OpenAI API
33     *
34     * @param string $endpoint
35     * @param array $data Payload to send
36     * @return array API response
37     * @throws \Exception
38     */
39    protected function request($endpoint, $data)
40    {
41        $url = $this->apiurl . '/' . $endpoint;
42        return $this->sendAPIRequest('POST', $url, $data);
43    }
44
45    /** @inheritdoc */
46    protected function parseAPIResponse($response)
47    {
48        if (isset($response['usage'])) {
49            if (isset($response['usage']['prompt_tokens'])) {
50                $this->inputTokensUsed += $response['usage']['prompt_tokens'];
51            } elseif ($response['usage']['total_tokens']) {
52                // on embedding models, prompt_tokens is not available
53                $this->inputTokensUsed += $response['usage']['total_tokens'];
54            }
55            $this->outputTokensUsed += $response['usage']['completion_tokens'] ?? 0;
56        }
57
58        if (isset($response['error'])) {
59            throw new ModelException('API error: ' . $response['error']['message'], 3002);
60        }
61
62        return $response;
63    }
64
65    /** @inheritdoc */
66    public function getAnswer(array $messages): string
67    {
68        $data = [
69            'messages' => $messages,
70            'model' => $this->getModelName(),
71            'max_completion_tokens' => null,
72            'stream' => false,
73            'n' => 1, // number of completions
74            'temperature' => 0.0
75        ];
76
77        $response = $this->request('chat/completions', $data);
78        return $response['choices'][0]['message']['content'];
79    }
80
81    /** @inheritdoc */
82    public function getEmbedding($text): array
83    {
84        $data = [
85            'model' => $this->getModelName(),
86            'input' => [$text],
87        ];
88        $response = $this->request('embeddings', $data);
89
90        return $response['data'][0]['embedding'];
91    }
92
93    /**
94     * @internal for checking available models
95     */
96    public function listUpstreamModels()
97    {
98        $http = $this->getHttpClient();
99        $url = $this->apiurl . '/models';
100        return $http->get($url);
101    }
102}
103