Skip to content
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

Refactor DSPy adapters to make it more extensible #7996

Merged
merged 9 commits into from
Apr 1, 2025

Conversation

chenmoneygithub
Copy link
Collaborator

We are reworking DSPy adapters for extensibility. For most users this change shouldn't cause backward compatibility issues, but if your workflow explicitly calls child methods of DSPy adapters, you need to make adjustments.

The goal here is with DSPy 3.0, we want Adapter to be a customizable interface rather than some tribal knowledge. We acknowledge that it will be common for users willing to write their own adapter to adjust to their LLMs and workflows. However, the current adapter doesn't have a decent abstraction, and to write a custom adapter users need to understand the source code, and go through a tedious debugging process without guidelines.

In this PR, we are trying to standardize the dspy Adapters, and open a few hooks for people to override during customization. We are aware that there is no single standard that fits all use cases, but trying to hit a stage where we don't over-simplify or over-engineer the base DSPy Adapter/

In a nutshell, we are making the following breakdown of Adapters:

  • Adapter
    • format(): formats the type-based inputs into LM multiturn messages
      • System messages: The high level description of the task, and LM I/O format.
        • Fields description: format_field_description()
        • LM input/output structure description: format_field_structure()
        • task description: format_task_description
      • Few-shot examples (demo): multiturn few-shot examples
        • user message (inputs of demo): format_user_message_content()
        • assistant message (outputs of demo): format_assistant_message_content()
      • Conversation history: multiturn conversation history
        • user message (inputs of history message): format_user_message_content()
        • assistant message (outputs of history message): format_assistant_message_content()
      • Current input: the actual question/input
        • user message: format_user_message_content()
    • parse(): parse the LM response to type-based outputs. No sub-hook for parse() because it varies for different adapters.

Note that format_user_message_content() and format_assistant_message_content() are used in multiple places. Users can override any level of hooks for customization.

We will publish a guide on how to customize Adapter with concrete use cases after landing this PR.

@chenmoneygithub chenmoneygithub marked this pull request as draft March 21, 2025 23:17
@chenmoneygithub chenmoneygithub changed the title Refactor DSPy adapters to make it more extensible [WIP] Refactor DSPy adapters to make it more extensible Mar 21, 2025
@chenmoneygithub chenmoneygithub changed the title [WIP] Refactor DSPy adapters to make it more extensible Refactor DSPy adapters to make it more extensible Mar 21, 2025
@chenmoneygithub chenmoneygithub marked this pull request as ready for review March 21, 2025 23:17

class MySignature(dspy.Signature):
text: str = dspy.InputField(description="The text to analyze")
context: str = dspy.InputField(description="The context of the text")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this long docstring that will go stale as soon as someone changes the adapter slightly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplified for easy maintenance!

for k, v in signature.input_fields.items():
value = inputs[k]
formatted_field_value = format_field_value(field_info=v, value=value)
messages.append(f"[[ ## {k} ## ]]\n{formatted_field_value}")
Copy link
Collaborator

@okhat okhat Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very specific to chat adapter. Why does it belong in base.py?

Overall, base.py should be very short and lightweight to allow anyone to build any kind of adapter. Right now, this PR is introducing a lot of specific and complicated structure to base.py

conversation_history = self.format_conversation_history(signature, inputs_copy)
if conversation_history:
# Conversation history and current input
messages.extend(conversation_history)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we handle cases where the demos have multi-turn history?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be a single turn entity with history as one field, for example:

User message:

This is an example of the task, though some input or output fields are not supplied.

[[ ## question ## ]]
What is the capital of France?

[[ ## history ## ]]
[{"question": "What is the capital of Germany?", "answer": "Berlin"}]

Respond with the corresponding output fields, starting with the field `[[ ## reasoning ## ]]`, then `[[ ## answer ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`.


Assistant message:

[[ ## reasoning ## ]]
Not supplied for this particular example. 

[[ ## answer ## ]]
Paris

@chenmoneygithub chenmoneygithub requested a review from okhat April 1, 2025 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants