You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Diffbot LLM endpoint supports response_format, but the client had no way
to pass it: the payload in ask.py was hardcoded to model/messages/stream.
Adds response_format to ask() and a new ask_json() that returns a parsed
object, both sync and async, plus json_schema_format() to build the payload.
The CLI gains --schema, and --json now uses real structured output instead of
nudging the prompt and slicing between the first { and last }.
Server-side (see ../diffbot-llm) the schema is compiled to an EBNF grammar and
the final answer is constrained to it, so this is enforced decoding rather than
a request the model may ignore. Two rough edges are handled client-side:
- The schema must be nested at json_schema.schema. Anywhere else and the server
returns 200 with the constraint silently dropped, so we reject that locally.
- The grammar permits a <think> block before the final answer, so ask_json
strips think blocks and markdown fences before parsing.
ask_json without a schema deliberately sends a permissive {"type": "object"}
json_schema rather than {"type": "json_object"}. json_object gets no grammar
applied, and the RAG loop's internal tool call is itself a valid JSON object,
so it can be returned as the final answer -- reproducible with a system message
on some queries (5/5 on one, 0/12 without a system message). A guard raises
ValidationError if a tool call ever comes back.
Tested with 44 unit tests and 7 live tests against the real endpoint, covering
flat and nested schemas, streaming with a schema, async, and a regression test
for the system-message case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+62Lines changed: 62 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,6 +75,42 @@ for chunk in db.ask([{"role": "user", "content": "What's the capital of France?"
75
75
print(chunk, end="")
76
76
```
77
77
78
+
### Structured output
79
+
Pass a JSON Schema to constrain the answer. The model is held to the schema by a grammar during decoding, so the result parses reliably even though the answer is retrieved live from the web.
80
+
81
+
```python
82
+
from diffbot import Diffbot
83
+
84
+
db = Diffbot(token="YOUR_TOKEN")
85
+
schema = {
86
+
"type": "object",
87
+
"properties": {
88
+
"country": {"type": "string"},
89
+
"capital": {"type": "string"},
90
+
},
91
+
"required": ["country", "capital"],
92
+
}
93
+
answer = db.ask_json([{"role": "user", "content": "What's the capital of France?"}], schema)
94
+
print(answer["capital"])
95
+
```
96
+
97
+
Omit the schema to let the model choose the shape, or use `ask` with `response_format` to stream a constrained answer:
98
+
99
+
```python
100
+
from diffbot import Diffbot, json_schema_format
101
+
102
+
db = Diffbot(token="YOUR_TOKEN")
103
+
answer = db.ask_json([{"role": "user", "content": "What's the capital of France?"}])
104
+
105
+
for chunk in db.ask(
106
+
[{"role": "user", "content": "What's the capital of France?"}],
107
+
response_format=json_schema_format(schema),
108
+
):
109
+
print(chunk, end="")
110
+
```
111
+
112
+
> **Avoid `response_format={"type": "json_object"}`.** The endpoint accepts it, but applies no grammar to it — the RAG loop's internal tool call is itself a JSON object, so it can be returned as the final answer. This is reproducible whenever the request includes a system message. `ask_json` therefore defaults to a permissive JSON Schema instead, and raises `ValidationError` if it ever sees a tool call come back.
113
+
78
114
### Crawl a site for structured content
79
115
```python
80
116
from diffbot import Diffbot
@@ -142,6 +178,30 @@ async def main():
142
178
asyncio.run(main())
143
179
```
144
180
181
+
### Structured output
182
+
```python
183
+
import asyncio
184
+
from diffbot import DiffbotAsync
185
+
186
+
schema = {
187
+
"type": "object",
188
+
"properties": {
189
+
"country": {"type": "string"},
190
+
"capital": {"type": "string"},
191
+
},
192
+
"required": ["country", "capital"],
193
+
}
194
+
195
+
asyncdefmain():
196
+
asyncwith DiffbotAsync(token="YOUR_TOKEN") as db:
197
+
answer =await db.ask_json(
198
+
[{"role": "user", "content": "What's the capital of France?"}], schema
0 commit comments