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