Przejdź do głównej zawartości

LLM API

Jako dodatkową usługę oferujemy możliwość korzystania z dużych modeli językowych. Są one dostępne przez API częściowo kompatybilne z OpenAI API oraz Anthropic Messages API.

Bazowy adres URL to:

https://llm.comtegra.cloud/v1

Umożliwiamy korzystanie z następujących zasobów:

Uwierzytelnienie odbywa się za pomocą klucza. Aby rozpocząć korzystanie z API, wystarczy utworzyć klucz za pomocą klienta CGC.

Aktualną listę modelów otrzymasz, wywołując:

/v1/models

Typy modeli​

Każdy model ma swój typ (type), który określa, z jakimi zasobami można go używać:

typeZasoby
chat/v1/chat/completions, /v1/completions, /v1/messages, /v1/responses
embedding/v1/embeddings
transcription/v1/audio/transcriptions
conversion/v1/files/convert
decision/v1/systemone (format System One API), /v1/decisions (format OpenAI Decisions)

Typ każdego modelu jest podany w odpowiedzi /v1/models:

{"id": "gte-qwen2-7b", "object": "model", "created": null, "owned_by": null, "type": "embedding", "device": "a5000", "model_repo": null, "max_model_len": 8192}

Zapytanie, które używa modelu w zasobie innego typu (np. chat completions z modelem do embeddingów), jest odrzucane z kodem HTTP 404 i "code": "model_not_supported". Nieistniejący model lub zapytanie bez pola model kończy się kodem HTTP 404 i "code": "model_not_found". Odrzucone zapytania nie są rozliczane.

{"error": {"message": "The model 'gte-qwen2-7b' has type 'embedding' and is not supported on /v1/chat/completions.", "type": "invalid_request_error", "param": "model", "code": "model_not_supported"}}

W zasobie /v1/messages oba błędy mają format błędów Anthropic:

{"type": "error", "error": {"type": "not_found_error", "message": "The model 'gte-qwen2-7b' has type 'embedding' and is not supported on /v1/messages."}}

Przykłady użycia​

Chat​

from openai import OpenAI

client = OpenAI(base_url="https://llm.comtegra.cloud/v1",
api_key="YOUR-API-SECRET")

res = client.chat.completions.create(model="glm-53-nvfp4", max_completion_tokens=100,
messages=[{"role": "user", "content": "Hi"}])

print(res)

Embeddings​

from openai import OpenAI

client = OpenAI(base_url="https://llm.comtegra.cloud/v1",
api_key="YOUR-API-SECRET")

res = client.embeddings.create(model="gte-qwen2-7b", input="Mary had a little lamb")

print(res)

Transcriptions​

from openai import OpenAI

client = OpenAI(base_url="https://llm.comtegra.cloud/v1",
api_key="YOUR-API-SECRET")

with open("recording.mp3", "rb") as f:
res = client.audio.transcriptions.create(model="whisper-1", file=f)

print(res)
notatka

Proxy zawsze zwraca format verbose_json, który zawiera długość nagrania wykorzystywaną do rozliczeń. Formaty text, srt oraz vtt nie są obsługiwane.

Konwersja plików​

Konwertuje PDF do Markdown. To rozszerzenie Comtegra (nie należy do API OpenAI ani Anthropic). Użyj identyfikatora modelu pdf-to-markdown. Dostępne są dwa tryby:

  • Synchroniczny (domyślny): odpowiedź wraca po zakończeniu konwersji.
  • W tle (background=true): API od razu zwraca identyfikator konwersji, a wynik odbierasz, gdy jest gotowy. Używaj go dla skanów i długich dokumentów (OCR jednej zeskanowanej strony trwa kilkadziesiąt sekund) albo gdy agent ma w tym czasie pracować dalej.

Tryb synchroniczny​

import requests

