akhaliq HF Staff Claude commited on
Commit
4eb5082
·
1 Parent(s): d2ccf03

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>

Files changed (2) hide show
  1. app.py +76 -44
  2. 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 (FastAPI + Gradio's queue/concurrency engine).
5
- Model: thinkingmachines/Inkling via Hugging Face Inference Providers,
6
- reached through the OpenAI-compatible router at
7
- https://router.huggingface.co/v1.
8
- Frontend: a hand-written single-page chat client (index.html) that talks to
9
- the backend through the Gradio JS client, so every request goes
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
- # Hugging Face's OpenAI-compatible router. The ":<provider>" suffix on the
50
- # model name tells the router how to route the request.
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 is fine the underlying httpx client is
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 HF router."""
74
  global _client
75
- token = os.environ.get("HF_TOKEN")
 
 
 
 
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=ROUTER_BASE_URL, api_key=token)
83
  return _client
84
 
85
 
86
- def resolve_model(provider: str) -> str:
87
- """Turn a short provider key into a fully-suffixed model id."""
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
- # Compose the final message list, prepending a system prompt if given.
108
- full: list[dict] = []
109
- if system_prompt and system_prompt.strip():
110
- full.append({"role": "system", "content": system_prompt.strip()})
 
 
 
 
 
 
 
 
 
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(provider),
122
  messages=full,
123
  temperature=temperature,
124
- max_tokens=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
- "providers": list(PROVIDERS.keys()),
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) {