xref: /plugin/aichat/Model/Generic/AbstractGenericModel.php (revision 7775eee7bc2214cbd8bc6b5c95ff60d44e95c8c2)
12e22aefbSAndreas Gohr<?php
22e22aefbSAndreas Gohr
32e22aefbSAndreas Gohrnamespace dokuwiki\plugin\aichat\Model\Generic;
42e22aefbSAndreas Gohr
52e22aefbSAndreas Gohruse dokuwiki\plugin\aichat\Model\AbstractModel;
62e22aefbSAndreas Gohruse dokuwiki\plugin\aichat\Model\ChatInterface;
72e22aefbSAndreas Gohruse dokuwiki\plugin\aichat\Model\EmbeddingInterface;
82e22aefbSAndreas Gohruse dokuwiki\plugin\aichat\Model\ModelException;
92e22aefbSAndreas Gohr
102e22aefbSAndreas Gohr/**
112e22aefbSAndreas Gohr * Abstract OpenAI-compatible Model
122e22aefbSAndreas Gohr *
132e22aefbSAndreas Gohr * This class provides a basic interface to the OpenAI API as implemented by many other providers.
142e22aefbSAndreas Gohr * It implements chat and embedding interfaces.
152e22aefbSAndreas Gohr */
16*7775eee7SAndreas Gohrabstract class AbstractGenericModel extends AbstractModel
172e22aefbSAndreas Gohr{
182e22aefbSAndreas Gohr
197c3b69cbSAndreas Gohr    /** @inheritdoc */
207c3b69cbSAndreas Gohr    protected function getHttpClient()
217c3b69cbSAndreas Gohr    {
227c3b69cbSAndreas Gohr        $http = parent::getHttpClient();
237c3b69cbSAndreas Gohr
247bd2bac6SAndreas Gohr        $apiKey = $this->getFromConf('apikey', ''); //auth is optional
257bd2bac6SAndreas Gohr        if ($apiKey) {
267c3b69cbSAndreas Gohr            $http->headers['Authorization'] = 'Bearer ' . $apiKey;
277bd2bac6SAndreas Gohr        }
287c3b69cbSAndreas Gohr        return $http;
297c3b69cbSAndreas Gohr    }
307c3b69cbSAndreas Gohr
312e22aefbSAndreas Gohr    /**
322e22aefbSAndreas Gohr     * Send a request to the OpenAI API
332e22aefbSAndreas Gohr     *
342e22aefbSAndreas Gohr     * @param string $endpoint
352e22aefbSAndreas Gohr     * @param array $data Payload to send
362e22aefbSAndreas Gohr     * @return array API response
372e22aefbSAndreas Gohr     * @throws \Exception
382e22aefbSAndreas Gohr     */
392e22aefbSAndreas Gohr    protected function request($endpoint, $data)
402e22aefbSAndreas Gohr    {
412e22aefbSAndreas Gohr        $url = $this->apiurl . '/' . $endpoint;
422e22aefbSAndreas Gohr        return $this->sendAPIRequest('POST', $url, $data);
432e22aefbSAndreas Gohr    }
442e22aefbSAndreas Gohr
452e22aefbSAndreas Gohr    /** @inheritdoc */
462e22aefbSAndreas Gohr    protected function parseAPIResponse($response)
472e22aefbSAndreas Gohr    {
482e22aefbSAndreas Gohr        if (isset($response['usage'])) {
492e22aefbSAndreas Gohr            if (isset($response['usage']['prompt_tokens'])) {
502e22aefbSAndreas Gohr                $this->inputTokensUsed += $response['usage']['prompt_tokens'];
512e22aefbSAndreas Gohr            } elseif ($response['usage']['total_tokens']) {
522e22aefbSAndreas Gohr                // on embedding models, prompt_tokens is not available
532e22aefbSAndreas Gohr                $this->inputTokensUsed += $response['usage']['total_tokens'];
542e22aefbSAndreas Gohr            }
552e22aefbSAndreas Gohr            $this->outputTokensUsed += $response['usage']['completion_tokens'] ?? 0;
562e22aefbSAndreas Gohr        }
572e22aefbSAndreas Gohr
582e22aefbSAndreas Gohr        if (isset($response['error'])) {
592e22aefbSAndreas Gohr            throw new ModelException('API error: ' . $response['error']['message'], 3002);
602e22aefbSAndreas Gohr        }
612e22aefbSAndreas Gohr
622e22aefbSAndreas Gohr        return $response;
632e22aefbSAndreas Gohr    }
642e22aefbSAndreas Gohr
652e22aefbSAndreas Gohr    /** @inheritdoc */
662e22aefbSAndreas Gohr    public function getAnswer(array $messages): string
672e22aefbSAndreas Gohr    {
682e22aefbSAndreas Gohr        $data = [
692e22aefbSAndreas Gohr            'messages' => $messages,
702e22aefbSAndreas Gohr            'model' => $this->getModelName(),
712e22aefbSAndreas Gohr            'max_completion_tokens' => null,
722e22aefbSAndreas Gohr            'stream' => false,
732e22aefbSAndreas Gohr            'n' => 1, // number of completions
74ac84f472SAndreas Gohr            'temperature' => 0.0
752e22aefbSAndreas Gohr        ];
762e22aefbSAndreas Gohr
772e22aefbSAndreas Gohr        $response = $this->request('chat/completions', $data);
782e22aefbSAndreas Gohr        return $response['choices'][0]['message']['content'];
792e22aefbSAndreas Gohr    }
802e22aefbSAndreas Gohr
812e22aefbSAndreas Gohr    /** @inheritdoc */
822e22aefbSAndreas Gohr    public function getEmbedding($text): array
832e22aefbSAndreas Gohr    {
842e22aefbSAndreas Gohr        $data = [
852e22aefbSAndreas Gohr            'model' => $this->getModelName(),
862e22aefbSAndreas Gohr            'input' => [$text],
872e22aefbSAndreas Gohr        ];
882e22aefbSAndreas Gohr        $response = $this->request('embeddings', $data);
892e22aefbSAndreas Gohr
902e22aefbSAndreas Gohr        return $response['data'][0]['embedding'];
912e22aefbSAndreas Gohr    }
922e22aefbSAndreas Gohr
932e22aefbSAndreas Gohr    /**
942e22aefbSAndreas Gohr     * @internal for checking available models
952e22aefbSAndreas Gohr     */
962e22aefbSAndreas Gohr    public function listUpstreamModels()
972e22aefbSAndreas Gohr    {
987c3b69cbSAndreas Gohr        $http = $this->getHttpClient();
992e22aefbSAndreas Gohr        $url = $this->apiurl . '/models';
1007c3b69cbSAndreas Gohr        return $http->get($url);
1012e22aefbSAndreas Gohr    }
1022e22aefbSAndreas Gohr}
103