with open("document.pdf", "rb") as f:
res = requests.post(
"https://llm.comtegra.cloud/v1/files/convert",
headers={"Authorization": "Bearer YOUR-API-SECRET"},
files={"file": ("document.pdf", f, "application/pdf")},
data={"model": "pdf-to-markdown"},
)

print(res.json())
notatka

Udane wywołanie zwraca JSON z "success": true, Markdown w polu "output", "page_count" używane do rozliczeń oraz wyodrębnione obrazy w polu "images" (base64, klucze to nazwy plików). Nieudane konwersje kończą się błędem i nie są rozliczane. Plik większy niż 50 MB jest odrzucany z kodem 413.

Tryb w tle​

Wyślij to samo zapytanie z background=true, a potem odpytuj konwersję, dopóki jej status jest in_progress.

import time

import requests

API = "https://llm.comtegra.cloud/v1/files/convert"
HEADERS = {"Authorization": "Bearer YOUR-API-SECRET"}

with open("document.pdf", "rb") as f:
res = requests.post(
API,
headers=HEADERS,
files={"file": ("document.pdf", f, "application/pdf")},
data={"model": "pdf-to-markdown", "background": "true"},
)
res.raise_for_status()
conversion = res.json() # {"id": "fc_...", "status": "queued", ...}

while True:
res = requests.get(f"{API}/{conversion['id']}", headers=HEADERS)
res.raise_for_status()
conversion = res.json()
if conversion["status"] != "in_progress":
break
time.sleep(int(res.headers.get("Retry-After", 5)))

if conversion["status"] == "completed":
print(conversion["output"])
else:
print(conversion["error"])
ZapytanieOdpowiedź
POST z background=true202 Accepted z {"id", "object": "file.conversion", "model", "status": "queued"} oraz nagłówkami Location: /v1/files/convert/{id} i Retry-After: 5.
GET /v1/files/convert/{id} w trakcie konwersji200 ze "status": "in_progress" i nagłówkiem Retry-After.
GET po zakończeniu200 ze "status": "completed" i tymi samymi polami co odpowiedź synchroniczna: output, page_count, images, output_format.
GET po nieudanej konwersji200 ze "status": "failed" i "error": {"message", "code"}.
GET z nieznanym lub wygasłym identyfikatorem albo z innym kluczem API404 z kodem conversion_not_found.
notatka
  • Identyfikator konwersji działa tylko z kluczem API, który ją zlecił.
  • Strony są rozliczane raz, przy pierwszym odebraniu wyniku; kolejne odebrania tego samego identyfikatora są bezpłatne. Nieudane konwersje nie są rozliczane.
  • Wyniki są przechowywane przez 7 dni.
  • Jeden klucz API może mieć naraz najwyżej 4 trwające odpytania; kolejne dostają 429 z nagłówkiem Retry-After. Odpytania nie wliczają się do limitów zapytań modelu.
  • Nagłówki Location i Retry-After są wymienione w Access-Control-Expose-Headers.

Messages​

Zasób Anthropic Messages API umożliwia korzystanie z naszych modeli narzędziom agentowym, takim jak Claude Code oraz Anthropic SDK.

from anthropic import Anthropic

# Anthropic SDK samo dokleja "/v1/messages" do adresu bazowego, więc ustaw go na
# https://llm.comtegra.cloud (bez "/v1"). Uwierzytelniaj się przez auth_token,
# który wysyłany jest jako token bearer; argument api_key (x-api-key) nie jest
# obsługiwany.
client = Anthropic(base_url="https://llm.comtegra.cloud",
auth_token="YOUR-API-SECRET")

res = client.messages.create(model="glm-53-nvfp4", max_tokens=100,
messages=[{"role": "user", "content": "Hi"}])

print(res)
notatka

Anthropic SDK oraz narzędzia kompatybilne z Anthropic (takie jak Claude Code) budują adres zapytania jako <adres bazowy>/v1/messages, dlatego ustaw ich adres bazowy na https://llm.comtegra.cloud — bez /v1 używanego przez zasoby w stylu OpenAI — i uwierzytelniaj się tokenem bearer (parametr auth_token w SDK). Schemat x-api-key nie jest obsługiwany.

