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..d3537e6c --- /dev/null +++ b/docs/query-agent/_includes/code/structured_outputs.mts @@ -0,0 +1,118 @@ +import 'dotenv/config' +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 +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 res = 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(res.finalAnswerParsed); +// END SOBasicDictExample +} + + +{ +// START SOReasoningExample +const FinalAnswer = z.object({ + reasoning: z.string(), + final_answer: z.string(), +}); + +const res = await qa.ask("What is the most recent contract about AI?", { outputFormat: FinalAnswer }); + +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"), + 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 res = await qa.ask("Find and return all contracts about AI in 2023", { outputFormat: ContractInfoResponse }); + +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"), + 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 res = await qa.ask("What is the most recent contract about AI?", { outputFormat: CitedAnswer }); + +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 new file mode 100644 index 00000000..2288f99f --- /dev/null +++ b/docs/query-agent/_includes/code/structured_outputs.py @@ -0,0 +1,105 @@ +import sys +sys.path.insert(0, "docs/query-agent/_includes/code") +from util import load_client_internally, populate_weaviate + +client = load_client_internally() +populate_weaviate(client, False) + + +# 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, Field +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 + +client.close() diff --git a/docs/query-agent/guides/ask_mode.md b/docs/query-agent/guides/ask_mode.md index 9913257a..85cfee2d 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. 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). | @@ -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). 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,6 +101,14 @@ The `AskModeResponse` class has the following properties: | `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`. | [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. 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 | +| --- | --- | --- | +| `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. @@ -116,6 +125,16 @@ The `AskModeResponse` class has the following properties: | `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`. | [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. 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 | +| --- | --- | --- | +| `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/ParsedAskModeResponse.html) diff --git a/docs/query-agent/reference/index.md b/docs/query-agent/reference/index.md index f121d088..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. ## 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..a849cdaa --- /dev/null +++ b/docs/query-agent/reference/structured_outputs.md @@ -0,0 +1,435 @@ +--- +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 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. + +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 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 + +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)): + + + + + + + + + + +The examples below 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 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.0', + contract_title='Employment Contract', + auto_renew=False, + parties_involved=['Weaviate (Employer)', 'Mark Robson (Employee)', 'Hans Zimmer (Chief Executive Officer/signatory)'], + requires_action=True + ) + ``` +
+ + + **Dictionary** + + +
+ Example output + ```python + { + 'contract_id': '46.0', + 'contract_title': 'Employment Contract', + 'auto_renew': False, + 'parties_involved': ['Weaviate (Employer)', 'Mark Robson (Employee)', 'Hans Zimmer, Chief Executive Officer'], + '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. + + 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.0', + contract_title: 'Employment Contract', + auto_renew: false, + parties_involved: [ 'Weaviate (Employer)', 'Mark Robson (Employee)' ], + requires_action: true + } + ``` +
+ + **Object** + +
+ Example output + ```typescript + { + contract_id: '46', + contract_title: 'Employment Contract', + auto_renew: false, + parties_involved: [ + 'Weaviate (Employer)', + 'Mark Robson (Employee)', + 'Hans Zimmer (Chief Executive Officer and signatory)' + ], + requires_action: true + } + ``` +
+ + 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. + +
+
+ +## 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='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' + ) + ``` +
+
+ + +
+ Example output + ```typescript + { + 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 + +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. + + + + + 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=['Weaviate', 'OpenAI', 'Mark Robson', 'Kaladin Stormblessed'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Arthur Penndragon', 'Mark Robson', 'Danny Williams'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Edward Elric', 'Mark Robson'], + contract_type='other', + 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=['Weaviate', 'OpenAI'], + contract_type='other', + 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', 'OpenAI', 'Johnathan Smith', 'Mark Robson'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Johnathan Smith', 'Mark Robson'], + contract_type='other', + 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=['Weaviate', 'OpenAI', 'Alice Johnson', 'Danny Williams'], + contract_type='other', + 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='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 `.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: [ 'Weaviate', 'OpenAI', 'Alice Johnson', 'Mark Robson' ], + contract_type: 'other', + summary: 'Partnership agreement dated March 15, 2023, between Weaviate and OpenAI to collaborate on artificial intelligence projects. Weaviate contributes $210.97 and OpenAI contributes $194.05; OpenAI is responsible for technical development and AI research expertise.', + contract_uuid: '056b6b5c-d6d3-4235-9003-4822dbbef9ef' + }, + { + names_mentioned: [ 'Weaviate', 'Danny Williams', 'Alice Johnson' ], + contract_type: 'other', + summary: 'Service agreement dated March 15, 2023, for AI development and consulting services. The total compensation is $249.44, payable in two installments, with a two-year term ending March 15, 2025.', + contract_uuid: '1363c2ae-83e6-4049-8f95-cd6c873a997e' + }, + { + names_mentioned: [ 'Weaviate', 'Danny Williams', 'Kaladin Stormblessed' ], + contract_type: 'other', + 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', + summary: 'Partnership agreement dated March 15, 2023, for developing advanced data processing technologies. OpenAI contributes AI and machine-learning expertise valued at $567.91, while Weaviate provides technological support and resources valued at $416.56. Revenue is split 60% to Weaviate and 40% to OpenAI.', + contract_uuid: '3cec1521-3d26-46b3-b63f-1af2ddaa3db4' + }, + { + names_mentioned: [ 'Weaviate', 'OpenAI', 'Johnathan Smith', 'Mark Robson' ], + contract_type: 'other', + 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', + summary: 'Partnership agreement dated May 15, 2023, for developing AI-driven solutions to improve data management and retrieval. Weaviate contributes $391.74 and OpenAI contributes $302.40; profits are split 60% and 40%, respectively.', + contract_uuid: 'e32c13b7-b552-45f6-b58b-3f0c325715ab' + } + ], + 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.' + } + ``` +
+
+
+ +## 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 the Ask Mode parameters for more details](../guides/ask_mode.md#parameters). +::: + + + + +
+ Example output + ```python + CitedAnswer( + 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.', + sources=[UUID('a06ab40a-bcc3-4fc2-bb8a-b2b2598f706c')] + ), + CitedText( + 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')] + ) + ] + ) + ``` +
+
+ + +
+ Example output + ```typescript + { + 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 a Partnership Agreement dated May 15, 2023, authored by Alice Johnson, with document ID 56.0.', + sources: [ 'e32c13b7-b552-45f6-b58b-3f0c325715ab' ] + }, + { + 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' ] + } + ] + } + ``` +
+
+
+ +## 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` | ✅ | 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. | +| 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 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 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. Read the `final_answer_parsed` attribute (`finalAnswerParsed` in TypeScript) of the [final state output](../guides/ask_mode.md#responses). + +## Questions and feedback + +import DocsFeedback from '/\_includes/docs-feedback.mdx'; + + 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/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", + }, ], }, { 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"