- Fix model_pricing table references (model_id -> model, display_name -> model)
- Fix price columns (output_price_per_1k -> output_price_per_million)
- Add price conversion (per_million / 1000 = per_1k) in all API responses
- Add whereNotNull('model') filters to exclude invalid entries
- Add getModelDisplayName() helper method to all controllers
- Fix AccountController to use gateway_users budget fields directly
- Remove Budget model dependencies from AccountController
- Add custom Scramble server URL configuration for API docs
- Create ScrambleServiceProvider to set correct /api prefix
- Add migration to rename user_id to gateway_user_id in llm_requests
- Add custom ApiGuard for gateway_users authentication
- Update all API controllers: AccountController, ModelController, PricingController, ProviderController
All API endpoints now working correctly:
- GET /api/account
- GET /api/models
- GET /api/pricing
- GET /api/providers/{provider}
173 lines
5.7 KiB
PHP
173 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\LLM;
|
|
|
|
use App\Models\GatewayUser;
|
|
use App\Models\GatewayUserCredential;
|
|
use App\Exceptions\{ProviderException, InsufficientBudgetException};
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class GatewayService
|
|
{
|
|
public function __construct(
|
|
private CostCalculator $costCalculator,
|
|
private RequestLogger $requestLogger,
|
|
) {}
|
|
|
|
/**
|
|
* Process a chat completion request through the gateway
|
|
*
|
|
* @param GatewayUser $user Gateway user making the request
|
|
* @param string $provider Provider name (openai, anthropic, google, deepseek, mistral)
|
|
* @param string $model Model name
|
|
* @param array $messages Chat messages
|
|
* @param array $options Optional parameters
|
|
* @param string|null $ipAddress Client IP address
|
|
* @param string|null $userAgent Client user agent
|
|
* @return array Response with metadata
|
|
* @throws ProviderException
|
|
*/
|
|
public function chatCompletion(
|
|
GatewayUser $user,
|
|
string $provider,
|
|
string $model,
|
|
array $messages,
|
|
array $options = [],
|
|
?string $ipAddress = null,
|
|
?string $userAgent = null
|
|
): array {
|
|
$startTime = microtime(true);
|
|
|
|
// 1. Get user's API credentials for the provider
|
|
$credential = $this->getUserCredential($user, $provider);
|
|
|
|
// 2. Create provider instance
|
|
$providerInstance = ProviderFactory::create($provider, $credential->api_key);
|
|
|
|
// 3. Build request payload for logging
|
|
$requestPayload = [
|
|
'provider' => $provider,
|
|
'model' => $model,
|
|
'messages' => $messages,
|
|
'options' => $options,
|
|
];
|
|
|
|
try {
|
|
// 4. Make the API request to LLM provider
|
|
$response = $providerInstance->chatCompletion($messages, array_merge($options, ['model' => $model]));
|
|
|
|
// 5. Normalize response to standard format
|
|
$normalized = $providerInstance->normalizeResponse($response);
|
|
|
|
// 6. Calculate response time
|
|
$responseTimeMs = (int) round((microtime(true) - $startTime) * 1000);
|
|
|
|
// 7. Calculate costs based on token usage
|
|
$costs = $this->costCalculator->calculate(
|
|
$provider,
|
|
$normalized['model'],
|
|
$normalized['usage']['prompt_tokens'],
|
|
$normalized['usage']['completion_tokens']
|
|
);
|
|
|
|
// 8. Log successful request
|
|
$requestId = $this->requestLogger->logSuccess(
|
|
$user->user_id, // Gateway user ID
|
|
$provider,
|
|
$normalized['model'],
|
|
$requestPayload,
|
|
$normalized,
|
|
$costs,
|
|
$responseTimeMs,
|
|
$ipAddress,
|
|
$userAgent
|
|
);
|
|
|
|
// 9. Update user's spending budget
|
|
$this->updateUserBudget($user, $costs['total_cost']);
|
|
|
|
// 10. Return standardized response with metadata
|
|
return [
|
|
'success' => true,
|
|
'request_id' => $requestId,
|
|
'provider' => $provider,
|
|
'model' => $normalized['model'],
|
|
'content' => $normalized['content'],
|
|
'role' => $normalized['role'],
|
|
'finish_reason' => $normalized['finish_reason'],
|
|
'usage' => $normalized['usage'],
|
|
'cost' => $costs,
|
|
'response_time_ms' => $responseTimeMs,
|
|
];
|
|
|
|
} catch (ProviderException $e) {
|
|
// Log failed request
|
|
$this->requestLogger->logFailure(
|
|
$user->user_id,
|
|
$provider,
|
|
$model,
|
|
$requestPayload,
|
|
$e->getMessage(),
|
|
$e->getCode(),
|
|
$ipAddress,
|
|
$userAgent
|
|
);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user's credential for a specific provider
|
|
*
|
|
* @param GatewayUser $user
|
|
* @param string $provider
|
|
* @return GatewayUserCredential
|
|
* @throws ProviderException
|
|
*/
|
|
private function getUserCredential(GatewayUser $user, string $provider): GatewayUserCredential
|
|
{
|
|
$credential = GatewayUserCredential::where('gateway_user_id', $user->user_id)
|
|
->where('provider', $provider)
|
|
->where('is_active', true)
|
|
->first();
|
|
|
|
if (!$credential) {
|
|
throw new ProviderException(
|
|
"No active API credentials found for provider: {$provider}",
|
|
400
|
|
);
|
|
}
|
|
|
|
// Update last used timestamp
|
|
$credential->update(['last_used_at' => now()]);
|
|
|
|
return $credential;
|
|
}
|
|
|
|
/**
|
|
* Update user's budget with spending
|
|
* Budget is now stored directly in gateway_users table
|
|
*
|
|
* @param GatewayUser $user
|
|
* @param float $cost Cost to add to spending
|
|
* @return void
|
|
*/
|
|
private function updateUserBudget(GatewayUser $user, float $cost): void
|
|
{
|
|
// Increment spending using model method
|
|
$user->incrementSpending($cost);
|
|
|
|
// Check if user should receive budget alert
|
|
if ($user->shouldSendBudgetAlert()) {
|
|
// TODO: Dispatch budget alert notification
|
|
Log::info("Budget alert: Gateway user {$user->user_id} has reached {$user->getBudgetUsagePercentage()}% of budget");
|
|
}
|
|
|
|
// Check if budget is now exceeded
|
|
if ($user->hasExceededBudget()) {
|
|
Log::warning("Budget exceeded: Gateway user {$user->user_id} has exceeded monthly budget");
|
|
}
|
|
}
|
|
}
|