Langertha-Skeid
view release on metacpan or search on metacpan
lib/Langertha/Skeid/Proxy.pm view on Meta::CPAN
};
if ($body->{stream}) {
_proxy_openai_stream($c, $url, $body, $node_id, $started, $meta);
return;
}
$c->render_later;
_proxy_openai_json_async($c, $url, $body, $node_id, $started, $meta, sub {
my ($res, $err, $status) = @_;
return if $err;
_render_upstream_response($c, $res, $node_id);
});
});
}
sub _handle_openai_embeddings {
my ($c) = @_;
my $body = $c->req->json;
unless (ref($body) eq 'HASH') {
$c->render(json => { error => { message => 'Invalid JSON body', type => 'invalid_request_error' } }, status => 400);
return;
}
my $model = $body->{model} // '';
my $api_key_id = _request_api_key_id($c);
_begin_route_async($c, $model, sub {
my ($route, $node_id, $started) = @_;
return unless $route;
my $url = _endpoint_url_for_node($route->{url}, '/embeddings');
my $meta = {
api_format => 'openai',
endpoint => '/v1/embeddings',
api_key_id => $api_key_id,
provider => 'skeid',
engine => ($route->{engine} // 'openaibase'),
model => $model,
route_url => ($route->{url} // ''),
};
$c->render_later;
_proxy_openai_json_async($c, $url, $body, $node_id, $started, $meta, sub {
my ($res, $err, $status) = @_;
return if $err;
_render_upstream_response($c, $res, $node_id);
});
});
}
sub _handle_anthropic_messages {
my ($c) = @_;
my $body = $c->req->json;
unless (ref($body) eq 'HASH') {
$c->render(json => { error => { message => 'Invalid JSON body', type => 'invalid_request_error' } }, status => 400);
return;
}
if ($body->{stream}) {
$c->render(json => {
error => {
message => 'Anthropic streaming is not implemented in Skeid proxy yet',
type => 'not_supported_error',
}
}, status => 501);
return;
}
my $openai_body = _anthropic_request_to_openai($body);
my $model = $openai_body->{model} // '';
my $api_key_id = _request_api_key_id($c);
_begin_route_async($c, $model, sub {
my ($route, $node_id, $started) = @_;
return unless $route;
my $url = _endpoint_url_for_node($route->{url}, '/chat/completions');
my $meta = {
api_format => 'anthropic',
endpoint => '/v1/messages',
api_key_id => $api_key_id,
provider => 'skeid',
engine => ($route->{engine} // 'openaibase'),
model => $model,
route_url => ($route->{url} // ''),
};
$c->render_later;
_proxy_openai_json_async($c, $url, $openai_body, $node_id, $started, $meta, sub {
my ($res, $err, $status) = @_;
return if $err;
my $payload = _openai_response_to_anthropic($res, $model);
$c->res->code($status || 200);
$c->res->headers->header('x-skeid-node' => $node_id);
$c->render(json => $payload);
});
});
}
sub _handle_ollama_chat {
my ($c) = @_;
my $body = $c->req->json;
unless (ref($body) eq 'HASH') {
$c->render(json => { error => 'Invalid JSON body' }, status => 400);
return;
}
if ($body->{stream}) {
$c->render(json => { error => 'Ollama streaming is not implemented in Skeid proxy yet' }, status => 501);
return;
}
my $openai_body = {
model => ($body->{model} // ''),
messages => ($body->{messages} || []),
(defined($body->{options}{temperature}) ? (temperature => 0 + $body->{options}{temperature}) : ()),
(defined($body->{options}{num_predict}) ? (max_tokens => 0 + $body->{options}{num_predict}) : ()),
(defined($body->{tools}) ? (tools => $body->{tools}) : ()),
(defined($body->{tool_choice}) ? (tool_choice => $body->{tool_choice}) : ()),
};
_begin_route_async($c, $openai_body->{model}, sub {
my ($route, $node_id, $started) = @_;
return unless $route;
my $url = _endpoint_url_for_node($route->{url}, '/chat/completions');
my $meta = {
api_format => 'ollama',
endpoint => '/api/chat',
api_key_id => _request_api_key_id($c),
provider => 'skeid',
engine => ($route->{engine} // 'openaibase'),
model => ($openai_body->{model} // ''),
route_url => ($route->{url} // ''),
};
$c->render_later;
_proxy_openai_json_async($c, $url, $openai_body, $node_id, $started, $meta, sub {
my ($res, $err, $status) = @_;
return if $err;
my $payload = _openai_response_to_ollama_chat($res);
$c->res->code($status || 200);
$c->res->headers->header('x-skeid-node' => $node_id);
$c->render(json => $payload);
});
});
}
sub _begin_route {
my ($c, $model) = @_;
my $wait_timeout_ms = 0 + ($c->skeid->route_wait_timeout_ms // 0);
my $wait_poll_ms = 0 + ($c->skeid->route_wait_poll_ms // 25);
$wait_poll_ms = 1 if $wait_poll_ms < 1;
$wait_timeout_ms = 0 if $wait_timeout_ms < 0;
my $started = time;
my $deadline = $started + ($wait_timeout_ms / 1000);
my $last_state;
my $last_node_id = '';
while (1) {
my $state = $c->skeid->call_function('route.state', { model => ($model // '') });
$last_state = $state if ref($state) eq 'HASH';
# Wrong model / no healthy nodes for this model: fail fast.
if (!$last_state || !$last_state->{has_eligible}) {
$c->render(json => {
error => {
message => "No healthy node available for model '$model'",
( run in 2.072 seconds using v1.01-cache-2.11-cpan-600a1bdf6e4 )