Skip to content

Commit d0bc53a

Browse files
authored
docs[patch]: Update structured output docs to be more opinionated (#5968)
* Update structured output docs to be more opinionated * Typo * Fix
1 parent 2de5cd7 commit d0bc53a

File tree

2 files changed

+163
-29
lines changed

2 files changed

+163
-29
lines changed

docs/core_docs/docs/concepts.mdx

+50-7
Original file line numberDiff line numberDiff line change
@@ -821,14 +821,56 @@ a few ways to get structured output from models in LangChain.
821821

822822
#### `.withStructuredOutput()`
823823

824-
For convenience, some LangChain chat models support a `.withStructuredOutput()` method.
824+
For convenience, some LangChain chat models support a [`.withStructuredOutput()`](/docs/how_to/structured_output/#the-.withstructuredoutput-method) method.
825825
This method only requires a schema as input, and returns an object matching the requested schema.
826826
Generally, this method is only present on models that support one of the more advanced methods described below,
827827
and will use one of them under the hood. It takes care of importing a suitable output parser and
828828
formatting the schema in the right format for the model.
829829

830+
Here's an example:
831+
832+
```ts
833+
import { z } from "zod";
834+
835+
const joke = z.object({
836+
setup: z.string().describe("The setup of the joke"),
837+
punchline: z.string().describe("The punchline to the joke"),
838+
rating: z.number().optional().describe("How funny the joke is, from 1 to 10"),
839+
});
840+
841+
// Can also pass in JSON schema.
842+
// It's also beneficial to pass in an additional "name" parameter to give the
843+
// model more context around the type of output to generate.
844+
const structuredLlm = model.withStructuredOutput(joke);
845+
846+
await structuredLlm.invoke("Tell me a joke about cats");
847+
```
848+
849+
```
850+
{
851+
setup: "Why don't cats play poker in the wild?",
852+
punchline: "Too many cheetahs.",
853+
rating: 7
854+
}
855+
```
856+
857+
We recommend this method as a starting point when working with structured output:
858+
859+
- It uses other model-specific features under the hood, without the need to import an output parser.
860+
- For the models that use tool calling, no special prompting is needed.
861+
- If multiple underlying techniques are supported, you can supply a `method` parameter to
862+
[toggle which one is used](/docs/how_to/structured_output/#specifying-the-output-method-advanced).
863+
864+
You may want or need to use other techiniques if:
865+
866+
- The chat model you are using does not support tool calling.
867+
- You are working with very complex schemas and the model is having trouble generating outputs that conform.
868+
830869
For more information, check out this [how-to guide](/docs/how_to/structured_output/#the-.withstructuredoutput-method).
831870

871+
You can also check out [this table](/docs/integrations/chat/) for a list of models that support
872+
`.withStructuredOutput()`.
873+
832874
#### Raw prompting
833875

834876
The most intuitive way to get a model to structure output is to ask nicely.
@@ -851,9 +893,8 @@ However, there are some drawbacks too:
851893
Some may be better at interpreting [JSON schema](https://json-schema.org/), others may be best with TypeScript definitions,
852894
and still others may prefer XML.
853895

854-
While we'll next go over some ways that you can take advantage of features offered by
855-
model providers to increase reliability, prompting techniques remain important for tuning your
856-
results no matter what method you choose.
896+
While features offered by model providers may increase reliability, prompting techniques remain important for tuning your
897+
results no matter which method you choose.
857898

858899
#### JSON mode
859900

@@ -864,10 +905,12 @@ Some models, such as [Mistral](/docs/integrations/chat/mistral/), [OpenAI](/docs
864905
support a feature called **JSON mode**, usually enabled via config.
865906

866907
When enabled, JSON mode will constrain the model's output to always be some sort of valid JSON.
867-
Often they require some custom prompting, but it's usually much less burdensome and along the lines of,
868-
`"you must always return JSON"`, and the [output is easier to parse](/docs/how_to/output_parser_json/).
908+
Often they require some custom prompting, but it's usually much less burdensome than completely raw prompting and
909+
more along the lines of,
910+
`"you must always return JSON"`. The [output also is generally easier to parse](/docs/how_to/output_parser_json/).
869911

870-
It's also generally simpler and more commonly available than tool calling.
912+
It's also generally simpler to use directly and more commonly available than tool calling, and can give
913+
more flexibility around prompting and shaping results than tool calling.
871914

872915
Here's an example:
873916

docs/core_docs/docs/how_to/structured_output.ipynb

+113-22
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@
7070
"import { z } from \"zod\";\n",
7171
"\n",
7272
"const joke = z.object({\n",
73-
" setup: z.string().describe(\"The setup of the joke\"),\n",
74-
" punchline: z.string().describe(\"The punchline to the joke\"),\n",
75-
" rating: z.number().optional().describe(\"How funny the joke is, from 1 to 10\"),\n",
73+
" setup: z.string().describe(\"The setup of the joke\"),\n",
74+
" punchline: z.string().describe(\"The punchline to the joke\"),\n",
75+
" rating: z.number().optional().describe(\"How funny the joke is, from 1 to 10\"),\n",
7676
"});\n",
7777
"\n",
7878
"const structuredLlm = model.withStructuredOutput(joke);\n",
@@ -153,22 +153,22 @@
153153
],
154154
"source": [
155155
"const structuredLlm = model.withStructuredOutput(\n",
156-
" {\n",
157-
" \"name\": \"joke\",\n",
158-
" \"description\": \"Joke to tell user.\",\n",
159-
" \"parameters\": {\n",
160-
" \"title\": \"Joke\",\n",
161-
" \"type\": \"object\",\n",
162-
" \"properties\": {\n",
163-
" \"setup\": {\"type\": \"string\", \"description\": \"The setup for the joke\"},\n",
164-
" \"punchline\": {\"type\": \"string\", \"description\": \"The joke's punchline\"},\n",
165-
" },\n",
166-
" \"required\": [\"setup\", \"punchline\"],\n",
167-
" },\n",
168-
" }\n",
156+
" {\n",
157+
" \"name\": \"joke\",\n",
158+
" \"description\": \"Joke to tell user.\",\n",
159+
" \"parameters\": {\n",
160+
" \"title\": \"Joke\",\n",
161+
" \"type\": \"object\",\n",
162+
" \"properties\": {\n",
163+
" \"setup\": {\"type\": \"string\", \"description\": \"The setup for the joke\"},\n",
164+
" \"punchline\": {\"type\": \"string\", \"description\": \"The joke's punchline\"},\n",
165+
" },\n",
166+
" \"required\": [\"setup\", \"punchline\"],\n",
167+
" },\n",
168+
" }\n",
169169
")\n",
170170
"\n",
171-
"await structuredLlm.invoke(\"Tell me a joke about cats\")"
171+
"await structuredLlm.invoke(\"Tell me a joke about cats\", { name: \"joke\" })"
172172
]
173173
},
174174
{
@@ -213,24 +213,115 @@
213213
],
214214
"source": [
215215
"const structuredLlm = model.withStructuredOutput(joke, {\n",
216-
" method: \"json_mode\",\n",
217-
" name: \"joke\",\n",
216+
" method: \"json_mode\",\n",
217+
" name: \"joke\",\n",
218218
"})\n",
219219
"\n",
220220
"await structuredLlm.invoke(\n",
221-
" \"Tell me a joke about cats, respond in JSON with `setup` and `punchline` keys\"\n",
221+
" \"Tell me a joke about cats, respond in JSON with `setup` and `punchline` keys\"\n",
222222
")"
223223
]
224224
},
225225
{
226226
"cell_type": "markdown",
227-
"id": "5e92a98a",
227+
"id": "56278a82",
228228
"metadata": {},
229229
"source": [
230230
"In the above example, we use OpenAI's alternate JSON mode capability along with a more specific prompt.\n",
231231
"\n",
232-
"For specifics about the model you choose, peruse its entry in the [API reference pages](https://v02.api.js.langchain.com/).\n",
232+
"For specifics about the model you choose, peruse its entry in the [API reference pages](https://api.js.langchain.com/).\n",
233233
"\n",
234+
"### (Advanced) Raw outputs\n",
235+
"\n",
236+
"LLMs aren't perfect at generating structured output, especially as schemas become complex. You can avoid raising exceptions and handle the raw output yourself by passing `includeRaw: true`. This changes the output format to contain the raw message output and the `parsed` value (if successful):"
237+
]
238+
},
239+
{
240+
"cell_type": "code",
241+
"execution_count": 2,
242+
"id": "46b616a4",
243+
"metadata": {},
244+
"outputs": [
245+
{
246+
"data": {
247+
"text/plain": [
248+
"{\n",
249+
" raw: AIMessage {\n",
250+
" lc_serializable: \u001b[33mtrue\u001b[39m,\n",
251+
" lc_kwargs: {\n",
252+
" content: \u001b[32m\"\"\u001b[39m,\n",
253+
" tool_calls: [\n",
254+
" {\n",
255+
" name: \u001b[32m\"joke\"\u001b[39m,\n",
256+
" args: \u001b[36m[Object]\u001b[39m,\n",
257+
" id: \u001b[32m\"call_0pEdltlfSXjq20RaBFKSQOeF\"\u001b[39m\n",
258+
" }\n",
259+
" ],\n",
260+
" invalid_tool_calls: [],\n",
261+
" additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: [ \u001b[36m[Object]\u001b[39m ] },\n",
262+
" response_metadata: {}\n",
263+
" },\n",
264+
" lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n",
265+
" content: \u001b[32m\"\"\u001b[39m,\n",
266+
" name: \u001b[90mundefined\u001b[39m,\n",
267+
" additional_kwargs: {\n",
268+
" function_call: \u001b[90mundefined\u001b[39m,\n",
269+
" tool_calls: [\n",
270+
" {\n",
271+
" id: \u001b[32m\"call_0pEdltlfSXjq20RaBFKSQOeF\"\u001b[39m,\n",
272+
" type: \u001b[32m\"function\"\u001b[39m,\n",
273+
" function: \u001b[36m[Object]\u001b[39m\n",
274+
" }\n",
275+
" ]\n",
276+
" },\n",
277+
" response_metadata: {\n",
278+
" tokenUsage: { completionTokens: \u001b[33m33\u001b[39m, promptTokens: \u001b[33m88\u001b[39m, totalTokens: \u001b[33m121\u001b[39m },\n",
279+
" finish_reason: \u001b[32m\"stop\"\u001b[39m\n",
280+
" },\n",
281+
" tool_calls: [\n",
282+
" {\n",
283+
" name: \u001b[32m\"joke\"\u001b[39m,\n",
284+
" args: {\n",
285+
" setup: \u001b[32m\"Why was the cat sitting on the computer?\"\u001b[39m,\n",
286+
" punchline: \u001b[32m\"Because it wanted to keep an eye on the mouse!\"\u001b[39m,\n",
287+
" rating: \u001b[33m7\u001b[39m\n",
288+
" },\n",
289+
" id: \u001b[32m\"call_0pEdltlfSXjq20RaBFKSQOeF\"\u001b[39m\n",
290+
" }\n",
291+
" ],\n",
292+
" invalid_tool_calls: [],\n",
293+
" usage_metadata: { input_tokens: \u001b[33m88\u001b[39m, output_tokens: \u001b[33m33\u001b[39m, total_tokens: \u001b[33m121\u001b[39m }\n",
294+
" },\n",
295+
" parsed: {\n",
296+
" setup: \u001b[32m\"Why was the cat sitting on the computer?\"\u001b[39m,\n",
297+
" punchline: \u001b[32m\"Because it wanted to keep an eye on the mouse!\"\u001b[39m,\n",
298+
" rating: \u001b[33m7\u001b[39m\n",
299+
" }\n",
300+
"}"
301+
]
302+
},
303+
"execution_count": 2,
304+
"metadata": {},
305+
"output_type": "execute_result"
306+
}
307+
],
308+
"source": [
309+
"const joke = z.object({\n",
310+
" setup: z.string().describe(\"The setup of the joke\"),\n",
311+
" punchline: z.string().describe(\"The punchline to the joke\"),\n",
312+
" rating: z.number().optional().describe(\"How funny the joke is, from 1 to 10\"),\n",
313+
"});\n",
314+
"\n",
315+
"const structuredLlm = model.withStructuredOutput(joke, { includeRaw: true, name: \"joke\" });\n",
316+
"\n",
317+
"await structuredLlm.invoke(\"Tell me a joke about cats\");"
318+
]
319+
},
320+
{
321+
"cell_type": "markdown",
322+
"id": "5e92a98a",
323+
"metadata": {},
324+
"source": [
234325
"## Prompting techniques\n",
235326
"\n",
236327
"You can also prompt models to outputting information in a given format. This approach relies on designing good prompts and then parsing the output of the models. This is the only option for models that don't support `.with_structured_output()` or other built-in approaches.\n",

0 commit comments

Comments
 (0)