Skip to content

Increased test coverage for ChatAdapter #8168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 21, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions tests/adapters/test_chat_adapter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Literal
from unittest import mock

import pydantic
import pytest

import dspy
Expand Down Expand Up @@ -97,6 +98,106 @@ async def test_chat_adapter_async_call():
assert result == [{"answer": "Paris"}]


def test_chat_adapter_with_pydantic_models():
"""
This test verifies that ChatAdapter can handle different input and output field types, both basic and nested.
"""

class DogClass(pydantic.BaseModel):
dog_breeds: list[str] = pydantic.Field(description="List of the breeds of dogs")
num_dogs: int = pydantic.Field(description="Number of dogs the owner has", ge=0, le=10)

class PetOwner(pydantic.BaseModel):
name: str = pydantic.Field(description="Name of the owner")
num_pets: int = pydantic.Field(description="Amount of pets the owner has", ge=0, le=100)
dogs: DogClass = pydantic.Field(description="Nested Pydantic class with dog specific information ")

class Answer(pydantic.BaseModel):
result: str
analysis: str

class TestSignature(dspy.Signature):
owner: PetOwner = dspy.InputField()
question: str = dspy.InputField()
output: Answer = dspy.OutputField()

dspy.configure(lm=dspy.LM(model="openai/gpt4o"), adapter=dspy.ChatAdapter())
program = dspy.Predict(TestSignature)

with mock.patch("litellm.completion") as mock_completion:
program(
owner=PetOwner(name="John", num_pets=5, dogs=DogClass(dog_breeds=["labrador", "chihuahua"], num_dogs=2)),
question="How many non-dog pets does John have?",
)

mock_completion.assert_called_once()
_, call_kwargs = mock_completion.call_args

system_content = call_kwargs["messages"][0]["content"]
user_content = call_kwargs["messages"][1]["content"]
assert "1. `owner` (PetOwner)" in system_content
assert "2. `question` (str)" in system_content
assert "1. `output` (Answer)" in system_content

assert "name" in user_content
assert "num_pets" in user_content
assert "dogs" in user_content
assert "dog_breeds" in user_content
assert "num_dogs" in user_content
assert "How many non-dog pets does John have?" in user_content


def test_chat_adapter_signature_information():
"""
This test ensures that the signature information sent to the LM follows an expected format.
"""

class TestSignature(dspy.Signature):
input1: str = dspy.InputField(desc="String Input")
input2: int = dspy.InputField(desc="Integer Input")
output: str = dspy.OutputField(desc="String Output")

dspy.configure(lm=dspy.LM(model="openai/gpt4o"), adapter=dspy.ChatAdapter())
program = dspy.Predict(TestSignature)

with mock.patch("litellm.completion") as mock_completion:
program(input1="Test", input2=11)

mock_completion.assert_called_once()
_, call_kwargs = mock_completion.call_args

assert len(call_kwargs["messages"]) == 2
assert call_kwargs["messages"][0]["role"] == "system"
assert call_kwargs["messages"][1]["role"] == "user"

system_content = call_kwargs["messages"][0]["content"]
user_content = call_kwargs["messages"][1]["content"]

assert "1. `input1` (str)" in system_content
assert "2. `input2` (int)" in system_content
assert "1. `output` (str)" in system_content
assert "[[ ## input1 ## ]]\n{input1}" in system_content
assert "[[ ## input2 ## ]]\n{input2}" in system_content
assert "[[ ## output ## ]]\n{output}" in system_content
assert "[[ ## completed ## ]]" in system_content

assert "[[ ## input1 ## ]]" in user_content
assert "[[ ## input2 ## ]]" in user_content
assert "[[ ## output ## ]]" in user_content
assert "[[ ## completed ## ]]" in user_content


def test_chat_adapter_exception_raised_on_failure():
"""
This test ensures that on an error, ChatAdapter raises an explicit exception.
"""
signature = dspy.make_signature("question->answer")
adapter = dspy.ChatAdapter()
invalid_completion = "{'output':'mismatched value'}"
with pytest.raises(dspy.utils.exceptions.AdapterParseError, match="Adapter ChatAdapter failed to parse*"):
adapter.parse(signature, invalid_completion)


def test_chat_adapter_formats_image():
# Test basic image formatting
image = dspy.Image(url="https://example.com/image.jpg")
Expand Down