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 */ 162e22aefbSAndreas Gohrabstract class AbstractGenericModel extends AbstractModel implements ChatInterface, EmbeddingInterface 172e22aefbSAndreas Gohr{ 182e22aefbSAndreas Gohr /** @var string The API base URL */ 192e22aefbSAndreas Gohr protected $apiurl = ''; 202e22aefbSAndreas Gohr 212e22aefbSAndreas Gohr 222e22aefbSAndreas Gohr /** @inheritdoc */ 232e22aefbSAndreas Gohr public function __construct(string $name, array $config) 242e22aefbSAndreas Gohr { 252e22aefbSAndreas Gohr parent::__construct($name, $config); 262e22aefbSAndreas Gohr 272e22aefbSAndreas Gohr if($this->apiurl === '') { 287c3b69cbSAndreas Gohr $this->apiurl = $this->getFromConf('apiurl'); 292e22aefbSAndreas Gohr } 302e22aefbSAndreas Gohr $this->apiurl = rtrim($this->apiurl, '/'); 312e22aefbSAndreas Gohr } 322e22aefbSAndreas Gohr 337c3b69cbSAndreas Gohr /** @inheritdoc */ 347c3b69cbSAndreas Gohr protected function getHttpClient() 357c3b69cbSAndreas Gohr { 367c3b69cbSAndreas Gohr $http = parent::getHttpClient(); 377c3b69cbSAndreas Gohr 387c3b69cbSAndreas Gohr $apiKey = $this->getFromConf('apikey'); 397c3b69cbSAndreas Gohr $http->headers['Authorization'] = 'Bearer ' . $apiKey; 407c3b69cbSAndreas Gohr return $http; 417c3b69cbSAndreas Gohr } 427c3b69cbSAndreas Gohr 432e22aefbSAndreas Gohr /** 442e22aefbSAndreas Gohr * Send a request to the OpenAI API 452e22aefbSAndreas Gohr * 462e22aefbSAndreas Gohr * @param string $endpoint 472e22aefbSAndreas Gohr * @param array $data Payload to send 482e22aefbSAndreas Gohr * @return array API response 492e22aefbSAndreas Gohr * @throws \Exception 502e22aefbSAndreas Gohr */ 512e22aefbSAndreas Gohr protected function request($endpoint, $data) 522e22aefbSAndreas Gohr { 532e22aefbSAndreas Gohr $url = $this->apiurl . '/' . $endpoint; 542e22aefbSAndreas Gohr return $this->sendAPIRequest('POST', $url, $data); 552e22aefbSAndreas Gohr } 562e22aefbSAndreas Gohr 572e22aefbSAndreas Gohr /** @inheritdoc */ 582e22aefbSAndreas Gohr protected function parseAPIResponse($response) 592e22aefbSAndreas Gohr { 602e22aefbSAndreas Gohr if (isset($response['usage'])) { 612e22aefbSAndreas Gohr if(isset($response['usage']['prompt_tokens'])) { 622e22aefbSAndreas Gohr $this->inputTokensUsed += $response['usage']['prompt_tokens']; 632e22aefbSAndreas Gohr } elseif ($response['usage']['total_tokens']) { 642e22aefbSAndreas Gohr // on embedding models, prompt_tokens is not available 652e22aefbSAndreas Gohr $this->inputTokensUsed += $response['usage']['total_tokens']; 662e22aefbSAndreas Gohr } 672e22aefbSAndreas Gohr $this->outputTokensUsed += $response['usage']['completion_tokens'] ?? 0; 682e22aefbSAndreas Gohr } 692e22aefbSAndreas Gohr 702e22aefbSAndreas Gohr if (isset($response['error'])) { 712e22aefbSAndreas Gohr throw new ModelException('API error: ' . $response['error']['message'], 3002); 722e22aefbSAndreas Gohr } 732e22aefbSAndreas Gohr 742e22aefbSAndreas Gohr return $response; 752e22aefbSAndreas Gohr } 762e22aefbSAndreas Gohr 772e22aefbSAndreas Gohr /** @inheritdoc */ 782e22aefbSAndreas Gohr public function getAnswer(array $messages): string 792e22aefbSAndreas Gohr { 802e22aefbSAndreas Gohr $data = [ 812e22aefbSAndreas Gohr 'messages' => $messages, 822e22aefbSAndreas Gohr 'model' => $this->getModelName(), 832e22aefbSAndreas Gohr 'max_completion_tokens' => null, 842e22aefbSAndreas Gohr 'stream' => false, 852e22aefbSAndreas Gohr 'n' => 1, // number of completions 86*ac84f472SAndreas Gohr 'temperature' => 0.0 872e22aefbSAndreas Gohr ]; 882e22aefbSAndreas Gohr 892e22aefbSAndreas Gohr $response = $this->request('chat/completions', $data); 902e22aefbSAndreas Gohr return $response['choices'][0]['message']['content']; 912e22aefbSAndreas Gohr } 922e22aefbSAndreas Gohr 932e22aefbSAndreas Gohr /** @inheritdoc */ 942e22aefbSAndreas Gohr public function getEmbedding($text): array 952e22aefbSAndreas Gohr { 962e22aefbSAndreas Gohr $data = [ 972e22aefbSAndreas Gohr 'model' => $this->getModelName(), 982e22aefbSAndreas Gohr 'input' => [$text], 992e22aefbSAndreas Gohr ]; 1002e22aefbSAndreas Gohr $response = $this->request('embeddings', $data); 1012e22aefbSAndreas Gohr 1022e22aefbSAndreas Gohr return $response['data'][0]['embedding']; 1032e22aefbSAndreas Gohr } 1042e22aefbSAndreas Gohr 1052e22aefbSAndreas Gohr /** 1062e22aefbSAndreas Gohr * @internal for checking available models 1072e22aefbSAndreas Gohr */ 1082e22aefbSAndreas Gohr public function listUpstreamModels() 1092e22aefbSAndreas Gohr { 1107c3b69cbSAndreas Gohr $http = $this->getHttpClient(); 1112e22aefbSAndreas Gohr $url = $this->apiurl . '/models'; 1127c3b69cbSAndreas Gohr return $http->get($url); 1132e22aefbSAndreas Gohr } 1142e22aefbSAndreas Gohr} 115