diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fcbd6f1b..06c901544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] ### Fixed +- **Citation validation**: Preserve LLM-provided citations when a validation context does not include source text, instead of raising a `TypeError`. - **v2 message handling**: Preserve caller-owned message lists and nested content across request preparation and retries for OpenAI-compatible, Cohere, Mistral, OpenRouter, Writer, and xAI handlers. ([#2417](https://github.com/567-labs/instructor/issues/2417), [#2428](https://github.com/567-labs/instructor/issues/2428)) - **v2 JSON extraction**: Prefer the final complete top-level JSON value in text responses and retain every JSON object when multiple objects arrive in one streaming chunk. - **v2 schemas**: Treat fields with Pydantic `default_factory` values as optional in generated OpenAI tool schemas. diff --git a/instructor/v2/dsl/citation.py b/instructor/v2/dsl/citation.py index c5ccd4f04..a1cc3e399 100644 --- a/instructor/v2/dsl/citation.py +++ b/instructor/v2/dsl/citation.py @@ -68,6 +68,8 @@ def validate_sources(self, info: ValidationInfo) -> "CitationMixin": # Get the context from the info text_chunks = info.context.get("context", None) + if text_chunks is None: + return self # Get the spans of the substring_phrase in the context spans = list(self.get_spans(text_chunks)) diff --git a/tests/coverage/test_dsl_small_coverage.py b/tests/coverage/test_dsl_small_coverage.py index 99564fff6..35da3d889 100644 --- a/tests/coverage/test_dsl_small_coverage.py +++ b/tests/coverage/test_dsl_small_coverage.py @@ -46,6 +46,17 @@ def test_citation_keeps_quotes_without_context_and_recovers_fuzzy_quotes() -> No assert list(cited.get_spans(context)) == [(21, 42)] +def test_citation_keeps_quotes_when_validation_context_lacks_context_key() -> None: + quotes = ["Jaxon is 20 years old"] + + cited = Claim.model_validate( + {"fact": "age", "substring_quotes": quotes}, + context={"request_id": "req_123"}, + ) + + assert cited.substring_quotes == quotes + + @pytest.mark.parametrize( ("value", "complete"), [("", False), (" \n\t", False), ('{"ok": true}', True), ('{"ok":', False)],