Skip to content

Commit 5e72f7b

Browse files
Add a util function to extract system message (#9006)
1 parent 9dafdcc commit 5e72f7b

File tree

5 files changed

+142
-5
lines changed

5 files changed

+142
-5
lines changed

docs/docs/learn/programming/adapters.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,34 @@ The output should resemble:
7474
{'role': 'user', 'content': '[[ ## question ## ]]\nWhat is 2+2?\n\nRespond with the corresponding output fields, starting with the field `[[ ## answer ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`.'}
7575
```
7676

77+
You can also only fetch the system message by calling `adapter.format_system_message(signature)`.
78+
79+
```python
80+
import dspy
81+
82+
signature = dspy.Signature("question -> answer")
83+
system_message = dspy.ChatAdapter().format_system_message(signature)
84+
print(system_message)
85+
```
86+
87+
The output should resemble:
88+
89+
```
90+
Your input fields are:
91+
1. `question` (str):
92+
Your output fields are:
93+
1. `answer` (str):
94+
All interactions will be structured in the following way, with the appropriate values filled in.
95+
96+
[[ ## question ## ]]
97+
{question}
98+
[[ ## answer ## ]]
99+
{answer}
100+
[[ ## completed ## ]]
101+
In adhering to this structure, your objective is:
102+
Given the fields `question`, produce the fields `answer`.
103+
```
104+
77105
## Types of Adapters
78106

79107
DSPy offers several adapter types, each tailored for specific use cases:

dspy/adapters/base.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,7 @@ def format(
272272
)
273273

274274
messages = []
275-
system_message = (
276-
f"{self.format_field_description(signature)}\n"
277-
f"{self.format_field_structure(signature)}\n"
278-
f"{self.format_task_description(signature)}"
279-
)
275+
system_message = self.format_system_message(signature)
280276
messages.append({"role": "system", "content": system_message})
281277
messages.extend(self.format_demos(signature, demos))
282278
if history_field_name:
@@ -292,6 +288,19 @@ def format(
292288
messages = split_message_content_for_custom_types(messages)
293289
return messages
294290

291+
def format_system_message(self, signature: type[Signature]) -> str:
292+
"""Format the system message for the LM call.
293+
294+
295+
Args:
296+
signature: The DSPy signature for which to format the system message.
297+
"""
298+
return (
299+
f"{self.format_field_description(signature)}\n"
300+
f"{self.format_field_structure(signature)}\n"
301+
f"{self.format_task_description(signature)}"
302+
)
303+
295304
def format_field_description(self, signature: type[Signature]) -> str:
296305
"""Format the field description for the system message.
297306

tests/adapters/test_chat_adapter.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ def test_chat_adapter_fallback_to_json_adapter_on_exception():
442442
result = adapter(lm, {}, signature, [], {"question": "What is the capital of France?"})
443443
assert result == [{"answer": "Paris"}]
444444

445+
445446
def test_chat_adapter_respects_use_json_adapter_fallback_flag():
446447
signature = dspy.make_signature("question->answer")
447448
adapter = dspy.ChatAdapter(use_json_adapter_fallback=False)
@@ -608,3 +609,35 @@ def get_weather(city: str) -> str:
608609
assert result[0]["tool_calls"] == dspy.ToolCalls(
609610
tool_calls=[dspy.ToolCalls.ToolCall(name="get_weather", args={"city": "Paris"})]
610611
)
612+
613+
614+
def test_format_system_message():
615+
class MySignature(dspy.Signature):
616+
"""Answer the question with multiple answers and scores"""
617+
618+
question: str = dspy.InputField()
619+
answers: list[str] = dspy.OutputField()
620+
scores: list[float] = dspy.OutputField()
621+
622+
adapter = dspy.ChatAdapter()
623+
system_message = adapter.format_system_message(MySignature)
624+
expected_system_message = """Your input fields are:
625+
1. `question` (str):
626+
Your output fields are:
627+
1. `answers` (list[str]):
628+
2. `scores` (list[float]):
629+
All interactions will be structured in the following way, with the appropriate values filled in.
630+
631+
[[ ## question ## ]]
632+
{question}
633+
634+
[[ ## answers ## ]]
635+
{answers} # note: the value you produce must adhere to the JSON schema: {"type": "array", "items": {"type": "string"}}
636+
637+
[[ ## scores ## ]]
638+
{scores} # note: the value you produce must adhere to the JSON schema: {"type": "array", "items": {"type": "number"}}
639+
640+
[[ ## completed ## ]]
641+
In adhering to this structure, your objective is:
642+
Answer the question with multiple answers and scores"""
643+
assert system_message == expected_system_message

tests/adapters/test_json_adapter.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,3 +942,36 @@ class TestSignature(dspy.Signature):
942942
assert "text" in call_kwargs
943943
assert isinstance(call_kwargs["text"]["format"], type)
944944
assert issubclass(call_kwargs["text"]["format"], pydantic.BaseModel)
945+
946+
947+
def test_format_system_message():
948+
class MySignature(dspy.Signature):
949+
"""Answer the question with multiple answers and scores"""
950+
951+
question: str = dspy.InputField()
952+
answers: list[str] = dspy.OutputField()
953+
scores: list[float] = dspy.OutputField()
954+
955+
adapter = dspy.JSONAdapter()
956+
system_message = adapter.format_system_message(MySignature)
957+
expected_system_message = """Your input fields are:
958+
1. `question` (str):
959+
Your output fields are:
960+
1. `answers` (list[str]):
961+
2. `scores` (list[float]):
962+
All interactions will be structured in the following way, with the appropriate values filled in.
963+
964+
Inputs will have the following structure:
965+
966+
[[ ## question ## ]]
967+
{question}
968+
969+
Outputs will be a JSON object with the following fields.
970+
971+
{
972+
"answers": "{answers} # note: the value you produce must adhere to the JSON schema: {\\"type\\": \\"array\\", \\"items\\": {\\"type\\": \\"string\\"}}",
973+
"scores": "{scores} # note: the value you produce must adhere to the JSON schema: {\\"type\\": \\"array\\", \\"items\\": {\\"type\\": \\"number\\"}}"
974+
}
975+
In adhering to this structure, your objective is:
976+
Answer the question with multiple answers and scores"""
977+
assert system_message == expected_system_message

tests/adapters/test_xml_adapter.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,37 @@ class QA(dspy.Signature):
297297

298298
assert messages[0]["content"] == expected_system
299299
assert messages[1]["content"] == expected_user
300+
301+
302+
def test_format_system_message():
303+
class MySignature(dspy.Signature):
304+
"""Answer the question with multiple answers and scores"""
305+
306+
question: str = dspy.InputField()
307+
answers: list[str] = dspy.OutputField()
308+
scores: list[float] = dspy.OutputField()
309+
310+
adapter = dspy.XMLAdapter()
311+
system_message = adapter.format_system_message(MySignature)
312+
313+
expected_system_message = """Your input fields are:
314+
1. `question` (str):
315+
Your output fields are:
316+
1. `answers` (list[str]):
317+
2. `scores` (list[float]):
318+
All interactions will be structured in the following way, with the appropriate values filled in.
319+
320+
<question>
321+
{question}
322+
</question>
323+
324+
<answers>
325+
{answers} # note: the value you produce must adhere to the JSON schema: {"type": "array", "items": {"type": "string"}}
326+
</answers>
327+
328+
<scores>
329+
{scores} # note: the value you produce must adhere to the JSON schema: {"type": "array", "items": {"type": "number"}}
330+
</scores>
331+
In adhering to this structure, your objective is:
332+
Answer the question with multiple answers and scores"""
333+
assert system_message == expected_system_message

0 commit comments

Comments
 (0)