From 9fa3ca42fc3badab2a2aa8c96ba3ad73608b99c1 Mon Sep 17 00:00:00 2001 From: Isaac Miller Date: Fri, 12 Sep 2025 11:14:17 +0100 Subject: [PATCH 1/5] Change DummyLM to properly look at current adapter and switch JSONAdpater tests to be correcT --- dspy/utils/dummies.py | 25 +++++++++++++++++++------ tests/adapters/test_json_adapter.py | 10 ++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/dspy/utils/dummies.py b/dspy/utils/dummies.py index 6e83d11266..c6b833cf88 100644 --- a/dspy/utils/dummies.py +++ b/dspy/utils/dummies.py @@ -9,6 +9,8 @@ from dspy.dsp.utils.utils import dotdict from dspy.signatures.field import OutputField from dspy.utils.callback import with_callbacks +from dspy.dsp.utils.settings import settings + class DummyLM(LM): @@ -94,12 +96,23 @@ def _use_example(self, messages): @with_callbacks def __call__(self, prompt=None, messages=None, **kwargs): def format_answer_fields(field_names_and_values: dict[str, Any]): - return ChatAdapter().format_field_with_value( - fields_with_values={ - FieldInfoWithName(name=field_name, info=OutputField()): value - for field_name, value in field_names_and_values.items() - } - ) + fields_with_values = { + FieldInfoWithName(name=field_name, info=OutputField()): value + for field_name, value in field_names_and_values.items() + } + # Use the current adapter if available + adapter = settings.adapter + if adapter is None: + # Fallback to ChatAdapter if no adapter is set + from dspy.adapters.chat_adapter import ChatAdapter + adapter = ChatAdapter() + + # Try to use role="assistant" if the adapter supports it (like JSONAdapter) + try: + return adapter.format_field_with_value(fields_with_values, role="assistant") + except TypeError: + # Fallback for adapters that don't support role parameter (like ChatAdapter) + return adapter.format_field_with_value(fields_with_values) # Build the request. outputs = [] diff --git a/tests/adapters/test_json_adapter.py b/tests/adapters/test_json_adapter.py index 632dc4d171..1a9af14d3c 100644 --- a/tests/adapters/test_json_adapter.py +++ b/tests/adapters/test_json_adapter.py @@ -106,18 +106,20 @@ class TestSignature(dspy.Signature): def test_json_adapter_sync_call(): signature = dspy.make_signature("question->answer") - adapter = dspy.ChatAdapter() + adapter = dspy.JSONAdapter() lm = dspy.utils.DummyLM([{"answer": "Paris"}]) - result = adapter(lm, {}, signature, [], {"question": "What is the capital of France?"}) + with dspy.context(adapter=adapter): + result = adapter(lm, {}, signature, [], {"question": "What is the capital of France?"}) assert result == [{"answer": "Paris"}] @pytest.mark.asyncio async def test_json_adapter_async_call(): signature = dspy.make_signature("question->answer") - adapter = dspy.ChatAdapter() + adapter = dspy.JSONAdapter() lm = dspy.utils.DummyLM([{"answer": "Paris"}]) - result = await adapter.acall(lm, {}, signature, [], {"question": "What is the capital of France?"}) + with dspy.context(adapter=adapter): + result = await adapter.acall(lm, {}, signature, [], {"question": "What is the capital of France?"}) assert result == [{"answer": "Paris"}] From cc9a82db1a0fa3d4c3dc2e4d2fc02367d559e9e4 Mon Sep 17 00:00:00 2001 From: Isaac Miller Date: Fri, 12 Sep 2025 11:22:20 +0100 Subject: [PATCH 2/5] ruff --- dspy/utils/dummies.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dspy/utils/dummies.py b/dspy/utils/dummies.py index c6b833cf88..2f48146515 100644 --- a/dspy/utils/dummies.py +++ b/dspy/utils/dummies.py @@ -4,13 +4,12 @@ import numpy as np -from dspy.adapters.chat_adapter import ChatAdapter, FieldInfoWithName, field_header_pattern +from dspy.adapters.chat_adapter import FieldInfoWithName, field_header_pattern from dspy.clients.lm import LM +from dspy.dsp.utils.settings import settings from dspy.dsp.utils.utils import dotdict from dspy.signatures.field import OutputField from dspy.utils.callback import with_callbacks -from dspy.dsp.utils.settings import settings - class DummyLM(LM): From 2d757a5618771c98cc2f2db78a975f4997deaa1f Mon Sep 17 00:00:00 2001 From: Isaac Miller Date: Fri, 12 Sep 2025 15:59:16 +0100 Subject: [PATCH 3/5] Refactor dummy to take adapter at init --- dspy/utils/dummies.py | 16 +++++++++------- tests/adapters/test_json_adapter.py | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dspy/utils/dummies.py b/dspy/utils/dummies.py index 2f48146515..0971d87659 100644 --- a/dspy/utils/dummies.py +++ b/dspy/utils/dummies.py @@ -68,12 +68,18 @@ class DummyLM(LM): """ - def __init__(self, answers: list[dict[str, str]] | dict[str, dict[str, str]], follow_examples: bool = False): + def __init__(self, answers: list[dict[str, str]] | dict[str, dict[str, str]], follow_examples: bool = False, adapter=None): super().__init__("dummy", "chat", 0.0, 1000, True) self.answers = answers if isinstance(answers, list): self.answers = iter(answers) self.follow_examples = follow_examples + + # Set adapter, defaulting to ChatAdapter + if adapter is None: + from dspy.adapters.chat_adapter import ChatAdapter + adapter = ChatAdapter() + self.adapter = adapter def _use_example(self, messages): # find all field names @@ -99,12 +105,8 @@ def format_answer_fields(field_names_and_values: dict[str, Any]): FieldInfoWithName(name=field_name, info=OutputField()): value for field_name, value in field_names_and_values.items() } - # Use the current adapter if available - adapter = settings.adapter - if adapter is None: - # Fallback to ChatAdapter if no adapter is set - from dspy.adapters.chat_adapter import ChatAdapter - adapter = ChatAdapter() + # Use the instance adapter (ignore context) + adapter = self.adapter # Try to use role="assistant" if the adapter supports it (like JSONAdapter) try: diff --git a/tests/adapters/test_json_adapter.py b/tests/adapters/test_json_adapter.py index 1a9af14d3c..7d703b5e91 100644 --- a/tests/adapters/test_json_adapter.py +++ b/tests/adapters/test_json_adapter.py @@ -107,7 +107,7 @@ class TestSignature(dspy.Signature): def test_json_adapter_sync_call(): signature = dspy.make_signature("question->answer") adapter = dspy.JSONAdapter() - lm = dspy.utils.DummyLM([{"answer": "Paris"}]) + lm = dspy.utils.DummyLM([{"answer": "Paris"}], adapter=adapter) with dspy.context(adapter=adapter): result = adapter(lm, {}, signature, [], {"question": "What is the capital of France?"}) assert result == [{"answer": "Paris"}] @@ -117,7 +117,7 @@ def test_json_adapter_sync_call(): async def test_json_adapter_async_call(): signature = dspy.make_signature("question->answer") adapter = dspy.JSONAdapter() - lm = dspy.utils.DummyLM([{"answer": "Paris"}]) + lm = dspy.utils.DummyLM([{"answer": "Paris"}], adapter=adapter) with dspy.context(adapter=adapter): result = await adapter.acall(lm, {}, signature, [], {"question": "What is the capital of France?"}) assert result == [{"answer": "Paris"}] From b28add1dd2d360e75903831ada1b531a21d8f5ef Mon Sep 17 00:00:00 2001 From: Isaac Miller Date: Fri, 12 Sep 2025 16:00:05 +0100 Subject: [PATCH 4/5] ruff --- dspy/utils/dummies.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dspy/utils/dummies.py b/dspy/utils/dummies.py index 0971d87659..c044daa346 100644 --- a/dspy/utils/dummies.py +++ b/dspy/utils/dummies.py @@ -6,7 +6,6 @@ from dspy.adapters.chat_adapter import FieldInfoWithName, field_header_pattern from dspy.clients.lm import LM -from dspy.dsp.utils.settings import settings from dspy.dsp.utils.utils import dotdict from dspy.signatures.field import OutputField from dspy.utils.callback import with_callbacks @@ -74,7 +73,7 @@ def __init__(self, answers: list[dict[str, str]] | dict[str, dict[str, str]], fo if isinstance(answers, list): self.answers = iter(answers) self.follow_examples = follow_examples - + # Set adapter, defaulting to ChatAdapter if adapter is None: from dspy.adapters.chat_adapter import ChatAdapter From e282b99b4249329212484da2c01e1910b5625eee Mon Sep 17 00:00:00 2001 From: Isaac Miller Date: Fri, 12 Sep 2025 16:05:14 +0100 Subject: [PATCH 5/5] add comment --- dspy/utils/dummies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dspy/utils/dummies.py b/dspy/utils/dummies.py index c044daa346..20d54a6239 100644 --- a/dspy/utils/dummies.py +++ b/dspy/utils/dummies.py @@ -104,7 +104,8 @@ def format_answer_fields(field_names_and_values: dict[str, Any]): FieldInfoWithName(name=field_name, info=OutputField()): value for field_name, value in field_names_and_values.items() } - # Use the instance adapter (ignore context) + # The reason why DummyLM needs an adapter is because it needs to know which output format to mimic. + # Normally LMs should not have any knowledge of an adapter, because the output format is defined in the prompt. adapter = self.adapter # Try to use role="assistant" if the adapter supports it (like JSONAdapter)