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