Responses​

Zasób OpenAI Responses API obsługuje narzędzia agentowe, takie jak Codex CLI.

from openai import OpenAI

client = OpenAI(base_url="https://llm.comtegra.cloud/v1",
api_key="YOUR-API-SECRET")

res = client.responses.create(model="glm-53-nvfp4", input="Hi")

print(res)
notatka

Ten zasób jest bezstanowy: w każdym zapytaniu wysyłaj pełną historię rozmowy w polu input. Stan rozmowy po stronie serwera (previous_response_id, conversation oraz zapytania background) nie jest obsługiwany.

Decyzje​

Zasoby decyzji serwują modele System One: zamiast generować tekst, model odpowiada na typowane pytania o dokument i podaje prawdopodobieństwa. Dokument przekazujesz raz, a potem zadajesz dowolną liczbę pytań na jego temat.

Obsługiwane są dwa formaty żądań:

  • /v1/systemone: format System One API (pole state i obiekt questions), do użycia zwykłym klientem HTTP;
  • /v1/decisions: obsługiwany podzbiór formatu OpenAI Decisions, więc oficjalny klient OpenAI działa od razu - patrz poniżej.

Żądanie /v1/systemone przyjmuje pole state oraz obiekt questions. Typy pytań:

typeCo odpowiada
choiceJedna opcja z podanych kryteriów, wraz z prawdopodobieństwami.
noulOdpowiedź tak/nie wraz z prawdopodobieństwem true.
scorePoziom na uporządkowanej skali (np. Niski/Średni/Wysoki).
multiZbiór pasujących etykiet z niezależnymi prawdopodobieństwami.
actDziałanie o najniższym oczekiwanym koszcie; defer, gdy ryzyko jest zbyt duże.
toolNajlepiej dopasowane narzędzie spośród definicji w stylu OpenAI.

noul, choice i score to podstawowe typy System One. multi, act i tool to rozszerzenia, których nie każdy model decyzyjny obsługuje. Typy obsługiwane przez dany model są podane w jego wpisie w /v1/models (decision_capabilities.question_types).

import requests

res = requests.post(
"https://llm.comtegra.cloud/v1/systemone",
headers={"Authorization": "Bearer YOUR-API-SECRET"},
json={
"model": "basal-20-max",
"state": "The customer received a broken product and requests a refund.",
"questions": {
"category": {
"type": "choice",
"instructions": "Classify the message.",
"option_keys": "hide",
"criteria": {
"complaint": "Complaint",
"question": "Question",
"other": "Other",
},
}
},
},
)

print(res.json())

Poprawna odpowiedź zachowuje natywny format: odpowiedź na każde pytanie oraz obiekt usage:

{
"model": "basal-20-max",
"answers": {
"category": {
"type": "choice",
"choice": "complaint",
"probabilities": {"complaint": 0.9506, "question": 0.0059, "other": 0.0435},
"confidence": 0.9506
}
},
"usage": {
"input_tokens": 106,
"output_tokens": 0,
"questions": 1,
"branches": 1,
"latency_ms": 107.71,
"longest_prompt_tokens": 106
}
}
notatka
  • Pole output_tokens ma zwykle wartość 0: decyzja jest odczytywana z prawdopodobieństwów modelu, a nie generowana token po tokenie.
  • Kontekst modelu (max_model_len, 32768 tokenów) obejmuje cały prompt: stan, pytanie i opcje razem. Stan, który się nie mieści, jest odrzucany z kodem HTTP 422.
  • Streaming i parametry czatu (messages, max_tokens, temperature) nie są obsługiwane na tym zasobie.

Użycie klienta OpenAI​

Zasób /v1/decisions mówi obsługującym podzbiorem formatu OpenAI Decisions, więc oficjalny klient OpenAI (openai>=3.26, z interfejsem client.decisions) działa od razu:

