chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import pytest
|
||||
from jinja2.exceptions import SecurityError
|
||||
from instructor.templating import handle_templating
|
||||
from instructor import Mode
|
||||
|
||||
|
||||
def test_handle_insecure_template():
|
||||
with pytest.raises(SecurityError):
|
||||
kwargs = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "{{ self.__init__.__globals__.__builtins__.__import__('os').system('ls') }} {{ variable }}",
|
||||
}
|
||||
]
|
||||
}
|
||||
context = {"variable": "test"}
|
||||
handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
|
||||
def test_handle_templating_with_context():
|
||||
kwargs = {"messages": [{"role": "user", "content": "Hello {{ name }}!"}]}
|
||||
context = {"name": "Alice"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {"messages": [{"role": "user", "content": "Hello Alice!"}]}
|
||||
|
||||
|
||||
def test_handle_templating_without_context():
|
||||
kwargs = {"messages": [{"role": "user", "content": "Hello {{ name }}!"}]}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS)
|
||||
|
||||
assert result == kwargs
|
||||
|
||||
|
||||
def test_handle_templating_with_anthropic_format():
|
||||
kwargs = {
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello {{ name }}!"}]}
|
||||
]
|
||||
}
|
||||
context = {"name": "Bob"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello Bob!"}]}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_handle_templating_with_mixed_content():
|
||||
kwargs = {
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello {{ name }}!"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "Nice to meet you, {{ name }}!"}],
|
||||
},
|
||||
]
|
||||
}
|
||||
context = {"name": "Charlie"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {
|
||||
"messages": [
|
||||
{"role": "user", "content": "Hello Charlie!"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [{"type": "text", "text": "Nice to meet you, Charlie!"}],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_handle_templating_with_secret_context():
|
||||
from pydantic import BaseModel, SecretStr
|
||||
|
||||
class UserContext(BaseModel):
|
||||
name: str
|
||||
address: SecretStr
|
||||
|
||||
kwargs = {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "{{ user.name }}'s address is '{{ user.address.get_secret_value() }}'",
|
||||
}
|
||||
]
|
||||
}
|
||||
context = {
|
||||
"user": UserContext(
|
||||
name="Jason", address=SecretStr("123 Secret St, Hidden City")
|
||||
)
|
||||
}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Jason's address is '123 Secret St, Hidden City'",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Ensure the original SecretStr is not exposed when rendered
|
||||
assert str(context["user"].address) == "**********"
|
||||
|
||||
|
||||
def test_handle_templating_with_cohere_format():
|
||||
kwargs = {
|
||||
"message": "Hello {{ name }}!",
|
||||
"chat_history": [{"message": "Previous message to {{ name }}"}],
|
||||
}
|
||||
context = {"name": "David"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {
|
||||
"message": "Hello David!",
|
||||
"chat_history": [{"message": "Previous message to David"}],
|
||||
}
|
||||
|
||||
|
||||
def test_handle_templating_with_gemini_format():
|
||||
kwargs = {
|
||||
"contents": [
|
||||
{"role": "user", "parts": ["Hello {{ name }}!", "How are you {{ name }}?"]}
|
||||
]
|
||||
}
|
||||
context = {"name": "Eve"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.TOOLS, context)
|
||||
|
||||
assert result == {
|
||||
"contents": [{"role": "user", "parts": ["Hello Eve!", "How are you Eve?"]}]
|
||||
}
|
||||
|
||||
|
||||
def test_handle_templating_with_genai_multimodal_parts():
|
||||
"""Non-text Parts (images/URIs) have text=None and must pass through untouched.
|
||||
|
||||
Regression test for #2253: apply_template was being called with None,
|
||||
crashing Jinja2 with "Can't compile non template nodes".
|
||||
"""
|
||||
from google.genai import types
|
||||
|
||||
text_part = types.Part.from_text(text="Describe {{ subject }}")
|
||||
image_part = types.Part.from_uri(
|
||||
file_uri="gs://example/cat.png", mime_type="image/png"
|
||||
)
|
||||
content = types.Content(role="user", parts=[text_part, image_part])
|
||||
|
||||
kwargs = {"contents": [content]}
|
||||
context = {"subject": "this image"}
|
||||
|
||||
result = handle_templating(kwargs, Mode.GENAI_TOOLS, context)
|
||||
|
||||
processed = result["contents"][0]
|
||||
assert processed.parts[0].text == "Describe this image"
|
||||
assert processed.parts[1].text is None
|
||||
assert processed.parts[1].file_data.file_uri == "gs://example/cat.png"
|
||||
Reference in New Issue
Block a user