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