from openai import OpenAI

client = OpenAI(base_url="https://llm.comtegra.cloud/v1",
api_key="YOUR-API-SECRET")

result = client.decisions.create(
model="basal-20-max",
input="The customer received a damaged product.",
questions=[
{
"type": "predicate",
"name": "complaint",
"instructions": "Is this a complaint?",
}
],
)
print(result.answers[0].probability)
notatka
  • Zasób /v1/decisions przyjmuje pola model, input i questions. Pole input to tekst albo tablica wiadomości użytkownika, których treść jest tekstem lub częściami input_text; części są łączone znakami nowej linii, a wiadomości pustą linią. Obrazy, pliki audio i inne pliki, inne role oraz streaming są odrzucane z kodem HTTP 400.
  • Obsługiwane typy pytań to predicate, choice i score. Pytania choice i score przyjmują tablicę 2-255 unikalnych wartości choices/levels (wartość lub etykieta plus opcjonalny opis).
  • Odpowiedzi zachowują kolejność pytań: predicate zwraca prawdopodobieństwo, choice zwraca wybraną opcję wraz z pewnością i rozkładem prawdopodobieństw, a score zwraca oczekiwany indeks poziomu liczony od zera. Odpowiedź nigdy nie dodaje ani nie pomija opcji, których nie zażądano.
  • Obiekt usage używa układu OpenAI; liczniki szczegółów (cache, reasoning) są zerowe, a output_tokens ma zwykle wartość 0.
  • Żądanie nie mieszczące się w kontekście modelu jest odrzucane z "code": "context_length_exceeded".
  • Wpis modelu decyzyjnego w /v1/models wymienia obsługiwane zasoby i typy pytań w polu decision_capabilities.

Klucze API​

cgc api-keys create --level LLM

Wypisany zostanie klucz z etykietą API secret. Zapisz go w wygodnym i bezpiecznym miejscu. Nie ma możliwości podejrzeć go drugi raz, więc jeśli go stracisz, usuń go i stwórz nowy. Nie ujawniaj swojego klucza, gdyż może to powodować naliczenie opłat za użycie API przez osoby trzecie.

Do kluczy, przypisanych do Twojego konta można dodać komentarze. Ułatwia to ich identyfikację.

cgc api-keys create --level LLM --comment "Klucz Michała"

Wszystkie dostępne opcje można zobaczyć, wykonując polecenie cgc api-keys --help.

Billing​

Zapytania do API doliczane są do rachunku Twojej organizacji. Ich koszt zależy od liczby tokenów wejściowych i wyjściowych, modelu i GPU. Wyjątki: transkrypcja audio jest rozliczana za każdą sekundę przetworzonego nagrania; konwersja PDF → Markdown (/v1/files/convert) jest rozliczana za każdą stronę (page_count w odpowiedzi); konwersja w tle jest rozliczana raz, przy pierwszym odebraniu wyniku. Cennik dostępny jest na stronie cgc.comtegra.cloud/pricing.

Zużycie tokenów i ich koszt można zobaczyć za pomocą polecenia cgc billing status.

Przykład obliczenia kosztu zapytania:

Używasz modelu Meta-Llama 3.1-70B-Instruct-Q5_K_M, uruchomionego na GPU NVIDIA A100 GPU. Załóżmy, że tokeny wejściowe kosztują 19,78 zł / 1M tok. a tokeny wyjściowe -- 167,66 zł / 1M tok. Twoje zapytanie to: Write a haiku about ChatGPT. Jest ono długości 18 tokenów. Dostajesz następującą odpowiedź: Silicon whispers. ChatGPT's gentle responses. Knowledge at my door, której długość to 16 tokenów. To zapytanie będzie kosztować 19,78 zł * 18 / 1000000 + 167,66 zł * 16 / 1000000 = 0,003039 zł.

