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