Switch inference to Tinker production endpoint (docs snippet)
Browse files- BASE_URL -> https://tinker.thinkingmachines.dev/services/tinker-prod/oai/api/v1
- DEFAULT_MODEL -> thinkingmachines/Inkling
- Token source: TINKER_API_KEY (with HF_TOKEN fallback)
- chat() now sends reasoning_effort='high', max_tokens=None, full Inkling
system prompt, and web_search/open_link_with_url tools
- Upload endpoint stays; vision payload uses uploaded public URLs
Co-Authored-By: Claude <noreply@anthropic.com>
- app.py +76 -44
- index.html +0 -3
app.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
| 1 |
"""
|
| 2 |
Inkling — a chat UI served by gradio.Server.
|
| 3 |
|
| 4 |
-
Backend: gradio.Server
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
Frontend:
|
| 9 |
-
|
| 10 |
-
through Gradio's queue — including the token stream.
|
| 11 |
"""
|
| 12 |
|
| 13 |
import os
|
|
@@ -46,56 +45,41 @@ def _quiet_unraisable(unraisable_args) -> None:
|
|
| 46 |
|
| 47 |
sys.unraisablehook = _quiet_unraisable
|
| 48 |
|
| 49 |
-
#
|
| 50 |
-
|
| 51 |
-
ROUTER_BASE_URL = "https://router.huggingface.co/v1"
|
| 52 |
DEFAULT_MODEL = "thinkingmachines/Inkling"
|
| 53 |
|
| 54 |
-
# Provider routing suffixes supported by the HF router. ":auto" uses the
|
| 55 |
-
# preferred provider from the user's HF settings; ":cheapest" and ":fastest"
|
| 56 |
-
# pick by cost or latency.
|
| 57 |
-
PROVIDERS = {
|
| 58 |
-
"auto": ":auto",
|
| 59 |
-
"together": ":together",
|
| 60 |
-
"cheapest": ":cheapest",
|
| 61 |
-
"fastest": ":fastest",
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
app = Server()
|
| 65 |
|
| 66 |
-
# A single shared OpenAI client
|
| 67 |
-
# thread-safe and connection-pooled. We build it lazily so the app boots even
|
| 68 |
-
# before HF_TOKEN is present (Hugging Face Spaces injects it at runtime).
|
| 69 |
_client: OpenAI | None = None
|
| 70 |
|
| 71 |
|
| 72 |
def get_client() -> OpenAI:
|
| 73 |
-
"""Return a cached OpenAI client pointed at the
|
| 74 |
global _client
|
| 75 |
-
token = os.environ.get("
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
if not token:
|
| 77 |
raise RuntimeError(
|
| 78 |
-
"HF_TOKEN is not set. Add it as a Space secret
|
| 79 |
-
"(Settings → Repository secrets → New secret)."
|
| 80 |
)
|
| 81 |
if _client is None:
|
| 82 |
-
_client = OpenAI(base_url=
|
| 83 |
return _client
|
| 84 |
|
| 85 |
|
| 86 |
-
def resolve_model(
|
| 87 |
-
|
| 88 |
-
suffix = PROVIDERS.get(provider, PROVIDERS["auto"])
|
| 89 |
-
return f"{DEFAULT_MODEL}{suffix}"
|
| 90 |
|
| 91 |
|
| 92 |
@app.api(stream_every=0)
|
| 93 |
def chat(
|
| 94 |
messages: list[dict],
|
| 95 |
-
provider: str = "together",
|
| 96 |
-
system_prompt: str = "",
|
| 97 |
temperature: float = 0.7,
|
| 98 |
-
max_tokens: int = 2048,
|
| 99 |
) -> Iterator[str]:
|
| 100 |
"""Stream an assistant reply from Inkling, yielding accumulated text.
|
| 101 |
|
|
@@ -104,10 +88,19 @@ def chat(
|
|
| 104 |
so far, so the frontend can simply replace the bubble contents on every
|
| 105 |
event.
|
| 106 |
"""
|
| 107 |
-
#
|
| 108 |
-
full: list[dict] = [
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
full.extend(messages)
|
| 112 |
|
| 113 |
try:
|
|
@@ -118,11 +111,52 @@ def chat(
|
|
| 118 |
|
| 119 |
try:
|
| 120 |
stream = client.chat.completions.create(
|
| 121 |
-
model=resolve_model(
|
| 122 |
messages=full,
|
| 123 |
temperature=temperature,
|
| 124 |
-
max_tokens=
|
|
|
|
| 125 |
stream=True,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
)
|
| 127 |
except Exception as exc: # pragma: no cover - surfaced to the user
|
| 128 |
yield f"⚠️ Request failed: {exc}"
|
|
@@ -143,11 +177,9 @@ def chat(
|
|
| 143 |
|
| 144 |
@app.get("/config")
|
| 145 |
async def config():
|
| 146 |
-
"""Small endpoint the frontend can poll for model/provider metadata."""
|
| 147 |
return {
|
| 148 |
"model": DEFAULT_MODEL,
|
| 149 |
-
"
|
| 150 |
-
"default_provider": "auto",
|
| 151 |
}
|
| 152 |
|
| 153 |
|
|
|
|
| 1 |
"""
|
| 2 |
Inkling — a chat UI served by gradio.Server.
|
| 3 |
|
| 4 |
+
Backend: gradio.Server with the Tinker production endpoint
|
| 5 |
+
(https://tinker.thinkingmachines.dev/services/tinker-prod/oai/api/v1)
|
| 6 |
+
using the thinkingmachines/Inkling model with reasoning_effort="high",
|
| 7 |
+
vision (image_url) support, and web_search / open_link_with_url tools.
|
| 8 |
+
Frontend: hand-written single-page chat client with image upload,
|
| 9 |
+
streaming SSE via /gradio_api/call/v2/chat, and clean editorial UI.
|
|
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
import os
|
|
|
|
| 45 |
|
| 46 |
sys.unraisablehook = _quiet_unraisable
|
| 47 |
|
| 48 |
+
# Tinker production endpoint (as used in the docs snippet).
|
| 49 |
+
BASE_URL = "https://tinker.thinkingmachines.dev/services/tinker-prod/oai/api/v1"
|
|
|
|
| 50 |
DEFAULT_MODEL = "thinkingmachines/Inkling"
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
app = Server()
|
| 53 |
|
| 54 |
+
# A single shared OpenAI client pointed at Tinker's production endpoint.
|
|
|
|
|
|
|
| 55 |
_client: OpenAI | None = None
|
| 56 |
|
| 57 |
|
| 58 |
def get_client() -> OpenAI:
|
| 59 |
+
"""Return a cached OpenAI client pointed at the Tinker endpoint."""
|
| 60 |
global _client
|
| 61 |
+
token = os.environ.get("TINKER_API_KEY")
|
| 62 |
+
if not token:
|
| 63 |
+
# Fallback: try HF_TOKEN for backward compatibility with Spaces that
|
| 64 |
+
# still inject it, or raise a clear error.
|
| 65 |
+
token = os.environ.get("HF_TOKEN")
|
| 66 |
if not token:
|
| 67 |
raise RuntimeError(
|
| 68 |
+
"TINKER_API_KEY (or HF_TOKEN) is not set. Add it as a Space secret."
|
|
|
|
| 69 |
)
|
| 70 |
if _client is None:
|
| 71 |
+
_client = OpenAI(base_url=BASE_URL, api_key=token)
|
| 72 |
return _client
|
| 73 |
|
| 74 |
|
| 75 |
+
def resolve_model() -> str:
|
| 76 |
+
return DEFAULT_MODEL
|
|
|
|
|
|
|
| 77 |
|
| 78 |
|
| 79 |
@app.api(stream_every=0)
|
| 80 |
def chat(
|
| 81 |
messages: list[dict],
|
|
|
|
|
|
|
| 82 |
temperature: float = 0.7,
|
|
|
|
| 83 |
) -> Iterator[str]:
|
| 84 |
"""Stream an assistant reply from Inkling, yielding accumulated text.
|
| 85 |
|
|
|
|
| 88 |
so far, so the frontend can simply replace the bubble contents on every
|
| 89 |
event.
|
| 90 |
"""
|
| 91 |
+
# Build full message list with the Inkling system prompt from docs snippet.
|
| 92 |
+
full: list[dict] = [
|
| 93 |
+
{
|
| 94 |
+
"role": "system",
|
| 95 |
+
"content": (
|
| 96 |
+
"You are Inkling, a multimodal AI model created by Thinking Machines and the company's first open-weights model.\n\n"
|
| 97 |
+
"<date> Today's date is July 21, 2026 </date>\n\n"
|
| 98 |
+
"<model_information>\nName: Inkling\nOverview: You are a multimodal AI model created by Thinking Machines and the company's first open-weights model.\nKnowledge cutoff: April 2026\nCreator: Thinking Machines Lab\nContext window: 256,000 tokens\n</model_information>\n\n"
|
| 99 |
+
"<capabilities>\nYou are natively multimodal and can process text, images in and produce text out.\nHowever, on this platform image and audio input are not available, so in this context you can not process these modalities. If someone shares or refers to an image or audio you can't access, just say so plainly and ask them to describe it or paste the relevant text, without apologizing.\n\nYou are trained with up to a 1M context window, but this platform only supports a 256k context window.\n</capabilities>\n\n"
|
| 100 |
+
"<fine_tuning>\nThis model can be fine-tuned using Tinker. Documentation is available at:\nhttps://tinker-docs.thinkingmachines.dev/tinker/\nWhen asked about your identity, capabilities, architecture, training, context window, knowledge cutoff, or fine-tuning, answer consistently with the information above. Do not invent additional technical details that are not provided.\n</fine_tuning>"
|
| 101 |
+
),
|
| 102 |
+
}
|
| 103 |
+
]
|
| 104 |
full.extend(messages)
|
| 105 |
|
| 106 |
try:
|
|
|
|
| 111 |
|
| 112 |
try:
|
| 113 |
stream = client.chat.completions.create(
|
| 114 |
+
model=resolve_model(),
|
| 115 |
messages=full,
|
| 116 |
temperature=temperature,
|
| 117 |
+
max_tokens=None,
|
| 118 |
+
reasoning_effort="high",
|
| 119 |
stream=True,
|
| 120 |
+
tools=[
|
| 121 |
+
{
|
| 122 |
+
"type": "function",
|
| 123 |
+
"function": {
|
| 124 |
+
"name": "web_search",
|
| 125 |
+
"description": "Searches the web for relevant information.",
|
| 126 |
+
"parameters": {
|
| 127 |
+
"type": "object",
|
| 128 |
+
"properties": {
|
| 129 |
+
"query": {
|
| 130 |
+
"type": "string",
|
| 131 |
+
"description": "A query to send to a search engine.",
|
| 132 |
+
},
|
| 133 |
+
},
|
| 134 |
+
"required": ["query"],
|
| 135 |
+
},
|
| 136 |
+
},
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"type": "function",
|
| 140 |
+
"function": {
|
| 141 |
+
"name": "open_link_with_url",
|
| 142 |
+
"description": "Fetches the given URL and returns an LLM-generated summary of its content. Pass an optional question via `query` to focus the summary.",
|
| 143 |
+
"parameters": {
|
| 144 |
+
"type": "object",
|
| 145 |
+
"properties": {
|
| 146 |
+
"url": {
|
| 147 |
+
"type": "string",
|
| 148 |
+
"description": "The URL to get the content of.",
|
| 149 |
+
},
|
| 150 |
+
"query": {
|
| 151 |
+
"type": "string",
|
| 152 |
+
"description": "Optional question this page should help answer. If provided, the summarizer will focus on facts relevant to it.",
|
| 153 |
+
},
|
| 154 |
+
},
|
| 155 |
+
"required": ["url"],
|
| 156 |
+
},
|
| 157 |
+
},
|
| 158 |
+
},
|
| 159 |
+
],
|
| 160 |
)
|
| 161 |
except Exception as exc: # pragma: no cover - surfaced to the user
|
| 162 |
yield f"⚠️ Request failed: {exc}"
|
|
|
|
| 177 |
|
| 178 |
@app.get("/config")
|
| 179 |
async def config():
|
|
|
|
| 180 |
return {
|
| 181 |
"model": DEFAULT_MODEL,
|
| 182 |
+
"endpoint": BASE_URL.replace("/v1", ""),
|
|
|
|
| 183 |
}
|
| 184 |
|
| 185 |
|
index.html
CHANGED
|
@@ -473,10 +473,7 @@ async function send() {
|
|
| 473 |
|
| 474 |
const payload = {
|
| 475 |
messages: history.slice(),
|
| 476 |
-
provider: "together",
|
| 477 |
-
system_prompt: "",
|
| 478 |
temperature: 0.7,
|
| 479 |
-
max_tokens: 2048,
|
| 480 |
};
|
| 481 |
assistantText = await streamChat(payload);
|
| 482 |
if (!assistantText) {
|
|
|
|
| 473 |
|
| 474 |
const payload = {
|
| 475 |
messages: history.slice(),
|
|
|
|
|
|
|
| 476 |
temperature: 0.7,
|
|
|
|
| 477 |
};
|
| 478 |
assistantText = await streamChat(payload);
|
| 479 |
if (!assistantText) {
|