Oznaczanie treści generowanych przez AI​

Akt o sztucznej inteligencji (rozporządzenie (UE) 2024/1689, art. 50 ust. 2) zobowiązuje dostawców generatywnych systemów AI do oznaczania ich wyników jako sztucznie wygenerowanych w formacie nadającym się do odczytu maszynowego. API spełnia ten wymóg, oznaczając każdą odpowiedź generatywną w momencie jej wytworzenia.

Każda pomyślna odpowiedź zasobów /v1/chat/completions, /v1/completions, /v1/messages, /v1/responses, /v1/audio/transcriptions i /v1/files/convert zawiera nagłówek X-AI-Generated: true. Odpowiedzi niestrumieniowe zasobów /v1/chat/completions, /v1/completions, /v1/messages i /v1/responses zawierają dodatkowo obiekt provenance na najwyższym poziomie, obok standardowych pól odpowiedzi:

"provenance": {
"ai_generated": true,
"digital_source_type": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
"legal_basis": "Regulation (EU) 2024/1689, Article 50(2)",
"generator": "comtegra-llmproxy",
"generator_version": "1.4.0",
"model_id": "glm-53-nvfp4",
"request_id": "4df57814-a195-467f-9d8a-294ce7796283",
"timestamp": "2026-08-24T14:08:02Z"
}
PoleZnaczenie
ai_generatedZawsze true.
digital_source_typeTermin ze słownika IPTC Digital Source Type oznaczający treść w całości wygenerowaną przez wytrenowany model; tego samego słownika używa C2PA.
legal_basisPrzepis, który oznaczenie realizuje.
generator, generator_versionSystem, który dodał oznaczenie, i jego wersja.
model_idWartość pola model z Twojego zapytania.
request_idIdentyfikator zapytania, równy nagłówkowi odpowiedzi X-Request-ID. Podaj go, kontaktując się z pomocą techniczną w sprawie konkretnej odpowiedzi.
timestampCzas wygenerowania (UTC, RFC 3339).

W poniższych przypadkach oznaczenie ogranicza się do nagłówka:

  • Odpowiedzi strumieniowe ("stream": true). Strumień zdarzeń jest przekazywany bez zmian, więc klienty i SDK obsługujące strumieniowanie nie są dotknięte tą zmianą.
  • /v1/audio/transcriptions. Treść transkrypcji jest przekazywana bez zmian.
  • /v1/files/convert (tryb synchroniczny i w tle). Wynik konwersji jest przekazywany tak, jak zwraca go konwerter; proxy nie dodaje opisanego wyżej obiektu.

Odpowiedzi /v1/embeddings i /v1/models oraz odpowiedzi z błędem nie są oznaczane, ponieważ nie są treścią generowaną.

Oba nagłówki są wymienione w Access-Control-Expose-Headers, więc mogą je odczytać również klienty przeglądarkowe z innych domen.

notatka

Obiekt provenance jest rozszerzeniem standardowych schematów odpowiedzi OpenAI i Anthropic. Oficjalne SDK zachowują go jako dodatkowe pole — np. res.model_extra["provenance"] w SDK OpenAI dla Pythona. Jeśli Twój klient waliduje odpowiedzi według ścisłego schematu, dopuść to dodatkowe pole na najwyższym poziomie.

Własna instancja LLM API​

Jeśli używasz jednocześnie kilku modeli w swoim własnym namespace, możesz uruchomić własną instancję LLM API. Umożliwia ona kontrolę dostępu do modeli i gromadzenie informacji o użyciu tokenów przez każdego z użytkowników.

Działania administracyjne takie, jak zarządzanie użytkownikami i monitorowanie użycia wymagają dostępu SSH do własnego kontenera LLM API. Instrukcja konfiguracji dostępu SSH znajduje się tutaj.

Tworzenie własnej instancji​

Najpierw utwórz wolumen, który będzie służył do przechowywania pliku konfiguracyjnego i bazy danych.

