97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
98 lines
2.9 KiB
Python
98 lines
2.9 KiB
Python
from pydantic import BaseModel, Field, model_validator, ValidationInfo
|
|
from collections.abc import Generator
|
|
|
|
|
|
class CitationMixin(BaseModel):
|
|
"""
|
|
Helpful mixing that can use `validation_context={"context": context}` in `from_response` to find the span of the substring_phrase in the context.
|
|
|
|
## Usage
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
from instructor import CitationMixin
|
|
|
|
class User(BaseModel):
|
|
name: str = Field(description="The name of the person")
|
|
age: int = Field(description="The age of the person")
|
|
role: str = Field(description="The role of the person")
|
|
|
|
|
|
context = "Betty was a student. Jason was a student. Jason is 20 years old"
|
|
|
|
user = openai.ChatCompletion.create(
|
|
model="gpt-3.5-turbo",
|
|
messages=[
|
|
{
|
|
"role": "user",
|
|
"content": "Extract jason from {context}",
|
|
},
|
|
response_model=User,
|
|
validation_context={"context": context},
|
|
]
|
|
)
|
|
|
|
for quote in user.substring_quotes:
|
|
assert quote in context
|
|
|
|
print(user.model_dump())
|
|
```
|
|
|
|
## Result
|
|
```
|
|
{
|
|
"name": "Jason Liu",
|
|
"age": 20,
|
|
"role": "student",
|
|
"substring_quotes": [
|
|
"Jason was a student",
|
|
"Jason is 20 years old",
|
|
]
|
|
}
|
|
```
|
|
|
|
"""
|
|
|
|
substring_quotes: list[str] = Field(
|
|
description="List of unique and specific substrings of the quote that was used to answer the question.",
|
|
)
|
|
|
|
@model_validator(mode="after") # type: ignore[misc]
|
|
def validate_sources(self, info: ValidationInfo) -> "CitationMixin":
|
|
"""
|
|
For each substring_phrase, find the span of the substring_phrase in the context.
|
|
If the span is not found, remove the substring_phrase from the list.
|
|
"""
|
|
if info.context is None:
|
|
return self
|
|
|
|
# Get the context from the info
|
|
text_chunks = info.context.get("context", None)
|
|
|
|
# Get the spans of the substring_phrase in the context
|
|
spans = list(self.get_spans(text_chunks))
|
|
# Replace the substring_phrase with the actual substring
|
|
self.substring_quotes = [text_chunks[span[0] : span[1]] for span in spans]
|
|
return self
|
|
|
|
def _get_span(
|
|
self, quote: str, context: str, errs: int = 5
|
|
) -> Generator[tuple[int, int], None, None]:
|
|
import regex
|
|
|
|
minor = quote
|
|
major = context
|
|
|
|
errs_ = 0
|
|
s = regex.search(f"({minor}){{e<={errs_}}}", major)
|
|
while s is None and errs_ <= errs:
|
|
errs_ += 1
|
|
s = regex.search(f"({minor}){{e<={errs_}}}", major)
|
|
|
|
if s is not None:
|
|
yield from s.spans()
|
|
|
|
def get_spans(self, context: str) -> Generator[tuple[int, int], None, None]:
|
|
for quote in self.substring_quotes:
|
|
yield from self._get_span(quote, context)
|