לבנות בבהירות.
API תואם OpenAI, עם מסלולי מודלים מפורסמים וטיפול פשוט וברור בנתונים.
איפה אנחנו עומדים
ה־API בנוי ונבדק. עד שייפתח מסלול המודל הראשון, בקשות צ׳אט מחזירות 503 inference_not_configured. המצב העדכני תמיד זמין ב־https://undergrove.vercel.app/api/status.
הוא עוקב אחרי הפורמט של OpenAI API, כך שערכות SDK קיימות עובדות אחרי שינוי כתובת הבסיס: צ׳אט עם תמונות, קריאה לפונקציות ופלטים מובנים, וגם embeddings, תמלול ודיבור. כדי שהבקשות יישארו בלתי קריאות אפילו לשער שלנו, השתמשו במצב מוצפן, כמו שסביבת העבודה עושה.
התחלה מהירה
כתובת בסיס: https://undergrove.vercel.app/v1. מציגים את המודלים עם GET /v1/models, ואז מזרימים תשובה:
curl https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"stream": true,
"messages": [{"role": "user", "content": "Help me explore an idea."}]
}'HTTP/2 200
9 כותרות
- content-type
- text/event-stream; charset=utf-8
- cache-control
- no-store
- x-undergrove-route
- tinfoil
- x-undergrove-attestation
- verified-by-gateway
- x-undergrove-enclave-host
- inference.tinfoil.sh
- x-undergrove-enclave-measurement
- c0ffee12…9a7d
- x-undergrove-enclave-key
- 4f2b81e0…d3c6
- x-undergrove-attestation-sha256
- 8e41c7aa…01fb
- x-undergrove-attestation-url
- /v1/attestation?sha256=8e41c7aa…01fb
התשובה עד עכשיו
מעבר מ־OpenAI בשורה אחת
Undergrove מדבר את OpenAI API, כך שערכות ה־SDK הרשמיות עובדות כמו שהן. תנו ללקוח את כתובת הבסיס של Undergrove ומפתח Undergrove, בשורה האחת שיוצרת אותו, ואז ציינו אחד מהמודלים הפתוחים שמוצעים כאן.
import os
from openai import OpenAI
client = OpenAI(base_url="https://undergrove.vercel.app/v1", api_key=os.environ["UNDERGROVE_API_KEY"])
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Summarise this contract in three points."}],
)
print(response.choices[0].message.content)
שתי שורות משתנות: כתובת הבסיס והמפתח של הלקוח, והמודל.
או בלי לשנות קוד בכלל: שתי ערכות ה־SDK קוראות את OPENAI_BASE_URL ואת OPENAI_API_KEY כשהלקוח נוצר בלעדיהם, כך שהגדרתם ל־https://undergrove.vercel.app/v1 ולמפתח שלכם מעבירה אפליקציה כמו שהיא. הזרמה, תמונות, קריאה לפונקציות ופלטים מובנים עובדים כמתואר למטה, במודלים שתומכים בהם.
כלים למפתחים
לקוחות שמאמתים את המובלעת ומצפינים במחשב שלכם. כל אחד מהם בודק קודם את האטסטציה של מובלעת Tinfoil, ואז מצפין כל בקשה אל המובלעת הזו, כך שהממסר של Undergrove מעביר אותה הלאה בלי יכולת לקרוא אותה. הם נמצאים בתיקייה packages/ במאגר, נבדקים מול תחליף מקומי, ועדיין לא פורסמו באף מאגר חבילות.
נסו את ה־CLI
הטרמינל הזה משחזר את הפקודה האמיתית undergrove. הפלט שלו הוקלט מול התחליף המקומי שהחבילות נבדקות מולו: verify היא בדיקה אמיתית של אטסטציה מוקלטת של Tinfoil, models מציגה את הקטלוג הנוכחי, והתשובות ל־ask כתובות מראש. אין מודל מאחורי הדף הזה, ושום דבר שמקלידים לא יוצא ממנו.
הטרמינל נטען כשמגיעים אליו.
אפליקציות לדוגמה
שלוש אפליקציות אינטרנט קטנות ב־packages/examples, כל אחת דף ושרת Node שמחזיק את המפתח. בלי מפתח הן רצות מול התחליף המקומי עם תשובות כתובות מראש, ושום דבר לא יוצא מהמחשב שלכם; כש־UNDERGROVE_API_KEY מוגדר, אותו קוד מאמת את המובלעת ומצפין אליה כל בקשה.
צ׳אט שעונה מתוך קובץ של מרכז עזרה, ומוזרם לדף כאירועים שנשלחים מהשרת.
npm run support-botב־packages/examples; קוד המקור נמצא ב־packages/examples/support-bot
const SYSTEM = `You are the support assistant for Acme Bikes.
Answer only from the help centre below. Quote prices and times exactly.
When it doesn't cover a question, say so and offer the contact options.
Keep answers short: two to four sentences, or a short list.
${knowledge}`;
// Streams one answer. `history` is the conversation so far, as
// [{ role: "user" | "assistant", content }].
export async function answer(undergrove, history, send) {
const stream = await undergrove.chat.completions.create({
model,
stream: true,
max_tokens: 600,
messages: [{ role: "system", content: SYSTEM }, ...history.slice(-12)],
});
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content;
if (text) send({ text });
}
}בעורך וב־CI
- SDK ל־Node@undergrove/sdk: צ׳אט בסגנון OpenAI, הזרמה, embeddings, תמלול ודיבור, מוצפן כברירת מחדל.
packages/sdk-js - SDK ל־Pythonundergrove, מבוסס על ה־SDK של Tinfoil ל־Python, עם אותה ברירת מחדל מוצפנת.
packages/sdk-python - CLIundergrove ask, models ו־verify, עם המפתח במחזיק המפתחות של המערכת. לעורכים: --events מזרים שורות JSON, acp מריץ אותו כסוכן ו־mcp ככלים.
packages/cli - VS Codeחלונית צ׳אט, ו־Explain selection, Fix this (עם Apply fix) ו־Write tests על הקוד שמולכם.
packages/vscode - JetBrains IDEsצ׳אט בחלון כלי ואותן שלוש פעולות ב־IntelliJ IDEA, ב־PyCharm ובשאר, עם המפתח בכספת הסיסמאות של ה־IDE.
packages/jetbrains - ZedUndergrove כסוכן ב־Agent Panel, עם /explain, /fix, /tests ו־/review, או ככלים לסוכן של Zed עצמו. שתי האפשרויות מריצות את ה־CLI, שמצפין במחשב שלכם.
packages/zed - Neovim:UndergroveAsk, ו־:UndergroveExplain על בחירה במצב visual, עם התשובה מוזרמת לחלון צף.
packages/neovim - RaycastAsk Undergrove, ו־Ask About Selected Text בכל אפליקציה.
packages/raycast - GitHub ActionUndergrove Review: סקירה אחת לכל pull request, עם הערות על שורות והצעות, והאטסטציה בתחתית שלה.
packages/github-action
סביבת ניסויים
אפשר לשלוח בקשת צ׳אט מהדף הזה עם המפתח שלכם, ולראות את התשובה, את השימוש ואת התגובה הגולמית, יחד עם אותה בקשה כפקודת curl. היא עוברת מהדפדפן שלכם אל /v1 של האתר הזה, כמו כל קריאת API אחרת, ומנצלת את המכסה או הקרדיט שלכם כרגיל.
בחרו מוצפן כדי לשלוח אותה כמו שהמצב המוצפן של ערכות ה־SDK עושה: ה־SDK של Tinfoil מאמת את המובלעת בדף הזה, ואז אוטם אליה את הבקשה. סביבת הניסויים מציגה את הבייטים שבאמת יצאו מהדף, את מה שחזר, ואת הקבלה של המובלעת שאליה הבקשה נאטמה.
אותה בקשה עם curl
curl --no-buffer https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"messages": [
{
"role": "user",
"content": "תעזור לי לחקור רעיון."
}
],
"stream": true
}'אימות זהות
שלחו מפתח API של Undergrove כ־Bearer token. מפתחות מתחילים ב־ugk_ וניתנים בהזמנה בתקופת האלפא. כשמפתחות בשירות עצמי מופעלים, חברי האלפא יוצרים עד חמישה מפתחות בהגדרות של סביבת העבודה, וכל אחד מהם מנצל את המכסה והקרדיט של החשבון שלו. אנחנו שומרים רק גיבוב חד־כיווני של כל מפתח, ולכן אי אפשר לשחזר מפתח שאבד, רק להחליף אותו.
לכל מפתח בשירות עצמי יכולות להיות מגבלות משלו, שנקבעות כשהוא נוצר או אחר כך בהגדרות: תקרת הוצאה יומית, שאחריה הבקשות שלו נדחות עד חצות UTC, ורשימה של המודלים שמותר לו להשתמש בהם. מפתח עם תקרה עדיין נספר במכסה של החשבון שלו. למפתחות שנוצרו לפני שהיו מגבלות אין מגבלות, והם ממשיכים לעבוד כמו קודם.
curl https://undergrove.vercel.app/v1/models \
-H "Authorization: Bearer $UNDERGROVE_API_KEY"נקודות קצה
| נקודת קצה | מטרה |
|---|---|
| GET /v1/models | המודלים הזמינים עכשיו, כל אחד עם סוג (צ׳אט, embedding, תמלול או דיבור), הרישיון שלו, גודל ההקשר, מחירים, רמות חשיבה, תמיכה בתמונות, בכלים ובחיפוש באינטרנט, קצב (מהיר או שקול), והמקום שבו הוא רץ. |
| POST /v1/chat/completions | Chat, streamed or as a single response, with images, function calling and structured outputs where the model supports them. |
| POST /v1/embeddings | וקטורים לחיפוש ולדמיון מ־Nomic Embed Text v1.5 (768 ממדים). עד 256 קלטים. $0.002 לכל 48 קלטים או 67,200 תווים, הגבוה מביניהם. |
| POST /v1/audio/transcriptions | תמלולים מ־Whisper Large v3 Turbo. העלאה מרובת חלקים (multipart) של עד 4.4 MB, עם שפה ופרומפט אופציונליים. $0.01 לכל מגה־בייט שהתחיל. |
| POST /v1/audio/speech | שמע WAV מ־Qwen3-TTS בתשעה קולות: aiden, dylan, eric, ono_anna, ryan, serena, sohee, uncle_fu ו־vivian. עד 4,096 תווים. $0.005 לכל 600 תווים שהתחילו. |
| GET /api/status | מצב השירות, מגבלות השימוש ומרשם המודלים המלא. |
תגובות משתמשות במבנה הרגיל של chat.completion. זרמים שולחים אירועי chat.completion.chunk ומסתיימים ב־data: [DONE]. מודלי חשיבה מזרימים את החשיבה שלהם בנפרד ב־delta.reasoning_content, והמקטע האחרון מדווח על השימוש בטוקנים. אם זרם נקטע, האירוע האחרון הוא אובייקט שגיאה במקום [DONE], כך שתשובה חלקית אף פעם לא נחשבת בטעות לתשובה מלאה.
curl https://undergrove.vercel.app/v1/embeddings \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "undergrove/nomic-embed-text",
"input": ["Where is the signed contract?", "Payment terms"]
}'פרמטרים
| שדה | סוג | ברירת מחדל | משמעות |
|---|---|---|---|
model | string | חובה | מזהה מתוך /v1/models. |
messages | array | חובה | עד 200 הודעות system, user, assistant ו־tool. במודלים עם ראייה, הודעות user יכולות לכלול עד 16 חלקי image_url ככתובות data URL ב־base64 (PNG, JPEG, GIF או WebP); קישורים לא נשלפים. כל תמונה נחשבת 3,000 טוקני קלט, אלא אם המודל מדווח אחרת. |
tools, tool_choice, parallel_tool_calls | array, string | object, boolean | — | קריאה לפונקציות במודלים עם tools ב־/v1/models: עד 128 פונקציות. קריאות מוזרמות מגיעות כ־deltas של tool_calls, והתשובה מסתיימת ב־finish_reason tool_calls. שלחו את התוצאות בחזרה כהודעות tool עם ה־tool_call_id שלהן. |
response_format | object | — | פלטים מובנים: {"type": "json_object"}, או json_schema עם שם וסכמה. |
stream | boolean | false | שלחו true כדי להזרים את התשובה כאירועים שנשלחים מהשרת. |
max_tokens | integer | המגבלה של המודל | מתקבל גם בשם max_completion_tokens. מוגבל למגבלת הפלט של המודל. |
temperature, top_p | number | נקבע על ידי המודל | בקרות דגימה: 0–2 ו־0–1. |
reasoning_effort | string | נקבע על ידי המודל | כמה זמן מודל חשיבה חושב לפני שהוא עונה. הערכים המקובלים משתנים ממודל למודל ומפורטים ב־/v1/models; האפשרויות מהיר ויסודי בסביבת העבודה משתמשות בערכי fast ו־thorough שמפורטים שם. |
web_search_options | object | — | שלחו {} כדי לאפשר למודל לחפש באינטרנט. השאילתות מסוננות מפרטים אישיים, והתוצאות נבדקות מפני הזרקת פרומפטים. המקורות מגיעים כהערות url_citation. כל בקשת חיפוש עולה $0.05 מהמכסה שלכם. |
stop | string | string[] | — | מחרוזת או עד ארבע מחרוזות. |
presence_penalty, frequency_penalty, seed | number, integer | — | מועברים הלאה כשמסלול המודל תומך בהם. |
curl https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"max_tokens": 500,
"messages": [{"role": "user", "content": "Is it raining in Lisbon?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'מצב מוצפן
את ה־API הרגיל השער שלנו קורא בזיכרון כדי לבדוק כל בקשה. כדי שהבקשות יישארו בלתי קריאות גם ל־Undergrove, השתמשו ב־SDK של Tinfoil עם המפתח שלכם. הוא מאמת את אטסטציית החומרה של המובלעת, ואז מצפין כל בקשה כך שרק המובלעת הזו יכולה לקרוא אותה. הממסר שלנו בודק את המפתח ואת המכסה שלכם ומעביר הלאה את הטקסט המוצפן.
- הקוד שלךה־SDK של Tinfoil מאמת את המובלעת, ואז מצפין אליה כל בקשה.יכול לקרוא את הבקשה
- אטום עבור המובלעתהממסר של Undergroveבודק את המפתח ואת המכסה, מעביר את הטקסט המוצפן הלאה. לא יכול לקרוא אותו.רואה רק טקסט מוצפן
- עדיין אטוםמובלעת המודלהמקום היחיד שבו הבקשה מפוענחת והמודל רץ.יכול לקרוא את הבקשה
import { SecureClient } from "tinfoil"; // npm install tinfoil
const RELAY = "https://undergrove.vercel.app/api/enclave";
const client = new SecureClient({
baseURL: `${RELAY}/v1/`,
attestationBundleURL: RELAY,
});
await client.ready(); // verifies the enclave; throws if it can't
const response = await client.fetch("chat/completions", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "gemma4-31b", // enclave_model in /v1/models
max_tokens: 1000,
messages: [{ role: "user", content: "Summarise this contract." }],
}),
});
console.log((await response.json()).choices[0].message.content);אותו לקוח מגיע גם ל־embeddings, ל־audio/transcriptions ול־audio/speech. מכיוון שהממסר לא יכול לקרוא את הבקשות האלה, המודלים נקראים לפי מזהה ה־enclave_model שלהם מ־/v1/models, התשובות חוזרות בפורמט של Tinfoil, והשימוש מוערך לפי גודל הנתונים המוצפנים. המכסות והקרדיט חלים כרגיל.
שרת MCP
Undergrove הוא גם שרת Model Context Protocol, בכתובת https://undergrove.vercel.app/mcp. כל מודל חי הוא כלי, כמו ask_<model>, שלקוחות MCP כמו Claude Code, Cursor ו־Claude Desktop יכולים לקרוא לו עם מפתח ה־API שלכם. כלי מקבל prompt, ובאופן אופציונלי גם system ו־max_tokens, ועבור מודלים שמחפשים גם web_search, ומחזיר את התשובה של המודל כטקסט.
קריאה לכלי היא בקשת API כמו כל בקשה אחרת: השער קורא אותה בזיכרון, שולח אותה במסלול שפורסם של המודל, לא שומר עותק, ומחשב אותה מול המגבלות, המכסה והקרדיט של המפתח שלכם. השרת משתמש ב־streamable HTTP, עונה ב־JSON ולא שומר סשנים. הוא תומך בגרסאות הפרוטוקול 2025-11-25, 2025-06-18 ו־2025-03-26.
Claude Code
מריצים פעם אחת בטרמינל:
claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
--header "Authorization: Bearer ugk_your_key"Cursor
בקובץ ~/.cursor/mcp.json, או ב־.cursor/mcp.json בתוך פרויקט:
{
"mcpServers": {
"undergrove": {
"url": "https://undergrove.vercel.app/mcp",
"headers": {
"Authorization": "Bearer ugk_your_key"
}
}
}
}Claude Desktop
בקובץ claude_desktop_config.json (Settings › Developer › Edit Config). Claude Desktop מתחבר לשרתים מרוחקים דרך mcp-remote, שדורש Node.js:
{
"mcpServers": {
"undergrove": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://undergrove.vercel.app/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer ugk_your_key"
}
}
}
}כל לקוח
או לקרוא לו ישירות. הוא מדבר JSON-RPC על גבי HTTP בהזרמה (streamable HTTP):
curl https://undergrove.vercel.app/mcp \
-H "Authorization: Bearer ugk_your_key" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'אחר כך אפשר לקרוא לכלי של מודל:
curl https://undergrove.vercel.app/mcp \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "ask_<model>",
"arguments": {"prompt": "Name three uses for a sealed envelope."}
}
}'בכיוון ההפוך, סביבת העבודה יכולה להשתמש בכלים משרתי MCP שמחברים בהגדרות. הקריאות האלה עוברות דרך השרת של Undergrove אל השרת שחיברתם, כטקסט גלוי, ולכן כדאי להשתמש רק בשרתים שסומכים עליהם עם מה שהכלים מקבלים.
שגיאות ומגבלות
שגיאות משתמשות בפורמט של OpenAI: {"error": {"message", "type", "code"}}.
| סטטוס | קודים |
|---|---|
| 400 | invalid_request, invalid_json, unsupported_parameter, unsupported_content, context_length_exceeded, model_rejected_request, unsupported_api_version, async_needs_webhook, async_stream, invalid_webhook_url |
| 401 | authentication_required, invalid_api_key, invalid_session, sandbox_key (מפתח ugk_sandbox_, שרק התחליף המקומי עונה לו) |
| 403 | alpha_access_required, model_not_allowed_for_key (המפתח מוגבל למודלים אחרים) |
| 404 | model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found |
| 409 | idempotency_in_progress (הבקשה הראשונה עם ה־Idempotency-Key הזה עדיין רצה), version_conflict (פריט אטום השתנה מאז שנקרא), id_taken |
| 413 | request_too_large (צ׳אט ותמלול מעל 4.4 MB, embeddings מעל 1 MB, דיבור מעל 100 KB) |
| 422 | idempotency_key_reused (אותו Idempotency-Key עם בקשה אחרת) |
| 429 | rate_limited, budget_exhausted, capacity_exhausted, key_budget_exceeded (המפתח הגיע לתקרה היומית שלו; ההודעה מציינת את מועד האיפוס). המתינו לפי הכותרת Retry-After, הוסיפו קרדיט בהגדרות של סביבת העבודה, או הגדילו שם את התקרה של המפתח. |
| 502 | model_unavailable, stream_interrupted |
| 503 | capacity_exhausted (עם Retry-After), inference_not_configured, realtime_not_available (מסלול שמע לא עבר אטסטציה) |
| 504 | model_timeout |
בתקופת האלפא, לכל מפתח או חשבון יש מכסת בקשות לדקה, מכסת בקשות יומית ומכסת הוצאה יומית. הערכים הנוכחיים מפורסמים ב־/api/status.
HTTP/2 429
retry-after: 41
{
"error": {
"message": "You’re sending messages quickly. Please wait a moment.",
"type": "rate_limit_error",
"code": "rate_limited"
}
}לדעת לאן הנתונים שלכם הולכים
- השרת שלךשולח את הבקשה עם המפתח שלכם.יכול לקרוא את הבקשה
- HTTPSהשער של Undergroveבודק את המפתח ואת המגבלות, קורא את הבקשה בזיכרון, לא שומר עותק.יכול לקרוא את הבקשה
- מוצפנת אל המובלעת המאומתתמובלעת המודלמובלעת החומרה של Tinfoil מריצה את המודל במסלול שפורסם.יכול לקרוא את הבקשה
השער בודק את המפתח שלכם, מחיל מגבלות, ומעביר למסלול המודל רק את השדות שלמעלה. הוא לא שומר עותק של פרומפטים או תשובות ולא רושם אותם ביומן. היומנים שלו מתעדים רק שמות מסלולים, קודי סטטוס ותזמונים. מוני מגבלת הקצב משתמשים בגיבוב חד־כיווני של המפתח או החשבון שלכם ופגים תוך יומיים.
המפעיל, המיקום ומדיניות השמירה של כל מסלול מודל מפורסמים במרשם המודלים ב־/api/status ובדף הפרטיות. בקשות אף פעם לא עוברות למסלול אחר כגיבוי. Vercel, המארחת שלנו, שומרת מטא־נתונים רגילים של בקשות, כמו כתובות IP.
במסלולי מחשוב חסוי, שער ה־API מאמת את אטסטציית החומרה של המובלעת לפני ההתחברות ומצפין כל בקשה כך שרק המובלעת הזו יכולה לקרוא אותה. השער עצמו עדיין קורא בקשות API בזיכרון כדי לבדוק אותן.
נתוני סביבת העבודה
סנכרון, קישורים משותפים ופרויקטים משותפים שומרים רק טקסט מוצפן שהדפדפן יצר. כל תרשים מראה, צעד אחר צעד, למי יש את המפתח.
מה האימות מוכיח
לפני שהיא שולחת משהו, סביבת העבודה מריצה בדפדפן שלכם את המאמת בקוד פתוח של Tinfoil. הוא בודק את דוח אטסטציית החומרה של המובלעת מול מפתחות החתימה של AMD או של Intel, בודק שהקוד שנמדד במובלעת תואם לגרסה ש־Tinfoil פרסמה וחתמה ב־GitHub, ובודק שמפתח ההצפנה שהוא משתמש בו שייך למובלעת הזו. בחרו מאומת בסביבת העבודה כדי לראות כל בדיקה, את הגרסה ואת המדידה.
זה מוכיח שההודעות שלכם יכולות להיות מפוענחות רק על ידי החומרה הזו כשהיא מריצה את הקוד שפורסם. זה לא מוכיח שהקוד שפורסם נקי מפגמים, לא מגן מפני חולשות במעבדים עצמם, ולא מכסה את מה שאתם עושים בתשובות אחרי שהן מגיעות לדפדפן שלכם. הקוד של סביבת העבודה מוגש על ידי Undergrove, כך שאתם סומכים עלינו שנגיש את המאמת כפי שפורסם; אימות הדף הזה משווה את הקוד שהדפדפן שלכם טען למניפסט של ה־build, ו־npm run verify:site משווה את האתר ל־build שאתם בונים בעצמכם מאותו commit. מתוכננת הערכה עצמאית.
לכל תשובה ממובלעת יש לחצן קבלה. הוא מציג את המובלעת, הגרסה והמדידות שאומתו לפני שהבקשה נשלחה ואת המפתח שאליו היא הוצפנה, ומוריד אותם כ־JSON. עד שהלשונית נסגרת, ההורדה כוללת את דוח האטסטציה, כך שאפשר להריץ עליו את המאמת שוב מאוחר יותר; אחרי כן, יש בה את הגיבוב של הדוח ואת המקום שממנו אפשר להביא את הדוח הנוכחי של המובלעת. הקבלה היא תיעוד של הדפדפן שלכם, לא הצהרה חתומה, והמובלעת שמופיעה בה היא נתב המודלים של Tinfoil; הנתב מעביר את הבקשות הלאה למובלעת של המודל עצמו. הקבלה מציינת גם את המובלעת הזו, והדפדפן שלכם מנסה לאמת אותה באותה דרך. רוב מובלעות המודלים רצות על Intel TDX, שהמאמת בדפדפן עדיין לא יכול לבדוק; עבורן הקבלה מציינת זאת, והבדיקה נשענת על הנתב, שהקוד שפורסם שלו מאמת כל מובלעת מודל לפני שהוא משתמש בה.
# The attestation bundle the gateway verified, what it checked
# in it, and the steps to check it again yourself
curl https://undergrove.vercel.app/v1/attestationתגובות שאפשר לאמת
במודלים שרצים במובלעת, כל תגובת API מציינת איזו מובלעת השער אימת לפני ששלח את הבקשה שלכם, ו־GET /v1/attestation מחזיר את חבילת האטסטציה שמאחוריה, כך שאפשר להריץ את המאמת בקוד פתוח של Tinfoil על אותן ראיות.
| כותרת | ערך |
|---|---|
x-undergrove-attestation | verified-by-gateway |
x-undergrove-enclave-host | המובלעת שעברה אטסטציה: נתב המודלים של Tinfoil |
x-undergrove-enclave-measurement | מדידת הקוד, מותאמת לגרסה חתומה |
x-undergrove-enclave-release | תג הגרסה (וגם -release-digest, ה־digest שפורסם שלה) |
x-undergrove-enclave-key | המפתח הציבורי של המובלעת שאליו הבקשה הוצפנה |
x-undergrove-attestation-sha256 | ה־SHA-256 של דוח האטסטציה |
x-undergrove-attestation-url | מאיפה להביא את החבילה: /v1/attestation?sha256=… |
אפשר לקרוא אותן מכל לקוח. עם ה־SDK של OpenAI ל־Python, בקשו את התגובה הגולמית:
curl -si https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Say hello."}]}' \
| grep -i '^x-undergrove-'כדי לבדוק תגובה, סקריפט ה־Node הזה שולח בקשה, מביא את החבילה שהכותרות שלה מציינות, מאמת אותה עם @tinfoilsh/verifier, ובודק שהמפתח, המדידה, המובלעת והגיבוב של הדוח תואמים כולם. סביבת הניסויים שלמעלה עושה את אותו הדבר בדפדפן שלכם עם בדיקה בדפדפן הזה.
verify-response.mjs
// Checks the attestation evidence on an Undergrove API response yourself.
//
// npm install @tinfoilsh/verifier
// UNDERGROVE_API_KEY=ugk_… node verify-response.mjs
import { createHash } from "node:crypto";
import { Verifier } from "@tinfoilsh/verifier";
const base = process.env.UNDERGROVE_BASE_URL || "https://undergrove.vercel.app/v1";
const model = process.env.UNDERGROVE_MODEL || "undergrove/gpt-oss-120b";
// 1. Each response from an attested route names the enclave the gateway
// verified and encrypted the request to.
const response = await fetch(`${base}/chat/completions`, {
method: "POST",
headers: {
authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model,
max_tokens: 50,
messages: [{ role: "user", content: "Say hello." }],
}),
});
if (!response.ok) throw new Error(`The API answered ${response.status}.`);
const header = (name) => response.headers.get(`x-undergrove-${name}`);
const claim = {
host: header("enclave-host"),
key: header("enclave-key"),
measurement: header("enclave-measurement"),
report: header("attestation-sha256"),
};
if (!claim.key) throw new Error("This response carries no attestation evidence.");
// 2. Fetch the attestation bundle it names. Without a report hash, the
// route's current bundle (its key must still match).
const query = claim.report ? `?sha256=${claim.report}` : "";
const { bundle } = await (await fetch(`${base}/attestation${query}`)).json();
// 3. Verify the bundle with Tinfoil's open-source verifier: the chip
// maker's signature on the report, the release's signed build record
// from GitHub, and the enclave's certificate. It throws if any fails.
const verifier = new Verifier({ configRepo: "tinfoilsh/confidential-model-router" });
const attested = await verifier.verifyBundle(bundle);
const verified = verifier.getVerificationDocument();
// 4. The verified bundle must attest exactly what the response claimed.
const { format, body } = bundle.enclaveAttestationReport;
const hash = createHash("sha256").update(format + body).digest("hex");
const checks = [
["report hash", !claim.report || hash === claim.report],
["encryption key", attested.hpkePublicKey === claim.key],
["code measurement", verified.codeFingerprint === claim.measurement],
["enclave host", verified.enclaveHost === claim.host],
];
for (const [name, ok] of checks) if (!ok) throw new Error(`The ${name} doesn’t match the response.`);
console.log(
`Verified: ${verified.enclaveHost} runs ${verified.configRepo} ${verified.releaseTag || ""},` +
` and its attested key is the one this response names (${claim.key.slice(0, 16)}…).`,
);
מה בדיקה שעוברת מראה
שהתקיימה מובלעת עם המפתח הזה והקוד הנמדד הזה: חומרת מחשוב חסוי אמיתית של AMD או של Intel, שמריצה את הגרסה ש־Tinfoil פרסמה וחתמה ב־GitHub. ושהשער מצהיר שהוא אימת את המובלעת הזו והצפין את הבקשה שלכם למפתח שלה לפני שהעביר אותה.
מה היא לא מראה
שהשער באמת הצפין את הבקשה שלכם למפתח הזה. הכותרות הן הדוח של השער, לא הצהרה חתומה על ידי המובלעת, והשער קורא בקשות API בזיכרון כדי לבדוק אותן. כדי להסתמך במקום זאת על האימות שלכם, השתמשו במצב מוצפן, שבו הלקוח שלכם בודק את המובלעת ומצפין כל בקשה בעצמו. המובלעת שמצוינת היא נתב המודלים של Tinfoil, שמעביר את הבקשות הלאה למובלעת של המודל עצמו.
עוזר לאתר
הוסיפו עוזר לאתר שלכם עם תג script אחד. הוא מוסיף לחצן שפותח חלונית צ׳אט, שעליה עונה המודל הפתוח שבוחרים, במסגרת תקרת הוצאה יומית שאתם קובעים. החלונית היא מסגרת (frame) שמוגשת על ידי Undergrove, כך שהדף שלכם לא יכול לקרוא את מה שמבקרים מקלידים.
- בסביבת העבודה, פתחו את הגדרות וצרו מפתח שניתן לפרסום תחת עוזר לאתר: השם שלו, המודל, האתרים שמותר להם להשתמש בו ותקרה יומית.
- העתיקו את הקוד שמתקבל והדביקו אותו לפני
</body>בכל דף. - נסו אותו קודם בדף ההדגמה, חנות מומצאת.
<script
src="https://undergrove.vercel.app/embed.js"
data-key="ugp_…"
data-model="<model-id>"
data-instructions="אנחנו סטודיו לקדרות. תעזור בשאלות על חוגים, שעות פתיחה ומשלוחים."
async
></script>| מאפיין | סוג | ברירת מחדל | משמעות |
|---|---|---|---|
data-key | string | חובה | המפתח שלכם שניתן לפרסום, ugp_… |
data-model | string | חובה | אחד מהמודלים שהמפתח מפרט. |
data-title | string | “Ask a question” | הכותרת של החלונית. |
data-greeting | string | — | ההודעה הראשונה שמבקרים רואים. |
data-instructions | string | — | על מה האתר שלכם, במה לעזור, ובאיזה טון. נשלח עם כל שאלה; מבקרים יכולים לקרוא אותו בדף שלכם. |
data-label | string | “Ask” | הטקסט של הלחצן. |
data-position | string | right | right או left. |
data-theme | string | auto | auto (המכשיר של המבקר), light או dark. |
data-open | boolean | false | true פותח את החלונית כשהדף נטען. |
למה מפתח שניתן לפרסום יכול להופיע בדף ציבורי
- הוא עובד רק לצ׳אט, ורק מתוך מסגרת העוזר של Undergrove עצמו. ה־API דוחה אותו בכל מקום אחר.
- הוא עובד רק כשהמסגרת מוטמעת באחד מהאתרים שציינתם. הדפדפן מדווח איזה דף מטמיע את המסגרת, והשרת בודק זאת.
- תמיד יש לו תקרת הוצאה יומית ורשימת מודלים. תוכנה שרצה מחוץ לדפדפן יכולה להתחזות לכל אתר, אז התייחסו למפתח כציבורי וקבעו את התקרה לסכום שאתם מוכנים להוציא ביום.
- לכל מבקר יש מגבלת שאלות לדקה ומגבלה יומית, שמפורסמות ב־/api/status. המבקרים נספרים לפי גיבוב חד־כיווני עם מפתח של כתובת ה־IP שלהם, אף פעם לא לפי הכתובת עצמה, והספירות פגות תוך יומיים.
התשובות מנצלות את המכסה והקרדיט של החשבון שלכם, כמו כל מפתח. במודלים שרצים במובלעת, המסגרת מצפינה כל הודעה בדפדפן של המבקר אל המובלעת המאומתת, כמו שסביבת העבודה עושה; למודלים אחרים מגיעים דרך השער, שקורא בקשות בזיכרון ולא שומר עותק. בשני המקרים האתר שלכם לא רואה את ההודעות.
התג “Verified by Undergrove”
אתר עם עוזר יכול להציג תג קטן שמקשר אל הדף של Undergrove עבור המפתח שלו. הדף הזה קורא את האתרים והמודלים של המפתח מהשרת של Undergrove, מאמת את המובלעת שמאחוריהם בדפדפן של המבקר, ומסביר מה התג טוען ומה לא. הדביקו את הקוד במקום שבו התג צריך להופיע.
או, בלי סקריפט, תמונה וקישור (badge-dark.svg לדפים כהים):
<script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>- התג הוא קישור. הוא לא שולח שום בקשה מהדף שלכם ולא בודק שום דבר בעצמו; הדף שהוא פותח הוא שעושה את הבדיקה.
- הוא מציין את המפתח שלכם שניתן לפרסום, שכבר נמצא בדף שלכם בשביל העוזר. דף התג מציג את האתרים והמודלים של המפתח, ואף פעם לא את החשבון, את התקרה או את מה שהוצא.
- הקישור מבקש מהדפדפן לשלוח את המקור (origin) של האתר שלכם, כדי שהדף יוכל לומר למבקרים אם הגיעו מאחד האתרים של המפתח. תג שהועתק לאתר אחר מציין זאת שם.
- הוא ערב לעוזר בלבד, לא לשאר האתר שלכם.
אינטגרציות
רוב הלקוחות של Undergrove עצמו בודקים את האטסטציה של המובלעת עם המאמת של Tinfoil, ואז מצפינים כל בקשה אל המובלעת הזו, כך שהממסר מעביר אותה הלאה בלי יכולת לקרוא אותה. כל אחד מהם מקבל את מפתח ה־API שלכם ומשתמש במזהי המודלים של Undergrove. הם עדיין לא נמצאים במאגרי חבילות; כל אחד נבנה מ־packages/ במאגר של האתר.
הלקוחות ל־Rust, ל־Java ול־Kotlin והאפליקציה ל־Make משתמשים במקום זאת ב־API למפתחים, כי אין עבורם SDK של Tinfoil לבנות עליו: השער קורא כל בקשה בזיכרון, מאמת את המובלעת בעצמו ומדווח מה הוא אימת בכותרות של התשובה.
| חבילה | עבור | מה היא עושה |
|---|---|---|
@undergrove/sdk | Node | לקוח בסגנון OpenAI עם הזרמה, כלים, embeddings, תמלול ודיבור. |
undergrove | Python | לקוח ה־Python, עם LangChain (ChatUndergrove) ו־LlamaIndex (UndergroveLLM) כתוספות. |
Go | Go | לקוח שמבוסס על המאמת של Tinfoil ל־Go, עם הזרמה וקריאות לכלים. |
Undergrove | Swift | חבילת Swift ל־macOS 14 ול־iOS 17, שמבוססת על המאמת של Tinfoil ל־Swift. |
@undergrove/ai-sdk-provider | Vercel AI SDK | generateText, streamText וכלים, עם מה שאומת במטא־נתוני הספק של כל תשובה. |
@undergrove/langchain | LangChain.js | מודל צ׳אט ו־embeddings. |
@undergrove/react | React | רכיבי צ׳אט ו־hooks, ונתיב שרת שמרחיק את מפתח ה־API מהדפדפן. |
create-undergrove-app | פתיח | npm create undergrove-app: אפליקציית צ׳אט פרטית עם פריסה ל־Vercel בשתי פקודות. |
@undergrove/agents | סוכנים | לולאת קריאה לכלים שמתעדת איזו מובלעת מאומתת ענתה על כל שלב, ברשומה שכל אחד יכול לבדוק אם נערכה, עם הצמדת מובלעות. |
undergrove | Rust | לקוח ל־API למפתחים, עם הזרמה וקריאות לכלים, וראיות האטסטציה של השער לכל תשובה. הוא לא מצפין בקשות אל המובלעת בעצמו. |
dev.undergrove:undergrove | Java ו־Kotlin | לקוח ל־API למפתחים, עם הזרמה, כלים ו־embeddings, ועם coroutines ו־Flow ל־Kotlin. לא מצב מוצפן: השער קורא בקשות בזיכרון. |
undergrove[jupyter] | Jupyter | פקודת הקסם %%undergrove לתאים, עם משתני המחברת ו־DataFrames כהקשר, ווידג׳ט אטסטציה. |
@undergrove/office | Excel ו־Word | =UNDERGROVE.ASK() בכל תא, וב־Word: סיכום, שכתוב או שאלה על הטקסט שנבחר. |
@undergrove/office | Outlook | סיכום של ההודעה הפתוחה, וטיוטת תשובה שבודקים לפני השליחה. |
undergrove-obsidian | Obsidian | שאלות על הפתק הפתוח, סיכום, שכתוב והמשך כתיבה, עם המובלעת המאומתת מתחת לכל תשובה. למחשב שולחני בלבד. |
Undergrove for Alfred | Alfred | מקלידים ug ושאלה: התשובה ב־Large Type ובלוח, מוצפנת דרך ה־CLI של undergrove. |
n8n-nodes-undergrove | n8n | צומת קהילתי: שאלה למודל, יצירת embeddings והצגת רשימת המודלים, עם מה שאומת בכל פריט. |
Undergrove for Zapier | Zapier | הפעולות Ask a Model ו־Create Embeddings, וכל בקשה מוצפנת אל המובלעת מתוך התהליך של ה־Zap. |
Undergrove for Make | Make | אפליקציה מותאמת אישית על ה־API למפתחים: השער מאמת את המובלעת ומדווח מה הוא אימת בפלט של כל מודול. |
Undergrove Enclave Pin | GitHub Actions | שלב שמאמת את המובלעת על ה־runner עם המאמת של Tinfoil ומכשיל את ה־job כשהיא לא זו שהצמדתם. |
עם Vercel AI SDK:
import { streamText } from "ai";
import { undergrove } from "@undergrove/ai-sdk-provider";
const result = streamText({
model: undergrove("undergrove/gemma-4-31b"),
prompt: "Summarise this contract.",
});
for await (const text of result.textStream) process.stdout.write(text);עם רכיבי React ותבנית הפתיחה, ההודעות עוברות מהדף לשרת שלכם, שאוטם אותן; מי שמפעיל את השרת הזה יכול לקרוא אותן, כמו בכל אינטגרציה בצד השרת.
בדיקות ב־CI
התחליף שהחבילות נבדקות מולו עונה למפתחות sandbox: כל מפתח שמתחיל ב־ugk_sandbox_. הבדיקות שלכם יכולות להשתמש במפתח כזה בלי סוד ובלי חיובים, ו־Undergrove עצמו דוחה אותם עם sandbox_key. התשובות שלו כתובות מראש, לא של מודל. ב־GitHub Actions, שלב אחד מפעיל אותו ומגדיר את UNDERGROVE_BASE_URL ואת UNDERGROVE_API_KEY:
- uses: ekempinski/undergrove/packages/github-action/sandbox@main
- run: npm test # UNDERGROVE_API_KEY=ugk_sandbox_ci
# And before a deploy: fail if the enclave isn’t the one you pinned.
- uses: ekempinski/undergrove/packages/github-action/pin@main
with:
measurement: 9134838da8aa4e1792566b2988820340356517c4db0c94261eb8154843e0357554ba72fcf185f950d9301fbede574c90מה הלאה
| זמין | Workspace with encryption from your browser to the enclave, streaming gateway, OpenAI-compatible API with images, function calling, structured outputs, embeddings, transcription and speech, encrypted API mode, web search, documents, folders and codebases, images, audio transcripts, read-aloud, search across long documents, voice input, model register, saved conversations and export on your device. |
|---|---|
| הבא | מסלול המודל הראשון, התחברות לאלפא הפרטית, מגבלות שימוש משותפות. |
| בנוי, עדיין לא מופעל | מפתחות בשירות עצמי, תוכניות בתשלום וקרדיט משולם מראש לשימוש מעבר למכסה היומית. |
| מתוכנן | גישה לצוותים. |
| דורש אימות | הערכת אבטחה עצמאית של סביבת העבודה, הממסר והשער. |