cgc volume create -s 1 -sc <storage_class> llm-api-data

Nastepnie uruchom swoją instancję LLM API.

cgc compute create -n llm-api -c 1 -m 2 -v llm-api-data llm-api

Domyślnie wolumen zostanie zamontowany pod ścieżką /media/api. Kiedy aplikacja uruchomi się po raz pierwszy, utworzy ona plik konfiguracyjny w /media/api/config.toml i zainicjalizuje bazę danych w /media/api/db.sqlite. Domyślny plik konfiguracyjny zawiera komentarze, które tłumaczą dostepne opcje i przykładowe fragmenty konfiguracji.

warning

Każdy backend w pliku konfiguracyjnym musi mieć ustawione pole type na jedną z wartości chat, embedding, transcription lub conversion (zob. Typy modeli). Aplikacja nie uruchomi się, dopóki któryś backend nie ma poprawnego type, dlatego przed aktualizacją istniejącej instancji dopisz type do każdego backendu.

Do edycji pliku konfiguracyjnego polecamy użycie File Browser. Aby po edycji i zapisaniu pliku aplikacja przeładowała konfigurację można zrestartować ją poleceniem cgc resource restart. Alternatywną opcją jest wysłanie do aplikacji sygnału HUP, który spowoduje przeładowanie konfiguracji backendów bez restartu aplikacji. W tym celu należy zalogować się do kontenera aplikacji LLM API wykonać polecenie kill -1 1.

Do polecenia cgc compute create llm-api można dodać argument -e llmproxy_config=/ścieżka/do/pliku, aby wskazać niestandardową lokalizację pliku konfiguracyjnego.

Zarządzanie użytkownikami​

Dodanie nowego użytkownika sprowadza się do wygenerowania nowego klucza API. Zaloguj się przez SSH do swojego kontenera LLM API i wykonaj następujące polecenie.

llmproxyctl user create --comment 'John Smith' --expires '2030-01-01 13:37'

# z argumentami w krótszej postaci
llmproxyctl user create -t 'John Smith' -e '2030-01-01 13:37'

Program wypisze informacje o nowym użytkowniku i wygenerowany losowo klucz.

User created
Expires: 2030-01-01 12:37:00+00:00
Comment: John Smith
Hash: dd2a60b77cb2
Plain API key: 3FpwuzyXdU-u3bm8hg9ipA3ZB7JFWSqYHPRw1EMeQsB-XuV5cJ_...

Warto zauważyć, że nie ma możliwości ponownego wypisania klucza do API w tej postaci. Jest to spowodowane faktem, że klucze są przechowywane w bazie danych w postaci skróconej (hasz).

Listę wszystkich użytkowników można uzyskać następującym poleceniem.

llmproxyctl user list

Jest również możliwość edycji komentarza lub daty wygaśnięcia dostępu użytkownika. Zwróć uwagę na pierwszą kolumnę listy użytkowników (Hash). Wybór użytkownika do edycji odbywa się na podstawie kilku pierwszych znaków tego hasza, na przykład (tutaj: da):

llmproxyctl user list
Hash Expires Status Comment
------------ -------------------- ------- -------
dd2a60b77cb2 2030-01-01 12:37:00 active John Smith
a26a23f52cd8 - active Sam Altman
da7534bd88a0 2022-10-26 22:00:00 expired Jack Dorosey

llmproxyctl user update -t "Elon Musk" -e '2030-01-01 00:00' da
User updated

llmproxyctl user list
Hash Expires Status Comment
------------ -------------------- ------- -------
dd2a60b77cb2 2030-01-01 12:37:00 active John Smith
a26a23f52cd8 - active Sam Altman
da7534bd88a0 2030-01-01 00:00:00 active Elon Musk

Wyłączenie danego klucza odbywa się poprzez ustawienie jego daty wygaśnięcia na specjalną wartość now.

llmproxyctl user update -e now da