_sBaseUri = rtrim(!empty($aParams['baseUri']) ? $aParams['baseUri'] : self::BASE_URI, '/'); $this->_iTimeout = (int)($aParams['timeout'] ?? 15); $this->_iRetries = (int)($aParams['retries'] ?? 2); } public function ask($mixedState, array $aQuestions): array { if (!$aQuestions) throw new Exception('No questions'); foreach ($aQuestions as $sId => $aQuestion) $this->_validateQuestion($sId, $aQuestion); $aRequest = [ 'state' => $mixedState, 'model' => $this->_aModel['model'], 'questions' => $aQuestions, ]; $this->_aLastRequest = $aRequest; $this->_aLastAnswers = []; $aHeaders = [ 'Authorization: Bearer ' . trim($this->_aModel['key']), 'Accept: application/json', ]; $sUrl = $this->_sBaseUri . self::ENDPOINT; $iAttempt = 0; while (true) { $sHttpCode = 0; $sResponse = bx_file_get_contents($sUrl, $aRequest, 'post-json', $aHeaders, $sHttpCode, [], $this->_iTimeout); $iHttpCode = (int)$sHttpCode; if ($iHttpCode == 200 && $sResponse !== '' && $sResponse !== false) break; $bRetry = $iAttempt < $this->_iRetries && ($iHttpCode == 0 || $iHttpCode == 429 || $iHttpCode >= 500); if (!$bRetry) throw new Exception("TypeSafe API error: HTTP {$iHttpCode} " . $this->_errorFromResponse($sResponse)); $iAttempt++; usleep(500000 * $iAttempt); // 0.5s, 1s, ... } $aResponse = json_decode($sResponse, true); if (!is_array($aResponse) || !isset($aResponse['answers']) || !is_array($aResponse['answers'])) throw new Exception('TypeSafe API error: unexpected response ' . mb_substr((string)$sResponse, 0, 300)); $this->_aLastUsage = isset($aResponse['usage']) && is_array($aResponse['usage']) ? $aResponse['usage'] : []; $this->_sLastModel = (string)($aResponse['model'] ?? ''); $this->_aLastAnswers = $aResponse['answers']; return $aResponse['answers']; } protected function _validateQuestion($sId, $aQuestion) { if (!is_array($aQuestion) || empty($aQuestion['type']) || !isset($aQuestion['instructions'])) throw new Exception("Question '{$sId}' is malformed, use BxDolAiJudge::noul/choice/score"); switch ($aQuestion['type']) { case self::Q_NOUL: break; case self::Q_CHOICE: $iOptions = isset($aQuestion['criteria']) && is_array($aQuestion['criteria']) ? count($aQuestion['criteria']) : 0; if ($iOptions < 2 || $iOptions > 255) throw new Exception("Question '{$sId}': choice needs 2..255 options"); break; case self::Q_SCORE: $iLevels = isset($aQuestion['criteria']) && is_array($aQuestion['criteria']) ? count($aQuestion['criteria']) : 0; if ($iLevels < 2 || $iLevels > 10) throw new Exception("Question '{$sId}': score needs 2..10 levels"); break; default: throw new Exception("Question '{$sId}': unknown type {$aQuestion['type']}"); } } protected function _errorFromResponse($sResponse): string { if (!$sResponse) return '(empty response)'; $a = json_decode($sResponse, true); if (is_array($a)) { if (!empty($a['error'])) return is_array($a['error']) ? json_encode($a['error']) : (string)$a['error']; if (!empty($a['detail'])) return is_array($a['detail']) ? json_encode($a['detail']) : (string)$a['detail']; if (!empty($a['message'])) return (string)$a['message']; } return mb_substr((string)$sResponse, 0, 300); } } /** @} */