From 5502eab28ae4111e71cb16e07a87783d502a13d6 Mon Sep 17 00:00:00 2001 From: dannyjameswilliams Date: Mon, 22 Jun 2026 15:57:20 +0100 Subject: [PATCH 1/6] docs: add new structured output section --- .../_includes/code/structured_outputs.mts | 108 +++++ .../_includes/code/structured_outputs.py | 102 ++++ docs/query-agent/guides/ask_mode.md | 21 +- docs/query-agent/reference/index.md | 2 +- .../reference/structured_outputs.md | 440 ++++++++++++++++++ sidebars.js | 5 + 6 files changed, 674 insertions(+), 4 deletions(-) create mode 100644 docs/query-agent/_includes/code/structured_outputs.mts create mode 100644 docs/query-agent/_includes/code/structured_outputs.py create mode 100644 docs/query-agent/reference/structured_outputs.md diff --git a/docs/query-agent/_includes/code/structured_outputs.mts b/docs/query-agent/_includes/code/structured_outputs.mts new file mode 100644 index 00000000..40e96982 --- /dev/null +++ b/docs/query-agent/_includes/code/structured_outputs.mts @@ -0,0 +1,108 @@ +import 'dotenv/config' +const { loadClientInternally } = await import('./util.mjs').catch(() => import('../docs/query-agent/_includes/code/util.mjs')); + +const client = await loadClientInternally(); + + +// START SOInstantiate +import { QueryAgent } from 'weaviate-agents'; + +const qa = new QueryAgent(client, { collections: ['FinancialContracts'] }); +// END SOInstantiate + + +// START SOBasicExampleBaseModel +import { z } from 'zod'; + +const ContractSummary = z.object({ + contract_id: z.string(), + contract_title: z.string(), + auto_renew: z.boolean(), + parties_involved: z.array(z.string()), + requires_action: z.boolean(), +}); + +const res = await qa.ask( + "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", + { outputFormat: ContractSummary } +); + +console.log(res.finalAnswerParsed); +// END SOBasicExampleBaseModel + + +// START SOBasicDictExample +const res2 = await qa.ask( + "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", + { + outputFormat: { + type: "object", + properties: { + contract_id: { title: "Contract Id", type: "string" }, + contract_title: { title: "Contract Title", type: "string" }, + auto_renew: { title: "Auto Renew", type: "boolean" }, + parties_involved: { items: { type: "string" }, title: "Parties Involved", type: "array" }, + requires_action: { title: "Requires Action", type: "boolean" }, + }, + required: ["contract_id", "contract_title", "auto_renew", "parties_involved", "requires_action"], + title: "ContractSummary", + additionalProperties: false, + }, + } +); + +console.log(res2.finalAnswerParsed); +// END SOBasicDictExample + + +// START SOReasoningExample +const FinalAnswer = z.object({ + reasoning: z.string(), + final_answer: z.string(), +}); + +const res3 = await qa.ask("What is the most recent contract about AI?", { outputFormat: FinalAnswer }); + +console.log(res3.finalAnswerParsed); +// END SOReasoningExample + + +// START SONestedExampleBaseModel +const ContractInfo = z.object({ + names_mentioned: z.array(z.string()).describe("All names within the contract text"), + contract_type: z.enum(["sales", "purchase", "other"]).describe("Determine the type of contract"), + summary: z.string().describe("Provide a brief summary of the contract."), + contract_uuid: z.uuid(), +}); + +const ContractInfoResponse = z.object({ + contract_infos: z.array(ContractInfo), + overall_summary: z.string(), +}); + +const res4 = await qa.ask("Find and return all contracts about AI in 2023", { outputFormat: ContractInfoResponse }); + +console.log(res4.finalAnswerParsed); +// END SONestedExampleBaseModel + + +// START SOCitationExample +const CitedText = z.object({ + sentence: z.string().describe("A single sentence from your answer, to be combined with other sentences"), + sources: z.array(z.uuid()).describe("The UUIDs of the sources that support the sentence"), +}); + +const CitedAnswer = z.object({ + reasoning: z.string(), + final_answer: z.array(CitedText).describe( + "A list of cited sentences, that will combine together in a paragraph to be a full answer" + ), +}); + +const res5 = await qa.ask("What is the most recent contract about AI?", { outputFormat: CitedAnswer }); + +console.log(res5.finalAnswerParsed); +// END SOCitationExample + + +await client.close(); diff --git a/docs/query-agent/_includes/code/structured_outputs.py b/docs/query-agent/_includes/code/structured_outputs.py new file mode 100644 index 00000000..80d7673f --- /dev/null +++ b/docs/query-agent/_includes/code/structured_outputs.py @@ -0,0 +1,102 @@ +import sys +sys.path.insert(0, "docs/query-agent/_includes/code") +from util import load_client_internally + +client = load_client_internally() + + +# START SOInstantiate +from weaviate.agents.query import QueryAgent + +qa = QueryAgent(client=client, collections=["FinancialContracts"]) +# END SOInstantiate + + +# START SOBasicExampleBaseModel +from pydantic import BaseModel + +class ContractSummary(BaseModel): + contract_id: str + contract_title: str + auto_renew: bool + parties_involved: list[str] + requires_action: bool + +res = qa.ask( + "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", + output_format=ContractSummary, +) + +print(res.final_answer_parsed) +# END SOBasicExampleBaseModel + +# START SOBasicDictExample +res = qa.ask( + "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", + output_format={ + 'properties': { + 'contract_id': {'title': 'Contract Id', 'type': 'string'}, + 'contract_title': {'title': 'Contract Title', 'type': 'string'}, + 'auto_renew': {'title': 'Auto Renew', 'type': 'boolean'}, + 'parties_involved': {'items': {'type': 'string'}, 'title': 'Parties Involved', 'type': 'array'}, + 'requires_action': {'title': 'Requires Action', 'type': 'boolean'} + }, + 'required': ['contract_id', 'contract_title', 'auto_renew', 'parties_involved', 'requires_action'], + 'title': 'ContractSummary', + 'type': 'object' + } +) + +print(res.final_answer_parsed) +# END SOBasicDictExample + +# START SOReasoningExample +from pydantic import BaseModel + +class FinalAnswer(BaseModel): + reasoning: str + final_answer: str + +res = qa.ask("What is the most recent contract about AI?", output_format=FinalAnswer) + +print(res.final_answer_parsed) +# END SOReasoningExample + +# START SONestedExampleBaseModel +from pydantic import BaseModel, Field +from uuid import UUID +from typing import Literal + +class ContractInfo(BaseModel): + names_mentioned: list[str] = Field(description="All names within the contract text") + contract_type: Literal["sales", "purchase", "other"] = Field(description="Determine the type of contract") + summary: str = Field(description="Provide a brief summary of the contract.") + contract_uuid: UUID + +class ContractInfoResponse(BaseModel): + contract_infos: list[ContractInfo] + overall_summary: str + +res = qa.ask("Find and return all contracts about AI in 2023", output_format=ContractInfoResponse) + +print(res.final_answer_parsed) +# END SONestedExampleBaseModel + +# START SOCitationExample +from pydantic import BaseModel +from uuid import UUID + +class CitedText(BaseModel): + sentence: str = Field(description="A single sentence from your answer, to be combined with other sentences") + sources: list[UUID] = Field(description="The UUIDs of the sources that support the sentence") + +class CitedAnswer(BaseModel): + reasoning: str + final_answer: list[CitedText] = Field( + description="A list of cited sentences, that will combine together in a paragraph to be a full answer" + ) + +res = qa.ask("What is the most recent contract about AI?", output_format=CitedAnswer) + +print(res.final_answer_parsed) +# END SOCitationExample \ No newline at end of file diff --git a/docs/query-agent/guides/ask_mode.md b/docs/query-agent/guides/ask_mode.md index 9913257a..b16f6795 100644 --- a/docs/query-agent/guides/ask_mode.md +++ b/docs/query-agent/guides/ask_mode.md @@ -66,7 +66,8 @@ The `.ask()` method accepts several arguments: | --- | --- | --- | | `query` | `str \| list[ChatMessage]` | The user query you want the agent to answer. This can be a simple string (`"What is the highest-grossing product?"`) or a list of chat messages (for conversational context). [See the page on multi-turn conversations for more detail](../reference/multi_turn_conversations.md). | | `collections` | `list[str \| QueryAgentCollectionConfig] \| None` | The name(s) of the collections to search. You can pass one or many collection names as a list of strings (e.g., `["ECommerce", "BookSales"]`), or provide collection configuration objects for more control. If specified in the `ask` method, it will overwrite those defined in the instantiation of `QueryAgent`. [See the page on collection configuration for more detail](../reference/advanced_collections.md). | -| `result_evaluation` | `Literal["llm", "none"]` | Controls whether the agent will ask an LLM to "evaluate" (i.e., rewrite or rephrase) the result based on all retrieved context. Accepts either:
• `"none"` (default): faster and cheaper; where the final answer is the last LLM call and no further analysis is completed.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | +| `result_evaluation` | `Literal["llm", "none"]` | Controls whether the agent will ask an LLM to "evaluate" the result based on all retrieved context. Accepts either:
• `"none"` (default): faster and cheaper; where the final answer is the last LLM call and no further analysis is completed.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | +| `output_format` | `dict \| type[BaseModel] \| None` | Optional schema for structured output in the final response. Modifies the `final_answer` field of the [response class](#response). See [the page on structured output for more details](../reference/structured_outputs.md). | @@ -74,8 +75,8 @@ The `.ask()` method accepts several arguments: | --- | --- | --- | | `query` | `string \| ChatMessage[]` | The user query you want the agent to answer. This can be a simple string (`"What is the highest-grossing product?"`) or a list of chat messages (for conversational context). [See the page on multi-turn conversations for more detail](../reference/multi_turn_conversations.md). | | `collections` | `(string \| QueryAgentCollectionConfig)[]` | The name(s) of the collections to search. You can pass one or many collection names as a list of strings (e.g., `["ECommerce", "BookSales"]`), or provide collection configuration objects for more control. [See the page on collection configuration for more detail](../reference/advanced_collections.md). If specified in the `ask` method, it will overwrite those defined in the instantiation of `QueryAgent`. | -| `resultEvaluation` | `"llm" \| "none"` | Controls whether the agent will ask an LLM to "evaluate" (i.e., rewrite or rephrase) the result based on all retrieved context. Accepts either:
• `"none"`: faster and cheaper; default setting where the final answer is the last LLM call.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | - +| `resultEvaluation` | `"llm" \| "none"` | Controls whether the agent will ask an LLM to "evaluate" the result based on all retrieved context. Accepts either:
• `"none"`: faster and cheaper; default setting where the final answer is the last LLM call.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | +| `outputFormat` | `ZodType \| object` | Optional schema for structured output in the final response. Pass a [Zod](https://zod.dev/) schema (parsed and validated) or a raw [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) object (parsed only). Modifies the `finalAnswer` field of the [response class](#response), exposing the typed result on `finalAnswerParsed`. See [the page on structured output for more details](../reference/structured_outputs.md). |
@@ -99,6 +100,13 @@ The `AskModeResponse` class has the following properties: | `final_answer` | `str` | A string comprising the LLM's final answer to the user query. | | `sources` | `list[Source] \| None` | A list of `Source` objects, which have an `object_id` property correlating to the UUID of the Weaviate object that was retrieved during the run. If `result_evaluation` is `"llm"`, these are subset to only those that are relevant to the `final_answer`. | +Additionally, there is another field if the `output_format` parameter on the call to ask mode (`qa.ask(..., output_format=...)`) was not `None`: +| Field | Type | Description | +| --- | --- | --- | +| `final_answer_parsed` | `` | The final response, conforming to the schema given in the `output_format`. | + +The type of `final_answer_parsed` is a `dict` if a dictionary was supplied to `output_format`, otherwise it will be the exact type of the `BaseModel` given. + [See the client documentation for more detail.](https://weaviate-python-client.readthedocs.io/en/latest/weaviate-agents-python-client/docs/weaviate_agents.classes.html#weaviate_agents.classes.AskModeResponse) @@ -115,6 +123,13 @@ The `AskModeResponse` class has the following properties: | `finalAnswer` | `string` | A string comprising the LLM's final answer to the user query. | | `sources` | `Source[]` | A list of `Source` objects, which have an `objectId` property correlating to the UUID of the Weaviate object that was retrieved during the run. If `resultEvaluation` is `"llm"`, these are subset to only those that are relevant to the `finalAnswer`. | +Additionally, there is another field if the `outputFormat` parameter on the call to ask mode (`qa.ask(..., { outputFormat: ... })`) was provided: +| Field | Type | Description | +| --- | --- | --- | +| `finalAnswerParsed` | `` | The final response, conforming to the schema given in `outputFormat`. | + +The type of `finalAnswerParsed` is `Record` if a raw JSON Schema object was supplied to `outputFormat`, otherwise it will be the inferred type of the Zod schema given (`z.infer`). + [See the client documentation for more detail.](https://weaviate.github.io/agents-typescript-client/types/AskModeResponse.html) diff --git a/docs/query-agent/reference/index.md b/docs/query-agent/reference/index.md index f121d088..e05e2459 100644 --- a/docs/query-agent/reference/index.md +++ b/docs/query-agent/reference/index.md @@ -12,7 +12,7 @@ See the different configuration options for the Query Agent and how you can cust * **[Multi-turn Conversations](./multi_turn_conversations.md)**: Learn how to include multiple turns of conversations in a message history instead of a single user query. * **[Additional Filters](./additional_filters.md)**: Define persistent filters that get added to every search the Query Agent performs. * **[Collection Configuration](./advanced_collections.md)**: Setup your collections with more advanced configurations, such as named vectors, multi-tenancy and additional filters. - +* **[Structured Outputs](./structured_outputs.md)**: Configure the format of the ask mode response to conform to a schema. ## Questions and feedback diff --git a/docs/query-agent/reference/structured_outputs.md b/docs/query-agent/reference/structured_outputs.md new file mode 100644 index 00000000..9e0ec058 --- /dev/null +++ b/docs/query-agent/reference/structured_outputs.md @@ -0,0 +1,440 @@ +--- +title: Structured Outputs +description: "Conform the final response to a particular schema." +image: og/docs/query-agent.png +# tags: ['agents', 'query-agent', 'configuration'] +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import FilteredTextBlock from '@site/src/components/Documentation/FilteredTextBlock'; +import PyCode from '!!raw-loader!/docs/query-agent/_includes/code/structured_outputs.py'; +import TSCode from '!!raw-loader!/docs/query-agent/_includes/code/structured_outputs.mts'; + + +Structured outputs ensure that the model's final response will adhere to a given schema. This means you can easily access aspects of a response which is customised to your use-case. + +## Basic Usage + + + + In Python, you can either provide a Pydantic `BaseModel` or a raw dictionary conforming to the [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) specification. + + In the below examples, we use structured outputs to generate a set of metadata associated with a single retrieved item. + + The structured output can be accessed by a new field, `final_answer_parsed`, which appears only when the `output_format` argument is not `None`. The raw string from the model can still be accessed at `final_answer`. + + ### Pydantic BaseModel + + +
+ Example output + ```python + ContractSummary( + contract_id='46', + contract_title='Employment Contract', + auto_renew=False, + parties_involved=['Weaviate', 'Mark Robson'], + requires_action=True + ) + ``` +
+ + + ### Dictionary + + +
+ Example output + ```python + { + 'contract_id': '46', + 'contract_title': 'Employment Contract', + 'auto_renew': False, + 'parties_involved': ['Weaviate', 'Mark Robson'], + 'requires_action': True + } + ``` +
+ +
+ + In JavaScript/TypeScript, you can either provide a [Zod](https://zod.dev/) schema or a raw object conforming to the [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) specification. + + In the below examples, we use structured outputs to generate a set of metadata associated with a single retrieved item. + + The structured output can be accessed by a new field, `finalAnswerParsed`, which appears only when the `outputFormat` argument is not null. The raw string from the model can still be accessed at `finalAnswer`. + + ### Zod schema + +
+ Example output + ```typescript + { + contract_id: '46', + contract_title: 'Employment Contract', + auto_renew: false, + parties_involved: [ 'Weaviate', 'Mark Robson' ], + requires_action: true + } + ``` +
+ + ### Object + +
+ Example output + ```typescript + { + contract_id: '46.0', + contract_title: 'Employment Contract', + auto_renew: false, + parties_involved: [ 'Weaviate', 'Mark Robson' ], + requires_action: true + } + ``` +
+ +
+
+ +## Example: Reasoning + +As a basic example, consider adding an additional field `reasoning` to the response. Order is preserved in the specification, so if this is provided _before_ the answer field, the model will produce a reasoning string before writing its answer, which can provide explainability to a response. + + + + +
+ Example output + ```python + FinalAnswer( + reasoning='The most recent contract mentioning AI is the sales agreement dated 2024-03-15, but it does not mention AI in the text. The most recent contract that is actually about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft, which explicitly states it is for artificial intelligence research and development. No later AI-related contract is present in the provided data.', + final_answer='The most recent contract about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft. It is specifically for collaboration on artificial intelligence research and development. No more recent AI-related contract appears in the provided data.' + ) + ``` +
+
+ + +
+ Example output + ```typescript + { + reasoning: 'The most recent contract related to AI is the partnership agreement dated 2024-03-15 at 10:30 UTC. It specifically mentions collaboration on artificial intelligence research and development.', + final_answer: 'The most recent AI-related contract is a **Partnership Agreement** dated **2024-03-15 10:30 UTC** between **Weaviate** and **FictionalSoft**. It says the parties wish to establish a partnership to collaborate on **artificial intelligence research and development**. It has a **2-year term** and includes financial contributions of **$244.46 from Weaviate** and **$151.01 from FictionalSoft**, with profits split **50/50**.\n' + + '\n' + + 'If you want, I can also summarize the next most recent AI-related contract.' + } + ``` +
+
+
+ +## Example: Nested Schemas + +Nested schemas are supported, for example, you can define two schemas and have one reference the other, allowing more complex structured outputs to be crafted. + +In the below example, the final response will generate a list of information for each object that was retrieved, either extracted or generated from the content of the data, as well as providing an overall answer. + + + + +
+ Example output + ```python + ContractInfoResponse( + contract_infos=[ + ContractInfo( + names_mentioned=['Hans Zimmer', 'Weaviate', 'Mark Robson'], + contract_type='other', + summary='Loan agreement between Weaviate and Mark Robson for $342.00, dated 2023-03-15.', + contract_uuid=UUID('b2c4ffdc-411c-423a-9040-b7cbf5439bd0') + ), + ContractInfo( + names_mentioned=['John Smith', 'Weaviate'], + contract_type='other', + summary='Non-disclosure agreement between Weaviate and John Smith, dated 2022-03-15.', + contract_uuid=UUID('c126e3d3-db85-4a77-b49c-2a87fe48cd52') + ), + ContractInfo( + names_mentioned=['Johnathan Smith', 'Weaviate', 'John Smith'], + contract_type='other', + summary='Lease agreement for office space between Weaviate and John Smith, dated 2024-03-15.', + contract_uuid=UUID('6bbca4b9-fea1-4275-889a-1f5d4591ecbb') + ), + ContractInfo( + names_mentioned=['Arthur Penndragon', 'Weaviate', 'Mark Robson'], + contract_type='other', + summary='Loan agreement between Weaviate and Mark Robson for $620.41, dated 2023-03-15.', + contract_uuid=UUID('93213fd4-4c55-4a7c-a190-2e4c9d808566') + ), + ContractInfo( + names_mentioned=['Alice Johnson', 'Weaviate', 'Danny Williams'], + contract_type='other', + summary='Service agreement for digital marketing services between Weaviate and Danny Williams, dated + 2023-03-15, with total compensation of $961.89.', + contract_uuid=UUID('eed7d7f9-b06a-4d7b-b19a-f0f3782e49fd') + ), + ContractInfo( + names_mentioned=['Weaviate', 'John Smith'], + contract_type='other', + summary='Loan agreement between Weaviate and John Smith for $403.65, dated 2022-03-15.', + contract_uuid=UUID('5f75ec7e-ca7f-415c-9f5d-7d3a1518fad8') + ), + ContractInfo( + names_mentioned=['Hans Zimmer', 'Weaviate', 'Mark Robson'], + contract_type='sales', + summary='Sales agreement between Weaviate and Mark Robson for software licenses and support services, + dated 2023-04-24, with a total purchase price of $420.03.', + contract_uuid=UUID('174b1a9a-e9b2-4f48-960d-283a0fbbe3ab') + ), + ContractInfo( + names_mentioned=['John Williams', 'Weaviate', 'Mark Robson'], + contract_type='other', + summary='Service agreement between Weaviate and Mark Robson for consultation, software development, and + project management services, dated 2023-04-15, with total compensation of $744.35.', + contract_uuid=UUID('65848d82-e38e-4bed-b0bb-0c2830af8b27') + ), + ContractInfo( + names_mentioned=['John Williams', 'Weaviate'], + contract_type='other', + summary='Invoice from Weaviate to John Williams for data analysis, API integration, system maintenance, + technical support, and consultation services, dated 2023-10-15, totaling $873.17.', + contract_uuid=UUID('5b5544f9-7092-45d0-830d-dd211b0f3a70') + ), + ContractInfo( + names_mentioned=['Kaladin Stormblessed', 'Weaviate', 'John Smith'], + contract_type='other', + summary='Lease agreement between Weaviate and John Smith for office space, dated 2023-07-15.', + contract_uuid=UUID('f3f3730b-f3c4-4b2f-aa7a-1bc07b5814d8') + ), + ContractInfo( + names_mentioned=['Johnathan Smith', 'Weaviate', 'Mark Robson'], + contract_type='other', + summary='Non-disclosure agreement between Weaviate and Mark Robson, dated 2023-09-15.', + contract_uuid=UUID('3d4d802d-1820-4a5f-b8fe-6c5e189de1e6') + ), + ContractInfo( + names_mentioned=['John Williams', 'Weaviate', 'Danny Williams'], + contract_type='sales', + summary='Sales agreement between Weaviate and Danny Williams for products A, B, and C, dated + 2023-11-15, with a total purchase price of $270.68.', + contract_uuid=UUID('e645324e-14a4-4ca4-aeae-da146d55a3bb') + ), + ContractInfo( + names_mentioned=['John Williams', 'Weaviate', 'Mark Robson'], + contract_type='other', + summary='Invoice from Weaviate to Mark Robson dated 2023-04-15 for consultation, software development, + and project management services, totaling $744.35.', + contract_uuid=UUID('48a58d82-e38e-4bed-b0bb-0c2830af8b27') + ), + ContractInfo( + names_mentioned=['John Williams', 'Weaviate', 'John Smith'], + contract_type='other', + summary='Invoice from Weaviate to John Smith dated 2023-10-15 for consultation, development, and + additional charges, totaling $296.36.', + contract_uuid=UUID('03c0f5bb-f999-4b8b-86f3-271405de0037') + ), + ContractInfo( + names_mentioned=['Johnathan Smith', 'Weaviate', 'Danny Williams'], + contract_type='other', + summary='Service agreement between Weaviate and Danny Williams for software development, technical + support, and consultation services, dated 2023-03-15, with compensation of $273.86.', + contract_uuid=UUID('d68e20a9-4bd4-42d0-8986-d16425c3444a') + ) + ], + overall_summary='There are multiple 2023 contracts related to AI, including two partnership agreements about artificial intelligence research and development, plus other 2023 contracts mentioning AI-adjacent services. Some listed contracts are not directly about AI but were returned from the available matching set.' + ) + ``` +
+ A `Field` can be used to provide additional metadata, such as a `description`, or even constraints on numeric objects. A `Literal` can be used to constrain a field to produce only one of a few different objects. +
+ + + A `.describe()` call can be used to provide additional metadata, such as a description, or even constraints on numeric objects. A `z.enum()` can be used to constrain a field to produce only one of a few different objects. +
+ Example output + ```typescript + { + contract_infos: [ + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Partnership agreement to collaborate on artificial intelligence research and development, including shared contributions, responsibilities, and profit or revenue sharing. Date: 2023-03-15.', + contract_uuid: '583a63f9-f71b-4926-8075-2ade04a689c3' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Partnership agreement to collaborate on artificial intelligence projects and develop innovative AI solutions, with financial contributions, roles, and termination terms. Date: 2022-03-15.', + contract_uuid: '301d007b-53b4-4ce5-9913-a4b3f28fae2f' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Partnership agreement focused on artificial intelligence and machine learning collaboration, including resource contributions, revenue sharing, confidentiality, and termination terms. Date: 2023-03-15.', + contract_uuid: 'c2d4620b-db64-4b20-ac10-dd805d9b135d' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Partnership agreement to advance artificial intelligence technologies through combined resources and expertise, with defined contributions, responsibilities, and termination provisions. Date: 2023-03-15.', + contract_uuid: 'bbfd975d-85e8-47f1-acae-8d46ff028272' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Partnership agreement to collaborate on artificial intelligence projects, with project management, technical development, funding contributions, and termination terms. Date: 2023-03-15.', + contract_uuid: '6e2f283c-0e29-4daa-979f-800dffe476fb' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Service agreement for data analysis, cloud computing, software development, technical support, and consultation services. It includes compensation, confidentiality, and termination terms. Date: 2023-03-15 to 2023-09-15 depending on the agreement.', + contract_uuid: '8cab56ce-512e-47ef-9a38-5d5fa829444e' + }, + { + names_mentioned: [Array], + contract_type: 'other', + summary: 'Service agreement providing consulting on artificial intelligence and data management systems, including implementation, training, and technical support. Date: 2023-03-15.', + contract_uuid: '24541d4f-d84c-4cbe-b935-60cbc64c170e' + } + ], + overall_summary: 'The AI-related contracts available are partnership and service agreements from 2022–2023, with one purchase order and several invoices that mention AI or related services.' + } + ``` +
+
+
+ +## Example: Citations + +For a custom implementation of citing text (for example, if you want citations in-line), you could create a schema that iteratively builds a response from objects consisting of pairs of text and source IDs. + +:::note Supported Citations +The Query Agent natively supports subsetting and evaluating the quality of the response via the `result_evaluation` parameter in ask mode. [See here for more details](../guides/ask_mode.md#parameters) +::: + + + + +
+ Example output + ```python + CitedAnswer( + reasoning='The most recent contract mentioning AI is the partnership agreement dated 2024-03-15, which explicitly refers to AI-related work only in the contract text by implication? However, among the provided contracts, the latest one that clearly concerns AI is the partnership agreement from 2023-11-15, and another earlier partnership agreement from 2023-10-15 also mentions AI solutions. The 2024-03-15 sales agreement does not mention AI, so it is not relevant. The latest clearly AI-related contract in the data is the 2023-11-15 partnership agreement between Weaviate and FictionalSoft.', + final_answer=[ + CitedText( + sentence='The most recent contract about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft.', + sources=[UUID('4601c407-7905-4bd5-a1b9-4234bf18e9b6')] + ), + CitedText( + sentence='It says the parties will collaborate on projects including artificial intelligence research and development, with a three-year term and 50/50 profit sharing.', + sources=[UUID('4601c407-7905-4bd5-a1b9-4234bf18e9b6')] + ) + ] + ) + ``` +
+
+ + +
+ Example output + ```typescript + { + reasoning: 'The most recent contract that explicitly concerns AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft, which states that the parties wish to collaborate on artificial intelligence research and development. Among the provided contracts, no later agreement mentions AI, and later-dated documents are sales or lease agreements without AI-related terms.', + final_answer: [ + { + sentence: 'The most recent AI-related contract is the partnership agreement dated November 15, 2023, between Weaviate and FictionalSoft, which is for collaboration on artificial intelligence research and development.', + sources: [Array] + }, + { + sentence: 'No later contract in the provided set mentions AI; the newer 2024 documents are a sales agreement and lease agreements that do not reference artificial intelligence.', + sources: [Array] + } + ] + } + ``` +
+
+
+ +## What is supported? + + +| Feature | Supported? | Notes | +|---------|:----------:|-------| +| Min / max number of items in an array | ✅ | | +| Min / max value of a number property | ✅ | E.g. constrain a value to be within a certain range. | +| String formats: `uuid`, `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6` | ✅ | Validated as a string with the given format. | +| Regular expression (pattern) on a string | ✅ | | +| Recursive schemas (a schema referencing itself) | ✅ | | +| Default values (e.g. `x: int = 1`) | ❌ | The field is always populated by the model, so the default is never used. Consider using nullable entries and transforming them afterwards. | +| Schemas with 5000+ properties | ❌ | | +| 1000 or more enum values across all properties | ❌ | | +| More than 10 levels of nesting in a single property | ❌ | | + + +## Streaming + +Structured outputs are supported with [streaming ask mode](../guides/ask_mode.md#streaming). + +When streaming, the structured output is delivered incrementally as raw string fragments through `StreamedTokens` instances. No special parsing is applied during the stream — each token is simply a piece of the final output. To use the result, accumulate the streamed tokens into a single string, then validate the completed string against your schema. You can also attempt partial validation as the string grows if you want to react to the output before the stream finishes. + +## Questions and feedback + +import DocsFeedback from '/\_includes/docs-feedback.mdx'; + + diff --git a/sidebars.js b/sidebars.js index b328f000..2e07fe83 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1371,6 +1371,11 @@ const sidebars = { id: "query-agent/reference/advanced_collections", className: "sidebar-item", }, + { + type: "doc", + id: "query-agent/reference/structured_outputs", + className: "sidebar-item", + }, ], }, { From c51d7476292ac6968a12b6b1163fe2d6406e3b4e Mon Sep 17 00:00:00 2001 From: dannyjameswilliams Date: Wed, 24 Jun 2026 16:15:55 +0100 Subject: [PATCH 2/6] streaming correction, python/TS titles in basic usage --- docs/query-agent/reference/structured_outputs.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/query-agent/reference/structured_outputs.md b/docs/query-agent/reference/structured_outputs.md index 9e0ec058..276ab249 100644 --- a/docs/query-agent/reference/structured_outputs.md +++ b/docs/query-agent/reference/structured_outputs.md @@ -24,7 +24,7 @@ Structured outputs ensure that the model's final response will adhere to a given The structured output can be accessed by a new field, `final_answer_parsed`, which appears only when the `output_format` argument is not `None`. The raw string from the model can still be accessed at `final_answer`. - ### Pydantic BaseModel + **Pydantic BaseModel** - ### Dictionary + **Dictionary** - ### Object + **Object** Date: Mon, 27 Jul 2026 11:23:50 +0200 Subject: [PATCH 3/6] docs: fix structured outputs review findings Follow-up to #457, addressing review findings. Correctness: - structured_outputs.py: the citations snippet used Field() but only imported BaseModel, so the block raised NameError when copied. - Register structured_outputs.py/.mts in tests/test_agents.py; they were the only query-agent snippet files not covered by CI. - Bump weaviate-agents to >=1.7.0 (Python) and ^1.6.0 (TS); output_format and outputFormat do not exist in the pinned 1.6.0/1.5.0. Add zod as an explicit dependency - it was only resolving via hoisting of a transitive copy, though weaviate-agents declares it as a peer dep. Snippets: - Block-scope each TS example so they can all use `res`; the rendered docs were publishing res2/res3/res4/res5. - Use console.dir(..., { depth: null }) for the nested and citation examples, which console.log was abbreviating to `[Array]` - exactly the nested values those examples exist to show. - Move the zod import into the shared instantiation block, and call populateWeaviate/populate_weaviate as the other snippet files do. Docs: - Regenerate every example output from a single real run of each file. The previous outputs named "FictionalSoft", a party that appears in 0 of the 300 objects in the query-agent-financial-contracts dataset (the counterparty is OpenAI), so they were not reproducible. - Note that outputs are non-deterministic and will vary between runs. - State the minimum client versions and the Zod 4 requirement. - Correct the string-format row: formats constrain the shape the model generates but are not validated afterwards. A hostname-formatted field returned a 3606-character value, 14x the RFC 1123 limit. - Name ParsedAskModeResponse as the returned class in ask_mode.md. - Note that additionalProperties is optional, and why only the TS example carries it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CxMtvQPuFNT2ERqHx7C49z --- .../_includes/code/structured_outputs.mts | 34 ++- .../_includes/code/structured_outputs.py | 11 +- docs/query-agent/guides/ask_mode.md | 22 +- docs/query-agent/reference/index.md | 2 +- .../reference/structured_outputs.md | 263 ++++++++---------- package.json | 5 +- pyproject.toml | 2 +- tests/test_agents.py | 2 + uv.lock | 8 +- yarn.lock | 13 +- 10 files changed, 185 insertions(+), 177 deletions(-) diff --git a/docs/query-agent/_includes/code/structured_outputs.mts b/docs/query-agent/_includes/code/structured_outputs.mts index 40e96982..a81f2852 100644 --- a/docs/query-agent/_includes/code/structured_outputs.mts +++ b/docs/query-agent/_includes/code/structured_outputs.mts @@ -1,19 +1,20 @@ import 'dotenv/config' -const { loadClientInternally } = await import('./util.mjs').catch(() => import('../docs/query-agent/_includes/code/util.mjs')); +const { loadClientInternally, populateWeaviate } = await import('./util.mjs').catch(() => import('../docs/query-agent/_includes/code/util.mjs')); const client = await loadClientInternally(); +await populateWeaviate(client, false); // START SOInstantiate import { QueryAgent } from 'weaviate-agents'; +import { z } from 'zod'; const qa = new QueryAgent(client, { collections: ['FinancialContracts'] }); // END SOInstantiate +{ // START SOBasicExampleBaseModel -import { z } from 'zod'; - const ContractSummary = z.object({ contract_id: z.string(), contract_title: z.string(), @@ -29,10 +30,12 @@ const res = await qa.ask( console.log(res.finalAnswerParsed); // END SOBasicExampleBaseModel +} +{ // START SOBasicDictExample -const res2 = await qa.ask( +const res = await qa.ask( "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", { outputFormat: { @@ -51,22 +54,26 @@ const res2 = await qa.ask( } ); -console.log(res2.finalAnswerParsed); +console.log(res.finalAnswerParsed); // END SOBasicDictExample +} +{ // START SOReasoningExample const FinalAnswer = z.object({ reasoning: z.string(), final_answer: z.string(), }); -const res3 = await qa.ask("What is the most recent contract about AI?", { outputFormat: FinalAnswer }); +const res = await qa.ask("What is the most recent contract about AI?", { outputFormat: FinalAnswer }); -console.log(res3.finalAnswerParsed); +console.log(res.finalAnswerParsed); // END SOReasoningExample +} +{ // START SONestedExampleBaseModel const ContractInfo = z.object({ names_mentioned: z.array(z.string()).describe("All names within the contract text"), @@ -80,12 +87,16 @@ const ContractInfoResponse = z.object({ overall_summary: z.string(), }); -const res4 = await qa.ask("Find and return all contracts about AI in 2023", { outputFormat: ContractInfoResponse }); +const res = await qa.ask("Find and return all contracts about AI in 2023", { outputFormat: ContractInfoResponse }); -console.log(res4.finalAnswerParsed); +// `console.dir` with `depth: null` prints nested values in full; +// `console.log` would abbreviate them to `[Array]`. +console.dir(res.finalAnswerParsed, { depth: null }); // END SONestedExampleBaseModel +} +{ // START SOCitationExample const CitedText = z.object({ sentence: z.string().describe("A single sentence from your answer, to be combined with other sentences"), @@ -99,10 +110,11 @@ const CitedAnswer = z.object({ ), }); -const res5 = await qa.ask("What is the most recent contract about AI?", { outputFormat: CitedAnswer }); +const res = await qa.ask("What is the most recent contract about AI?", { outputFormat: CitedAnswer }); -console.log(res5.finalAnswerParsed); +console.dir(res.finalAnswerParsed, { depth: null }); // END SOCitationExample +} await client.close(); diff --git a/docs/query-agent/_includes/code/structured_outputs.py b/docs/query-agent/_includes/code/structured_outputs.py index 80d7673f..2288f99f 100644 --- a/docs/query-agent/_includes/code/structured_outputs.py +++ b/docs/query-agent/_includes/code/structured_outputs.py @@ -1,8 +1,9 @@ import sys sys.path.insert(0, "docs/query-agent/_includes/code") -from util import load_client_internally +from util import load_client_internally, populate_weaviate client = load_client_internally() +populate_weaviate(client, False) # START SOInstantiate @@ -32,7 +33,7 @@ class ContractSummary(BaseModel): # START SOBasicDictExample res = qa.ask( - "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", + "Find the oldest contract and include if it automatically renews, who is involved, and if user action is needed", output_format={ 'properties': { 'contract_id': {'title': 'Contract Id', 'type': 'string'}, @@ -83,7 +84,7 @@ class ContractInfoResponse(BaseModel): # END SONestedExampleBaseModel # START SOCitationExample -from pydantic import BaseModel +from pydantic import BaseModel, Field from uuid import UUID class CitedText(BaseModel): @@ -99,4 +100,6 @@ class CitedAnswer(BaseModel): res = qa.ask("What is the most recent contract about AI?", output_format=CitedAnswer) print(res.final_answer_parsed) -# END SOCitationExample \ No newline at end of file +# END SOCitationExample + +client.close() diff --git a/docs/query-agent/guides/ask_mode.md b/docs/query-agent/guides/ask_mode.md index b16f6795..72ffabff 100644 --- a/docs/query-agent/guides/ask_mode.md +++ b/docs/query-agent/guides/ask_mode.md @@ -67,7 +67,7 @@ The `.ask()` method accepts several arguments: | `query` | `str \| list[ChatMessage]` | The user query you want the agent to answer. This can be a simple string (`"What is the highest-grossing product?"`) or a list of chat messages (for conversational context). [See the page on multi-turn conversations for more detail](../reference/multi_turn_conversations.md). | | `collections` | `list[str \| QueryAgentCollectionConfig] \| None` | The name(s) of the collections to search. You can pass one or many collection names as a list of strings (e.g., `["ECommerce", "BookSales"]`), or provide collection configuration objects for more control. If specified in the `ask` method, it will overwrite those defined in the instantiation of `QueryAgent`. [See the page on collection configuration for more detail](../reference/advanced_collections.md). | | `result_evaluation` | `Literal["llm", "none"]` | Controls whether the agent will ask an LLM to "evaluate" the result based on all retrieved context. Accepts either:
• `"none"` (default): faster and cheaper; where the final answer is the last LLM call and no further analysis is completed.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | -| `output_format` | `dict \| type[BaseModel] \| None` | Optional schema for structured output in the final response. Modifies the `final_answer` field of the [response class](#response). See [the page on structured output for more details](../reference/structured_outputs.md). | +| `output_format` | `dict \| type[BaseModel] \| None` | Optional schema for structured output in the final response. When set, `.ask()` returns a `ParsedAskModeResponse` instead of an `AskModeResponse`: the parsed result is added on a new `final_answer_parsed` field, and `final_answer` still holds the raw model output. See [the response class](#response) and [the page on structured outputs for more details](../reference/structured_outputs.md). | @@ -76,7 +76,7 @@ The `.ask()` method accepts several arguments: | `query` | `string \| ChatMessage[]` | The user query you want the agent to answer. This can be a simple string (`"What is the highest-grossing product?"`) or a list of chat messages (for conversational context). [See the page on multi-turn conversations for more detail](../reference/multi_turn_conversations.md). | | `collections` | `(string \| QueryAgentCollectionConfig)[]` | The name(s) of the collections to search. You can pass one or many collection names as a list of strings (e.g., `["ECommerce", "BookSales"]`), or provide collection configuration objects for more control. [See the page on collection configuration for more detail](../reference/advanced_collections.md). If specified in the `ask` method, it will overwrite those defined in the instantiation of `QueryAgent`. | | `resultEvaluation` | `"llm" \| "none"` | Controls whether the agent will ask an LLM to "evaluate" the result based on all retrieved context. Accepts either:
• `"none"`: faster and cheaper; default setting where the final answer is the last LLM call.
• `"llm"`: higher cost/latency - enables a final step where an LLM subsets the sources retrieved to only those used in the answer, as well as enabling the optional fields `is_partial_answer` and `missing_information`. See [the response class](#response) for more details. | -| `outputFormat` | `ZodType \| object` | Optional schema for structured output in the final response. Pass a [Zod](https://zod.dev/) schema (parsed and validated) or a raw [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) object (parsed only). Modifies the `finalAnswer` field of the [response class](#response), exposing the typed result on `finalAnswerParsed`. See [the page on structured output for more details](../reference/structured_outputs.md). | +| `outputFormat` | `ZodType \| object` | Optional schema for structured output in the final response. Pass a [Zod](https://zod.dev/) schema (parsed and validated) or a raw [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) object (parsed only). When set, `.ask()` returns a `ParsedAskModeResponse` instead of an `AskModeResponse`: the typed result is added on a new `finalAnswerParsed` field, and `finalAnswer` still holds the raw model output. See [the response class](#response) and [the page on structured outputs for more details](../reference/structured_outputs.md). |
@@ -100,14 +100,15 @@ The `AskModeResponse` class has the following properties: | `final_answer` | `str` | A string comprising the LLM's final answer to the user query. | | `sources` | `list[Source] \| None` | A list of `Source` objects, which have an `object_id` property correlating to the UUID of the Weaviate object that was retrieved during the run. If `result_evaluation` is `"llm"`, these are subset to only those that are relevant to the `final_answer`. | -Additionally, there is another field if the `output_format` parameter on the call to ask mode (`qa.ask(..., output_format=...)`) was not `None`: +[See the client documentation for more detail.](https://weaviate-python-client.readthedocs.io/en/latest/weaviate-agents-python-client/docs/weaviate_agents.classes.html#weaviate_agents.classes.AskModeResponse) + +If you provide the `output_format` parameter (`qa.ask(..., output_format=...)`), Ask Mode returns a `ParsedAskModeResponse` instead. This is a subclass of `AskModeResponse`, so it keeps every field above — including `final_answer`, which still holds the raw string from the model — and adds one more: + | Field | Type | Description | | --- | --- | --- | -| `final_answer_parsed` | `` | The final response, conforming to the schema given in the `output_format`. | +| `final_answer_parsed` | `` | The final response, parsed into the schema given in `output_format`. | The type of `final_answer_parsed` is a `dict` if a dictionary was supplied to `output_format`, otherwise it will be the exact type of the `BaseModel` given. - -[See the client documentation for more detail.](https://weaviate-python-client.readthedocs.io/en/latest/weaviate-agents-python-client/docs/weaviate_agents.classes.html#weaviate_agents.classes.AskModeResponse) @@ -123,14 +124,17 @@ The type of `final_answer_parsed` is a `dict` if a dictionary was supplied to `o | `finalAnswer` | `string` | A string comprising the LLM's final answer to the user query. | | `sources` | `Source[]` | A list of `Source` objects, which have an `objectId` property correlating to the UUID of the Weaviate object that was retrieved during the run. If `resultEvaluation` is `"llm"`, these are subset to only those that are relevant to the `finalAnswer`. | -Additionally, there is another field if the `outputFormat` parameter on the call to ask mode (`qa.ask(..., { outputFormat: ... })`) was provided: +[See the client documentation for more detail.](https://weaviate.github.io/agents-typescript-client/types/AskModeResponse.html) + +If you provide the `outputFormat` parameter (`qa.ask(..., { outputFormat: ... })`), Ask Mode returns a `ParsedAskModeResponse` instead. This is an `AskModeResponse` extended with one more field, so it keeps every field above — including `finalAnswer`, which still holds the raw string from the model: + | Field | Type | Description | | --- | --- | --- | -| `finalAnswerParsed` | `` | The final response, conforming to the schema given in `outputFormat`. | +| `finalAnswerParsed` | `` | The final response, parsed into the schema given in `outputFormat`. | The type of `finalAnswerParsed` is `Record` if a raw JSON Schema object was supplied to `outputFormat`, otherwise it will be the inferred type of the Zod schema given (`z.infer`). -[See the client documentation for more detail.](https://weaviate.github.io/agents-typescript-client/types/AskModeResponse.html) +[See the client documentation for more detail.](https://weaviate.github.io/agents-typescript-client/types/ParsedAskModeResponse.html) diff --git a/docs/query-agent/reference/index.md b/docs/query-agent/reference/index.md index e05e2459..0bbff9b2 100644 --- a/docs/query-agent/reference/index.md +++ b/docs/query-agent/reference/index.md @@ -12,7 +12,7 @@ See the different configuration options for the Query Agent and how you can cust * **[Multi-turn Conversations](./multi_turn_conversations.md)**: Learn how to include multiple turns of conversations in a message history instead of a single user query. * **[Additional Filters](./additional_filters.md)**: Define persistent filters that get added to every search the Query Agent performs. * **[Collection Configuration](./advanced_collections.md)**: Setup your collections with more advanced configurations, such as named vectors, multi-tenancy and additional filters. -* **[Structured Outputs](./structured_outputs.md)**: Configure the format of the ask mode response to conform to a schema. +* **[Structured Outputs](./structured_outputs.md)**: Configure the format of the Ask Mode response to conform to a schema. ## Questions and feedback diff --git a/docs/query-agent/reference/structured_outputs.md b/docs/query-agent/reference/structured_outputs.md index 276ab249..fccaa294 100644 --- a/docs/query-agent/reference/structured_outputs.md +++ b/docs/query-agent/reference/structured_outputs.md @@ -1,5 +1,5 @@ --- -title: Structured Outputs +title: Structured outputs description: "Conform the final response to a particular schema." image: og/docs/query-agent.png # tags: ['agents', 'query-agent', 'configuration'] @@ -12,17 +12,46 @@ import PyCode from '!!raw-loader!/docs/query-agent/_includes/code/structured_out import TSCode from '!!raw-loader!/docs/query-agent/_includes/code/structured_outputs.mts'; -Structured outputs ensure that the model's final response will adhere to a given schema. This means you can easily access aspects of a response which is customised to your use-case. +Structured outputs ensure that the Query Agent's final response adheres to a schema that you provide. Instead of parsing a free-text answer, you get back an object whose fields are customized to your use case. -## Basic Usage +Structured outputs are supported in **[Ask Mode](../guides/ask_mode.md) only.** + +:::info Client versions +Structured outputs require `weaviate-agents` **1.7.0 or later** in Python, and **1.6.0 or later** in JavaScript/TypeScript. The JavaScript/TypeScript examples that use a Zod schema also require **Zod 4 or later**. [See the installation page](../installation.md). +::: + +The example outputs on this page are illustrative. The Query Agent is non-deterministic, so the wording, the level of detail, and the specific records it selects will vary between runs, even for an identical query and schema. The *shape* of the response is what the schema guarantees. + +## Basic usage + +Set the schema per request, with the `output_format` argument of `.ask()` (`outputFormat` in JavaScript/TypeScript). The examples on this page use a Query Agent instantiated over a `FinancialContracts` collection ([see the class instantiation page for more detail](./instantiation.md)): - In Python, you can either provide a Pydantic `BaseModel` or a raw dictionary conforming to the [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) specification. + + + + + + + +The examples below use structured outputs to generate a set of metadata associated with a single retrieved item. - In the below examples, we use structured outputs to generate a set of metadata associated with a single retrieved item. + + + In Python, you can either provide a Pydantic `BaseModel` or a raw dictionary conforming to the [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) specification. - The structured output can be accessed by a new field, `final_answer_parsed`, which appears only when the `output_format` argument is not `None`. The raw string from the model can still be accessed at `final_answer`. + The structured output is available on a new field, `final_answer_parsed`, which appears when you provide `output_format`. The raw string from the model can still be accessed at `final_answer`. **Pydantic BaseModel** Example output ```python ContractSummary( - contract_id='46', + contract_id='46.0', contract_title='Employment Contract', auto_renew=False, - parties_involved=['Weaviate', 'Mark Robson'], + parties_involved=['Weaviate (Employer)', 'Mark Robson (Employee)', 'Hans Zimmer (Chief Executive Officer/signatory)'], requires_action=True ) ``` @@ -58,10 +87,10 @@ Structured outputs ensure that the model's final response will adhere to a given Example output ```python { - 'contract_id': '46', + 'contract_id': '46.0', 'contract_title': 'Employment Contract', 'auto_renew': False, - 'parties_involved': ['Weaviate', 'Mark Robson'], + 'parties_involved': ['Weaviate (Employer)', 'Mark Robson (Employee)', 'Hans Zimmer, Chief Executive Officer'], 'requires_action': True } ``` @@ -71,9 +100,7 @@ Structured outputs ensure that the model's final response will adhere to a given In JavaScript/TypeScript, you can either provide a [Zod](https://zod.dev/) schema or a raw object conforming to the [Draft 2020-12 JSON Schema](https://json-schema.org/draft/2020-12) specification. - In the below examples, we use structured outputs to generate a set of metadata associated with a single retrieved item. - - The structured output can be accessed by a new field, `finalAnswerParsed`, which appears only when the `outputFormat` argument is not null. The raw string from the model can still be accessed at `finalAnswer`. + The structured output is available on a new field, `finalAnswerParsed`, which appears when you provide `outputFormat`. The raw string from the model can still be accessed at `finalAnswer`. **Zod schema** Example output ```typescript { - contract_id: '46', + contract_id: '46.0', contract_title: 'Employment Contract', auto_renew: false, - parties_involved: [ 'Weaviate', 'Mark Robson' ], + parties_involved: [ 'Weaviate (Employer)', 'Mark Robson (Employee)' ], requires_action: true } ``` @@ -106,15 +133,21 @@ Structured outputs ensure that the model's final response will adhere to a given Example output ```typescript { - contract_id: '46.0', + contract_id: '46', contract_title: 'Employment Contract', auto_renew: false, - parties_involved: [ 'Weaviate', 'Mark Robson' ], + parties_involved: [ + 'Weaviate (Employer)', + 'Mark Robson (Employee)', + 'Hans Zimmer (Chief Executive Officer and signatory)' + ], requires_action: true } ``` + The `additionalProperties: false` entry above is what Zod's own JSON Schema conversion emits, so the two examples describe the same schema. It is optional — the Query Agent accepts a schema with or without it. + @@ -134,8 +167,8 @@ As a basic example, consider adding an additional field `reasoning` to the respo Example output ```python FinalAnswer( - reasoning='The most recent contract mentioning AI is the sales agreement dated 2024-03-15, but it does not mention AI in the text. The most recent contract that is actually about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft, which explicitly states it is for artificial intelligence research and development. No later AI-related contract is present in the provided data.', - final_answer='The most recent contract about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft. It is specifically for collaboration on artificial intelligence research and development. No more recent AI-related contract appears in the provided data.' + reasoning='Among the provided contracts, the latest one explicitly concerning AI is dated November 15, 2023. It is a partnership agreement between Weaviate and OpenAI for collaboration on artificial intelligence research and development. The March 15, 2024 contracts are sales and lease agreements and do not concern AI.', + final_answer='The most recent AI-related contract is a **Partnership Agreement dated November 15, 2023**, between **Weaviate and OpenAI**. It establishes a three-year partnership to collaborate on **artificial intelligence research and development**. Weaviate is responsible for marketing and promotion, while OpenAI provides technical expertise and development support; profits are to be split equally.\n\n- **Contract type:** Partnership agreement\n- **Date:** November 15, 2023\n- **Author/signatory:** Johnathan Smith, CEO of Weaviate\n- **Document ID:** 60' ) ``` @@ -151,17 +184,15 @@ As a basic example, consider adding an additional field `reasoning` to the respo Example output ```typescript { - reasoning: 'The most recent contract related to AI is the partnership agreement dated 2024-03-15 at 10:30 UTC. It specifically mentions collaboration on artificial intelligence research and development.', - final_answer: 'The most recent AI-related contract is a **Partnership Agreement** dated **2024-03-15 10:30 UTC** between **Weaviate** and **FictionalSoft**. It says the parties wish to establish a partnership to collaborate on **artificial intelligence research and development**. It has a **2-year term** and includes financial contributions of **$244.46 from Weaviate** and **$151.01 from FictionalSoft**, with profits split **50/50**.\n' + - '\n' + - 'If you want, I can also summarize the next most recent AI-related contract.' + reasoning: 'The latest dated contract explicitly concerning AI is dated May 15, 2023. Later contracts in the provided data are dated November 15, 2023 and March 15, 2024, but do not concern AI. There are multiple duplicate records for the May 15 contract; the matching record identifies doc_id 56.0 and author Alice Johnson.', + final_answer: 'The most recent AI-related contract is a **Partnership Agreement** dated **May 15, 2023**.\n\n- **Author:** Alice Johnson\n- **Doc ID:** 56.0\n- **Summary:** Weaviate and OpenAI agree to collaborate on developing AI-driven solutions to improve data management and retrieval. Weaviate contributes $391.74, OpenAI contributes $302.40, profits are split 60/40, and the agreement lasts three years.\n\nNo later contract in the provided records explicitly concerns AI.' } ``` -## Example: Nested Schemas +## Example: Nested schemas Nested schemas are supported, for example, you can define two schemas and have one reference the other, allowing more complex structured outputs to be crafted. @@ -175,115 +206,71 @@ In the below example, the final response will generate a list of information for endMarker="# END SONestedExampleBaseModel" language="py" /> + A `Field` can be used to provide additional metadata, such as a `description`, or even constraints on numeric objects. A `Literal` can be used to constrain a field to produce only one of a few different objects.
Example output ```python ContractInfoResponse( contract_infos=[ ContractInfo( - names_mentioned=['Hans Zimmer', 'Weaviate', 'Mark Robson'], + names_mentioned=['Weaviate', 'OpenAI', 'Mark Robson', 'Kaladin Stormblessed'], contract_type='other', - summary='Loan agreement between Weaviate and Mark Robson for $342.00, dated 2023-03-15.', - contract_uuid=UUID('b2c4ffdc-411c-423a-9040-b7cbf5439bd0') + summary='Partnership Agreement dated March 15, 2023, establishing collaboration between Weaviate and OpenAI on artificial intelligence research and development. Weaviate contributes technology resources valued at $112.85 and staff time valued at $550.09; OpenAI contributes research expertise and project-management support valued at $98.14. Net profits are split 60% to Weaviate and 40% to OpenAI.', + contract_uuid=UUID('8ec8f74a-2d38-4aca-80ca-e66f12ae0cb6') ), ContractInfo( - names_mentioned=['John Smith', 'Weaviate'], + names_mentioned=['Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson'], contract_type='other', - summary='Non-disclosure agreement between Weaviate and John Smith, dated 2022-03-15.', - contract_uuid=UUID('c126e3d3-db85-4a77-b49c-2a87fe48cd52') + summary='Partnership Agreement dated March 15, 2023, for collaboration on artificial intelligence projects. Weaviate contributes $210.97 toward initial project costs, while OpenAI contributes $194.05 toward research and development. OpenAI is responsible for technical development and AI research expertise.', + contract_uuid=UUID('056b6b5c-d6d3-4235-9003-4822dbbef9ef') ), ContractInfo( - names_mentioned=['Johnathan Smith', 'Weaviate', 'John Smith'], + names_mentioned=['Weaviate', 'OpenAI', 'Arthur Penndragon', 'Mark Robson', 'Danny Williams'], contract_type='other', - summary='Lease agreement for office space between Weaviate and John Smith, dated 2024-03-15.', - contract_uuid=UUID('6bbca4b9-fea1-4275-889a-1f5d4591ecbb') + summary='Partnership Agreement dated March 15, 2023, for artificial intelligence research and development, with shared resources and expertise. Weaviate contributes technology resources and staff time; OpenAI contributes research expertise and project-management support. Profits are divided 60% to Weaviate and 40% to OpenAI.', + contract_uuid=UUID('c50c3b9b-339a-4830-b2f7-4b0b9b84bc56') ), ContractInfo( - names_mentioned=['Arthur Penndragon', 'Weaviate', 'Mark Robson'], + names_mentioned=['Weaviate', 'OpenAI', 'Edward Elric', 'Mark Robson'], contract_type='other', - summary='Loan agreement between Weaviate and Mark Robson for $620.41, dated 2023-03-15.', - contract_uuid=UUID('93213fd4-4c55-4a7c-a190-2e4c9d808566') + summary='Partnership Agreement dated March 15, 2023, to develop advanced data-processing technologies. Weaviate provides technological support and resources valued at $416.56; OpenAI contributes AI and machine-learning expertise valued at $567.91. Revenue is split 60% to Weaviate and 40% to OpenAI.', + contract_uuid=UUID('3cec1521-3d26-46b3-b63f-1af2ddaa3db4') ), ContractInfo( - names_mentioned=['Alice Johnson', 'Weaviate', 'Danny Williams'], + names_mentioned=['Weaviate', 'OpenAI'], contract_type='other', - summary='Service agreement for digital marketing services between Weaviate and Danny Williams, dated - 2023-03-15, with total compensation of $961.89.', - contract_uuid=UUID('eed7d7f9-b06a-4d7b-b19a-f0f3782e49fd') + summary='Partnership Agreement dated March 15, 2023, focused on collaborative projects in AI technology development. Weaviate contributes $234.12 toward project funding, and OpenAI contributes $173.25 for marketing and promotion. Weaviate handles technical development, while OpenAI conducts research and data analysis.', + contract_uuid=UUID('3d2afbc4-24c1-4f64-b676-193e4dccb451') ), ContractInfo( - names_mentioned=['Weaviate', 'John Smith'], + names_mentioned=['Weaviate', 'OpenAI', 'Johnathan Smith', 'Mark Robson'], contract_type='other', - summary='Loan agreement between Weaviate and John Smith for $403.65, dated 2022-03-15.', - contract_uuid=UUID('5f75ec7e-ca7f-415c-9f5d-7d3a1518fad8') - ), - ContractInfo( - names_mentioned=['Hans Zimmer', 'Weaviate', 'Mark Robson'], - contract_type='sales', - summary='Sales agreement between Weaviate and Mark Robson for software licenses and support services, - dated 2023-04-24, with a total purchase price of $420.03.', - contract_uuid=UUID('174b1a9a-e9b2-4f48-960d-283a0fbbe3ab') + summary='Partnership Agreement dated March 15, 2023, to advance artificial intelligence technologies and develop innovative AI solutions. Weaviate contributes $177.98 and OpenAI contributes $67.09; each party is responsible for roles specified in an attached exhibit.', + contract_uuid=UUID('8ac31eff-9936-4a1b-a5a4-8e148a4596bf') ), ContractInfo( - names_mentioned=['John Williams', 'Weaviate', 'Mark Robson'], + names_mentioned=['Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson'], contract_type='other', - summary='Service agreement between Weaviate and Mark Robson for consultation, software development, and - project management services, dated 2023-04-15, with total compensation of $744.35.', - contract_uuid=UUID('65848d82-e38e-4bed-b0bb-0c2830af8b27') + summary='Partnership Agreement dated October 15, 2023, for development of innovative AI solutions. Weaviate contributes $726.88 and project resources, while OpenAI contributes $251.09 and technical expertise. Profits are shared 60% to Weaviate and 40% to OpenAI.', + contract_uuid=UUID('aa3c40bc-8bea-42d3-9692-75a417a99a0d') ), ContractInfo( - names_mentioned=['John Williams', 'Weaviate'], + names_mentioned=['Weaviate', 'OpenAI', 'Johnathan Smith', 'Mark Robson'], contract_type='other', - summary='Invoice from Weaviate to John Williams for data analysis, API integration, system maintenance, - technical support, and consultation services, dated 2023-10-15, totaling $873.17.', - contract_uuid=UUID('5b5544f9-7092-45d0-830d-dd211b0f3a70') + summary='Partnership Agreement dated November 15, 2023, covering projects including artificial intelligence research and development. Weaviate contributes $244.46 and handles marketing; OpenAI contributes $151.01 and provides technical expertise. Profits are shared equally.', + contract_uuid=UUID('1e85f2b6-f2f7-4e52-86ba-c4f508f6c06d') ), ContractInfo( - names_mentioned=['Kaladin Stormblessed', 'Weaviate', 'John Smith'], + names_mentioned=['Weaviate', 'OpenAI', 'Alice Johnson', 'Danny Williams'], contract_type='other', - summary='Lease agreement between Weaviate and John Smith for office space, dated 2023-07-15.', - contract_uuid=UUID('f3f3730b-f3c4-4b2f-aa7a-1bc07b5814d8') - ), - ContractInfo( - names_mentioned=['Johnathan Smith', 'Weaviate', 'Mark Robson'], - contract_type='other', - summary='Non-disclosure agreement between Weaviate and Mark Robson, dated 2023-09-15.', - contract_uuid=UUID('3d4d802d-1820-4a5f-b8fe-6c5e189de1e6') - ), - ContractInfo( - names_mentioned=['John Williams', 'Weaviate', 'Danny Williams'], - contract_type='sales', - summary='Sales agreement between Weaviate and Danny Williams for products A, B, and C, dated - 2023-11-15, with a total purchase price of $270.68.', - contract_uuid=UUID('e645324e-14a4-4ca4-aeae-da146d55a3bb') - ), - ContractInfo( - names_mentioned=['John Williams', 'Weaviate', 'Mark Robson'], - contract_type='other', - summary='Invoice from Weaviate to Mark Robson dated 2023-04-15 for consultation, software development, - and project management services, totaling $744.35.', - contract_uuid=UUID('48a58d82-e38e-4bed-b0bb-0c2830af8b27') - ), - ContractInfo( - names_mentioned=['John Williams', 'Weaviate', 'John Smith'], - contract_type='other', - summary='Invoice from Weaviate to John Smith dated 2023-10-15 for consultation, development, and - additional charges, totaling $296.36.', - contract_uuid=UUID('03c0f5bb-f999-4b8b-86f3-271405de0037') - ), - ContractInfo( - names_mentioned=['Johnathan Smith', 'Weaviate', 'Danny Williams'], - contract_type='other', - summary='Service agreement between Weaviate and Danny Williams for software development, technical - support, and consultation services, dated 2023-03-15, with compensation of $273.86.', - contract_uuid=UUID('d68e20a9-4bd4-42d0-8986-d16425c3444a') + summary='Service Agreement dated March 15, 2023, for AI development and consulting services. The total fee is $249.44, payable in two installments of $124.72.', + contract_uuid=UUID('1363c2ae-83e6-4049-8f95-cd6c873a997e') ) ], - overall_summary='There are multiple 2023 contracts related to AI, including two partnership agreements about artificial intelligence research and development, plus other 2023 contracts mentioning AI-adjacent services. Some listed contracts are not directly about AI but were returned from the available matching set.' + overall_summary='Nine distinct contracts concerning AI, artificial intelligence research and development, AI technology development, AI solutions, or AI development and consulting were identified in 2023. Several duplicate records in the provided data were consolidated by document identity.' ) ```
- A `Field` can be used to provide additional metadata, such as a `description`, or even constraints on numeric objects. A `Literal` can be used to constrain a field to produce only one of a few different objects. @@ -353,7 +334,7 @@ In the below example, the final response will generate a list of information for For a custom implementation of citing text (for example, if you want citations in-line), you could create a schema that iteratively builds a response from objects consisting of pairs of text and source IDs. :::note Supported Citations -The Query Agent natively supports subsetting and evaluating the quality of the response via the `result_evaluation` parameter in ask mode. [See here for more details](../guides/ask_mode.md#parameters) +The Query Agent natively supports subsetting and evaluating the quality of the response via the `result_evaluation` parameter in Ask Mode. [See the Ask Mode parameters for more details](../guides/ask_mode.md#parameters). ::: @@ -368,15 +349,15 @@ The Query Agent natively supports subsetting and evaluating the quality of the r Example output ```python CitedAnswer( - reasoning='The most recent contract mentioning AI is the partnership agreement dated 2024-03-15, which explicitly refers to AI-related work only in the contract text by implication? However, among the provided contracts, the latest one that clearly concerns AI is the partnership agreement from 2023-11-15, and another earlier partnership agreement from 2023-10-15 also mentions AI solutions. The 2024-03-15 sales agreement does not mention AI, so it is not relevant. The latest clearly AI-related contract in the data is the 2023-11-15 partnership agreement between Weaviate and FictionalSoft.', + reasoning='The latest dated contract in the provided records that explicitly concerns AI is the partnership agreement dated November 15, 2023 (doc_id 60.0), which covers collaboration on AI research and development. The March 15, 2024 contract is a sales agreement for unspecified products and does not mention AI.', final_answer=[ CitedText( - sentence='The most recent contract about AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft.', - sources=[UUID('4601c407-7905-4bd5-a1b9-4234bf18e9b6')] + sentence='The most recent contract explicitly about AI is a Partnership Agreement dated November 15, 2023, between Weaviate and OpenAI (doc_id 60.0), authored by Johnathan Smith.', + sources=[UUID('a06ab40a-bcc3-4fc2-bb8a-b2b2598f706c')] ), CitedText( - sentence='It says the parties will collaborate on projects including artificial intelligence research and development, with a three-year term and 50/50 profit sharing.', - sources=[UUID('4601c407-7905-4bd5-a1b9-4234bf18e9b6')] + sentence='It establishes a three-year collaboration on projects including artificial-intelligence research and development, with Weaviate contributing $244.46, OpenAI contributing $151.01, shared marketing and technical responsibilities, and profits split equally.', + sources=[UUID('a06ab40a-bcc3-4fc2-bb8a-b2b2598f706c')] ) ] ) @@ -394,15 +375,15 @@ The Query Agent natively supports subsetting and evaluating the quality of the r Example output ```typescript { - reasoning: 'The most recent contract that explicitly concerns AI is the partnership agreement dated 2023-11-15 between Weaviate and FictionalSoft, which states that the parties wish to collaborate on artificial intelligence research and development. Among the provided contracts, no later agreement mentions AI, and later-dated documents are sales or lease agreements without AI-related terms.', + reasoning: 'The latest dated contract in the provided records that explicitly concerns AI is dated May 15, 2023. It is a partnership agreement for developing AI-driven solutions; the later March 15, 2024 contracts do not mention AI.', final_answer: [ { - sentence: 'The most recent AI-related contract is the partnership agreement dated November 15, 2023, between Weaviate and FictionalSoft, which is for collaboration on artificial intelligence research and development.', - sources: [Array] + sentence: 'The most recent AI-related contract is a Partnership Agreement dated May 15, 2023, authored by Alice Johnson, with document ID 56.0.', + sources: [ 'e32c13b7-b552-45f6-b58b-3f0c325715ab' ] }, { - sentence: 'No later contract in the provided set mentions AI; the newer 2024 documents are a sales agreement and lease agreements that do not reference artificial intelligence.', - sources: [Array] + sentence: 'It establishes a partnership between Weaviate and OpenAI to develop AI-driven solutions for improving data management and retrieval, with Weaviate contributing $391.74, OpenAI contributing $302.40, and profits split 60% to Weaviate and 40% to OpenAI over a three-year term.', + sources: [ 'e32c13b7-b552-45f6-b58b-3f0c325715ab' ] } ] } @@ -418,22 +399,22 @@ The Query Agent natively supports subsetting and evaluating the quality of the r |---------|:----------:|-------| | Min / max number of items in an array | ✅ | | | Min / max value of a number property | ✅ | E.g. constrain a value to be within a certain range. | -| String formats: `uuid`, `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6` | ✅ | Validated as a string with the given format. | +| String formats: `uuid`, `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6` | ✅ | Constrains the model to generate a string in the shape of the given format. The value is not validated against the full specification for that format after generation, so ask for a field the retrieved data can actually populate. | | Regular expression (pattern) on a string | ✅ | | | Recursive schemas (a schema referencing itself) | ✅ | | -| Default values (e.g. `x: int = 1`) | ❌ | The field is always populated by the model, so the default is never used. Consider using nullable entries and transforming them afterwards. | -| Schemas with 5000+ properties | ❌ | | -| 1000 or more enum values across all properties | ❌ | | -| More than 10 levels of nesting in a single property | ❌ | | +| Default values (e.g. `x: int = 1`) | ❌ | The schema is accepted, but the field is always populated by the model, so the default is never used. Consider using nullable entries and transforming them afterwards. | +| Schemas with 5000+ properties | ❌ | Rejected with a `SCHEMA_VALIDATION_ERROR` before the agent runs. | +| 1000 or more enum values across all properties | ❌ | Rejected with a `SCHEMA_VALIDATION_ERROR` before the agent runs. | +| More than 10 levels of nesting in a single property | ❌ | Rejected with a `SCHEMA_VALIDATION_ERROR` before the agent runs. The error names the offending field path and its depth. | ## Streaming -Structured outputs are supported with [streaming ask mode](../guides/ask_mode.md#streaming). +Structured outputs are supported with [streaming in Ask Mode](../guides/ask_mode.md#streaming). -When streaming, the structured output is delivered incrementally as raw string fragments through `StreamedTokens` instances. No special parsing is applied during the stream — each token is simply a piece of the final output. To use the partial result, accumulate the streamed tokens into a single string, then partially validate the string against your schema. +When streaming, the structured output is delivered incrementally as raw string fragments through `StreamedTokens` instances. No special parsing is applied during the stream — each token is a fragment of the final output. To use the partial result, accumulate the streamed tokens into a single string, then partially validate the string against your schema. -To use the final result after completion, you do not need to use the streamed tokens. Simply access the `final_answer_parsed` attribute (`finalAnswerParsed` in TypeScript) of the [`AskModeResponse` final state output](../guides/ask_mode.md#responses). +To use the final result after completion, you do not need to use the streamed tokens. Read the `final_answer_parsed` attribute (`finalAnswerParsed` in TypeScript) of the [final state output](../guides/ask_mode.md#responses). ## Questions and feedback diff --git a/package.json b/package.json index fae12bed..0c238474 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,10 @@ "stream-chain": "^2.2.5", "stream-json": "^1.7.5", "uuid": "^13.0.0", - "weaviate-agents": "^1.5.0", + "weaviate-agents": "^1.6.0", "weaviate-client": "^3.12.1", - "weaviate-ts-embedded": "^1.1.0" + "weaviate-ts-embedded": "^1.1.0", + "zod": "^4.0.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.8.1", diff --git a/pyproject.toml b/pyproject.toml index 132c210c..b1a9cce6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "python-dotenv>=1.1.1", "requests>=2.32.3", "tqdm>=4.67.1", - "weaviate-agents>=1.6.0", + "weaviate-agents>=1.7.0", "weaviate-client==4.22.0", "weaviate-demo-datasets>=0.8.1", "weaviate-engram>=0.3.0", diff --git a/tests/test_agents.py b/tests/test_agents.py index 13fe8682..66763582 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -18,6 +18,7 @@ "./docs/query-agent/_includes/code/query_agent.py", "./docs/query-agent/_includes/code/quickstart.py", "./docs/query-agent/_includes/code/search_mode.py", + "./docs/query-agent/_includes/code/structured_outputs.py", "./docs/query-agent/_includes/code/suggest_queries.py", "./docs/query-agent/_includes/code/system_prompt.py", # Recipe walkthroughs (Weaviate Cloud + Weaviate Embeddings only) @@ -61,6 +62,7 @@ def test_recipes_requiring_openai_pyv4(script_loc): "./docs/query-agent/_includes/code/query_agent.mts", "./docs/query-agent/_includes/code/quickstart.mts", "./docs/query-agent/_includes/code/search_mode.mts", + "./docs/query-agent/_includes/code/structured_outputs.mts", "./docs/query-agent/_includes/code/suggest_queries.mts", "./docs/query-agent/_includes/code/system_prompt.mts", ], diff --git a/uv.lock b/uv.lock index 1bf725bd..1db99e98 100644 --- a/uv.lock +++ b/uv.lock @@ -1718,16 +1718,16 @@ wheels = [ [[package]] name = "weaviate-agents" -version = "1.6.0" +version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx-sse" }, { name = "rich" }, { name = "weaviate-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/a5/bd394d83a86153bcb54c297052558b312a247368c04ed08149850f0dcf71/weaviate_agents-1.6.0.tar.gz", hash = "sha256:a30c9c5120df3bc55ba1fa2384a4ebf5a3654d427a758294d9fa712cb5791576", size = 107790, upload-time = "2026-06-16T19:38:56.386Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/99/750d7d2e33f610bad0d4b1d77e2a7f0c73dbab5459876fa51c7a4a440a3a/weaviate_agents-1.7.0.tar.gz", hash = "sha256:f033085cbde7123424f5579dfab553df5e0128d395dfea3b58c5aea6602340cb", size = 111884, upload-time = "2026-07-27T08:31:58.622Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/69/9c5876da7925332d63151bcc61990e272cb8140a6dc7c6e887c905a3aede/weaviate_agents-1.6.0-py3-none-any.whl", hash = "sha256:e68723e3c14bf9639feadf307a55b7af4ddf2010e4d382e87b70766aeec8dae4", size = 50262, upload-time = "2026-06-16T19:38:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d3/30952e1a4ca1556beb73e756d1f514c0bd62db53c21bdc65cdd6c5c6b78e/weaviate_agents-1.7.0-py3-none-any.whl", hash = "sha256:052e37ad9114424149b29deb8ba96159fce69f54e45874a4d6e42b1831643931", size = 54505, upload-time = "2026-07-27T08:31:57.755Z" }, ] [[package]] @@ -1795,7 +1795,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.1.1" }, { name = "requests", specifier = ">=2.32.3" }, { name = "tqdm", specifier = ">=4.67.1" }, - { name = "weaviate-agents", specifier = ">=1.6.0" }, + { name = "weaviate-agents", specifier = ">=1.7.0" }, { name = "weaviate-client", specifier = "==4.22.0" }, { name = "weaviate-demo-datasets", specifier = ">=0.8.1" }, { name = "weaviate-engram", specifier = ">=0.3.0" }, diff --git a/yarn.lock b/yarn.lock index 3d8e031b..750e8bf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15483,10 +15483,10 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weaviate-agents@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/weaviate-agents/-/weaviate-agents-1.5.0.tgz#a842c85bccfbdba172f8e3b00e61b8ff9953bfd7" - integrity sha512-0In5bvKaI8kaef8hX+NXp/VwJmkBYIsWz0zEH8Jw+ZEF/kORDzH3FNMcID29+TuoCrGQTRz/kbAx1LI68a+JRQ== +weaviate-agents@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/weaviate-agents/-/weaviate-agents-1.6.0.tgz#29139b1afa51c65b64b20b6c8ad180f6c46f7915" + integrity sha512-rOFYiZz9c+RdFj/G28xWp+6jx5I2Nl88dMB14QwZWPNBbieRTFzo8S+DeZbh/7zsXa9/oRjRiTMkxMb/VoNyVA== weaviate-client@^3.12.1: version "3.13.0" @@ -15959,6 +15959,11 @@ zod@^3.23.8: resolved "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz" integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== +zod@^4.0.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.4.3.tgz#b680f172885d18bbebf21a834ea25e55a1bbf356" + integrity sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ== + zod@^4.1.8, zod@^4.3.5: version "4.3.6" resolved "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz" From 3ac2570aecbc93b4c8b5bd79a1105426e753aaf3 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:30:38 +0200 Subject: [PATCH 4/6] docs: drop explanatory comment from console.dir examples Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CxMtvQPuFNT2ERqHx7C49z --- docs/query-agent/_includes/code/structured_outputs.mts | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/query-agent/_includes/code/structured_outputs.mts b/docs/query-agent/_includes/code/structured_outputs.mts index a81f2852..d3537e6c 100644 --- a/docs/query-agent/_includes/code/structured_outputs.mts +++ b/docs/query-agent/_includes/code/structured_outputs.mts @@ -89,8 +89,6 @@ const ContractInfoResponse = z.object({ const res = await qa.ask("Find and return all contracts about AI in 2023", { outputFormat: ContractInfoResponse }); -// `console.dir` with `depth: null` prints nested values in full; -// `console.log` would abbreviate them to `[Array]`. console.dir(res.finalAnswerParsed, { depth: null }); // END SONestedExampleBaseModel } From a185616edd96de1762df856124e7c787042c4c46 Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:01:18 +0200 Subject: [PATCH 5/6] docs: tighten prose in structured outputs pages Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CxMtvQPuFNT2ERqHx7C49z --- docs/query-agent/guides/ask_mode.md | 4 ++-- docs/query-agent/reference/structured_outputs.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/query-agent/guides/ask_mode.md b/docs/query-agent/guides/ask_mode.md index 72ffabff..85cfee2d 100644 --- a/docs/query-agent/guides/ask_mode.md +++ b/docs/query-agent/guides/ask_mode.md @@ -102,7 +102,7 @@ The `AskModeResponse` class has the following properties: [See the client documentation for more detail.](https://weaviate-python-client.readthedocs.io/en/latest/weaviate-agents-python-client/docs/weaviate_agents.classes.html#weaviate_agents.classes.AskModeResponse) -If you provide the `output_format` parameter (`qa.ask(..., output_format=...)`), Ask Mode returns a `ParsedAskModeResponse` instead. This is a subclass of `AskModeResponse`, so it keeps every field above — including `final_answer`, which still holds the raw string from the model — and adds one more: +If you provide the `output_format` parameter (`qa.ask(..., output_format=...)`), Ask Mode returns a `ParsedAskModeResponse` instead. It is a subclass of `AskModeResponse`, so it keeps every field above and adds one more. `final_answer` still holds the raw string from the model. | Field | Type | Description | | --- | --- | --- | @@ -126,7 +126,7 @@ The type of `final_answer_parsed` is a `dict` if a dictionary was supplied to `o [See the client documentation for more detail.](https://weaviate.github.io/agents-typescript-client/types/AskModeResponse.html) -If you provide the `outputFormat` parameter (`qa.ask(..., { outputFormat: ... })`), Ask Mode returns a `ParsedAskModeResponse` instead. This is an `AskModeResponse` extended with one more field, so it keeps every field above — including `finalAnswer`, which still holds the raw string from the model: +If you provide the `outputFormat` parameter (`qa.ask(..., { outputFormat: ... })`), Ask Mode returns a `ParsedAskModeResponse` instead. It is an `AskModeResponse` with one more field, so it keeps every field above. `finalAnswer` still holds the raw string from the model. | Field | Type | Description | | --- | --- | --- | diff --git a/docs/query-agent/reference/structured_outputs.md b/docs/query-agent/reference/structured_outputs.md index fccaa294..38120a78 100644 --- a/docs/query-agent/reference/structured_outputs.md +++ b/docs/query-agent/reference/structured_outputs.md @@ -20,7 +20,7 @@ Structured outputs are supported in **[Ask Mode](../guides/ask_mode.md) only.** Structured outputs require `weaviate-agents` **1.7.0 or later** in Python, and **1.6.0 or later** in JavaScript/TypeScript. The JavaScript/TypeScript examples that use a Zod schema also require **Zod 4 or later**. [See the installation page](../installation.md). ::: -The example outputs on this page are illustrative. The Query Agent is non-deterministic, so the wording, the level of detail, and the specific records it selects will vary between runs, even for an identical query and schema. The *shape* of the response is what the schema guarantees. +The example outputs on this page will not match yours exactly. The Query Agent is non-deterministic, so the wording and the records it picks vary between runs. Your schema controls the shape of the response, not the content. ## Basic usage @@ -146,7 +146,7 @@ The examples below use structured outputs to generate a set of metadata associat ``` - The `additionalProperties: false` entry above is what Zod's own JSON Schema conversion emits, so the two examples describe the same schema. It is optional — the Query Agent accepts a schema with or without it. + Zod adds `additionalProperties: false` when it converts a schema to JSON Schema, so both examples above describe the same thing. The field is optional. The Query Agent accepts a schema with or without it. @@ -399,7 +399,7 @@ The Query Agent natively supports subsetting and evaluating the quality of the r |---------|:----------:|-------| | Min / max number of items in an array | ✅ | | | Min / max value of a number property | ✅ | E.g. constrain a value to be within a certain range. | -| String formats: `uuid`, `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6` | ✅ | Constrains the model to generate a string in the shape of the given format. The value is not validated against the full specification for that format after generation, so ask for a field the retrieved data can actually populate. | +| String formats: `uuid`, `date-time`, `time`, `date`, `duration`, `email`, `hostname`, `ipv4`, `ipv6` | ✅ | Guides the model to produce a string in that format. The result is not checked afterwards, so a field your data cannot fill may still come back invalid. | | Regular expression (pattern) on a string | ✅ | | | Recursive schemas (a schema referencing itself) | ✅ | | | Default values (e.g. `x: int = 1`) | ❌ | The schema is accepted, but the field is always populated by the model, so the default is never used. Consider using nullable entries and transforming them afterwards. | From 1a60493b79bfacc1c912611e69cb207d79405fbe Mon Sep 17 00:00:00 2001 From: Ivan Despot <66276597+g-despot@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:09:04 +0200 Subject: [PATCH 6/6] docs: restore verbatim run output in two structured outputs examples The TS nested example had been trimmed from 8 entries to 6 with a reworded overall_summary, and the Python citations reasoning had its clauses reordered. Both now match the captured run output exactly. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01CxMtvQPuFNT2ERqHx7C49z --- docs/query-agent/reference/structured_outputs.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/query-agent/reference/structured_outputs.md b/docs/query-agent/reference/structured_outputs.md index 38120a78..a849cdaa 100644 --- a/docs/query-agent/reference/structured_outputs.md +++ b/docs/query-agent/reference/structured_outputs.md @@ -303,6 +303,12 @@ In the below example, the final response will generate a list of information for summary: 'Service agreement dated March 15, 2023, for consulting on artificial intelligence and data management systems, including implementation, training, and technical support. The total fee is $428.14, with a two-year term ending March 15, 2025.', contract_uuid: 'dbc30467-5f6a-4630-b7ea-7a17ade8b70a' }, + { + names_mentioned: [ 'Weaviate', 'Danny Williams', 'Kaladin Stormblessed' ], + contract_type: 'other', + summary: 'Duplicate record of the service agreement for artificial intelligence and data management systems consulting, implementation, training, and technical support. Total fee: $428.14; term ends March 15, 2025.', + contract_uuid: '010aeced-3328-415c-b93d-c19dc4baeefa' + }, { names_mentioned: [ 'Weaviate', 'OpenAI', 'Edward Elric', 'Mark Robson' ], contract_type: 'other', @@ -315,6 +321,12 @@ In the below example, the final response will generate a list of information for summary: 'Partnership agreement dated March 15, 2023, to collaborate on advancements in artificial intelligence technologies and develop innovative AI solutions. Weaviate contributes $177.98 and OpenAI contributes $67.09.', contract_uuid: '8ac31eff-9936-4a1b-a5a4-8e148a4596bf' }, + { + names_mentioned: [ 'Weaviate', 'OpenAI', 'Johnathan Smith', 'Mark Robson' ], + contract_type: 'other', + summary: 'Duplicate record of the partnership agreement to collaborate on artificial intelligence technologies and develop AI solutions. Weaviate contributes $177.98 and OpenAI contributes $67.09.', + contract_uuid: '6fcb6899-6344-41e0-8963-10b49bd17625' + }, { names_mentioned: [ 'Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson' ], contract_type: 'other', @@ -322,7 +334,7 @@ In the below example, the final response will generate a list of information for contract_uuid: 'e32c13b7-b552-45f6-b58b-3f0c325715ab' } ], - overall_summary: 'Several 2023 contracts concern AI or artificial-intelligence-related services and projects, comprising partnership agreements and AI-related service agreements. Duplicate records were consolidated by document identity.' + overall_summary: 'Eight unique 2023 contracts concern AI or artificial intelligence-related services and projects. Several duplicate records were omitted from the main list. The matching documents include five partnership agreements and three AI-related service-agreement records; no invoice was explicitly identified as AI-related based on its text.' } ``` @@ -349,7 +361,7 @@ The Query Agent natively supports subsetting and evaluating the quality of the r Example output ```python CitedAnswer( - reasoning='The latest dated contract in the provided records that explicitly concerns AI is the partnership agreement dated November 15, 2023 (doc_id 60.0), which covers collaboration on AI research and development. The March 15, 2024 contract is a sales agreement for unspecified products and does not mention AI.', + reasoning='The latest dated contract in the provided records that explicitly concerns AI is dated March 15, 2024, but it is a sales agreement for unspecified products and does not mention AI. The latest contract that explicitly concerns artificial intelligence is the partnership agreement dated November 15, 2023 (doc_id 60.0), which covers collaboration on AI research and development.', final_answer=[ CitedText( sentence='The most recent contract explicitly about AI is a Partnership Agreement dated November 15, 2023, between Weaviate and OpenAI (doc_id 60.0), authored by Johnathan Smith.',