darinkishore / dspy

Stanford DSPy: The framework for programming with foundation models
MIT License
0 stars 0 forks source link

Sweep: Add docstrings to `signature`. #66

Open darinkishore opened 7 months ago

darinkishore commented 7 months ago

Details

Please add genuinely useful (ie: not! trivial!!) docstrings to dspy/signatures/signature.py and field.py (same dir).

Below is some documentation on Signatures, from the readme:

#### 3.a) Declaring the input/output behavior of LMs with `dspy.Signature`

When we assign tasks to LMs in **DSPy**, we specify the behavior we need as a **Signature**. A signature is a declarative specification of input/output behavior of a **DSPy module**.

Instead of investing effort into _how_ to get your LM to do a sub-task, signatures enable you to inform **DSPy** _what_ the sub-task is. Later, the **DSPy compiler** will figure out how to build a complex prompt for your large LM (or finetune your small LM) specifically for your signature, on your data, and within your pipeline.

A signature consists of three simple elements:

- A minimal description of the sub-task the LM is supposed to solve.
- A description of one or more input fields (e.g., input question) that will we will give to the LM.
- A description of one or more output fields (e.g., the question's answer) that we will expect from the LM.

We support two notations for expressing signatures. The **short-hand signature notation** is for quick development. You just provide your module (e.g., `dspy.ChainOfThought`) with a string with `input_field_name_1, ... -> output_field_name_1, ...` with the fields separated by commas.

In the `RAG` class earlier, we saw:

```python
self.generate_answer = dspy.ChainOfThought("context, question -> answer")

In many cases, this barebones signature is sufficient. However, sometimes you need more control. In these cases, we can use the full notation to express a more fully-fledged signature below.

class GenerateSearchQuery(dspy.Signature):
    """Write a simple search query that will help answer a complex question."""

    context = dspy.InputField(desc="may contain relevant facts")
    question = dspy.InputField()
    query = dspy.OutputField()

### inside your program's __init__ function
self.generate_answer = dspy.ChainOfThought(GenerateSearchQuery)

You can optionally provide a prefix and/or desc key for each input or output field to refine or constraint the behavior of modules using your signature. The description of the sub-task itself is specified as the docstring (i.e., """Write a simple...""").



<details open>
<summary>Checklist</summary>

- [X] Modify `dspy/signatures/field.py` ✓ https://github.com/darinkishore/dspy/commit/bfea0337c513fe182d221707e13d724d8e6ed7df [Edit](https://github.com/darinkishore/dspy/edit/sweep/add_docstrings_to_signature/dspy/signatures/field.py#L4-L30)
- [X] Running GitHub Actions for `dspy/signatures/field.py` ✓  [Edit](https://github.com/darinkishore/dspy/edit/sweep/add_docstrings_to_signature/dspy/signatures/field.py#L4-L30)
- [X] Modify `dspy/signatures/signature.py` ✓ https://github.com/darinkishore/dspy/commit/7ad290f13bf8d1ba7a2b6f4e695b17c1974ed6d4 [Edit](https://github.com/darinkishore/dspy/edit/sweep/add_docstrings_to_signature/dspy/signatures/signature.py#L34-L100)
- [X] Running GitHub Actions for `dspy/signatures/signature.py` ✓  [Edit](https://github.com/darinkishore/dspy/edit/sweep/add_docstrings_to_signature/dspy/signatures/signature.py#L34-L100)
</details>
sweep-ai[bot] commented 7 months ago

🚀 Here's the PR! #67

See Sweep's progress at the progress dashboard!
💎 Sweep Pro: I'm using GPT-4. You have unlimited GPT-4 tickets. (tracking ID: 31125586c1)

Actions (click)

Sandbox Execution ✓

Here are the sandbox execution logs prior to making any changes:

Sandbox logs for 2edbec9
Checking dspy/signatures/field.py for syntax errors... ✅ dspy/signatures/field.py has no syntax errors! 1/1 ✓
Checking dspy/signatures/field.py for syntax errors...
✅ dspy/signatures/field.py has no syntax errors!

Sandbox passed on the latest main, so sandbox checks will be enabled for this issue.


Step 1: 🔎 Searching

I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.

Some code snippets I think are relevant in decreasing order of relevance (click to expand). If some file is missing from here, you can mention the path in the ticket description. https://github.com/darinkishore/dspy/blob/2edbec95a9f2806dd62113b1fe0c9e4a47fb0479/dspy/signatures/field.py#L3-L30 https://github.com/darinkishore/dspy/blob/2edbec95a9f2806dd62113b1fe0c9e4a47fb0479/dspy/signatures/signature.py#L34-L100

Step 2: ⌨️ Coding

--- 
+++ 
@@ -4,11 +4,24 @@
 class Field:
     """A more ergonomic datatype that infers prefix and desc if omitted."""
     def __init__(self, *, prefix=None, desc=None, input, format=None):
+        """Initializes the Field instance with the given parameters.
+
+        :param prefix: Optional prefix for the field. If not provided, it will be inferred.
+        :param desc: Optional description for the field. If not provided, it will be inferred.
+        :param input: Specifies if the field is for input. Otherwise, it's for output.
+        :param format: The format of the field, if applicable.
+        """
         self.prefix = prefix  # This can be None initially and set later
         self.desc = desc
         self.format = format

     def finalize(self, key, inferred_prefix):
+        """Sets the prefix for the field if it's not provided explicitly and updates the description.
+
+        :param key: The key identifying the field in the signature.
+        :param inferred_prefix: The prefix inferred for the field.
+        :return: None
+        """
         """Set the prefix if it's not provided explicitly."""
         if self.prefix is None:
             self.prefix = inferred_prefix + ":"
@@ -17,15 +30,28 @@
             self.desc = f'${{{key}}}'

     def __repr__(self):
+        """Represents the Field instance as a string.
+
+        :return: The string representation of the Field instance.
+        """
         return f"{self.__class__.__name__}(prefix={self.prefix}, desc={self.desc})"

     def __eq__(self, __value: object) -> bool:
+        """Determines if this Field instance is equal to another object.
+
+        :param __value: The object to compare against.
+        :return: True if objects are equal, False otherwise.
+        """
         return self.__dict__ == __value.__dict__

 class InputField(Field):
+    """A subclass of Field that specifically represents input fields, inheriting the functionality and allowing further specification.
+    """
     def __init__(self, *, prefix=None, desc=None, format=None):
         super().__init__(prefix=prefix, desc=desc, input=True, format=format)

 class OutputField(Field):
+    """A subclass of Field that specifically represents output fields, inheriting the functionality and allowing further specification.
+    """
     def __init__(self, *, prefix=None, desc=None, format=None):
         super().__init__(prefix=prefix, desc=desc, input=False, format=format)

Ran GitHub Actions for bfea0337c513fe182d221707e13d724d8e6ed7df:

--- 
+++ 
@@ -5,6 +5,7 @@
 import threading

 class SignatureMeta(type):
+    """A metaclass for the Signature class in the DSPy framework."""
     _thread_local_storage = threading.local()

     class _SignatureNamespace:
@@ -45,6 +46,12 @@
         return cls.signature.fields

     def __call__(cls, *args, **kwargs):
+        """Calls a Signature instance or creates a new one based on the provided arguments.
+
+        :param args: Positional arguments for the function call.
+        :param kwargs: Keyword arguments for the function call.
+        :return: A Signature instance or the result from the `_template` call.
+        """
         if len(args) == 1 and isinstance(args[0], str):
             instance = super(SignatureMeta, cls).__call__(*args, **kwargs)
             return instance
@@ -52,13 +59,28 @@
         return cls._template(*args, **kwargs)

     def __getattr__(cls, attr):
+        """Gets the attribute from the _template object if not found in the class's dictionary.
+
+        :param attr: The name of the attribute to get.
+        :return: The value of the attribute from the _template object.
+        """
         # Redirect attribute access to the template object when accessed on the class directly
         if attr not in cls.__dict__:
             return getattr(cls._template, attr)
         return super().__getattr__(attr)    

 class Signature(metaclass=SignatureMeta):
+    """Class used to declare the input/output behavior of LMs in DSPy.
+
+    A Signature instance encapsulates descriptions for input and output fields
+    of a sub-task, allowing DSPy modules to interact with large LMs efficiently.
+    """
     def __init__(self, signature: str = "", instructions: str = ""):
+        """Initializes the Signature instance with a signature string and instructions.
+
+        :param signature: A string defining the input and output fields.
+        :param instructions: Additional instructions for the signature.
+        """
         self.signature = signature
         self.instructions = instructions
         self.fields = {}
@@ -74,6 +96,10 @@
         return {k: v for k, v in self.fields.items()}

     def parse_structure(self):
+        """Parses the signature string to extract and define input and output fields.
+
+        :return: None
+        """
         inputs_str, outputs_str = self.signature.split("->")
         for name in inputs_str.split(","):
             self.add_field(name.strip(), InputField())
@@ -81,6 +107,11 @@
             self.add_field(name.strip(), OutputField())

     def attach(self, **kwargs):
+        """Attaches fields to the Signature with additional properties like prefix and description.
+
+        :param kwargs: A dictionary with field names as keys and tuples of (prefix, desc) as values.
+        :return: The instance of Signature for chaining method calls.
+        """
         for key, (prefix, desc) in kwargs.items():
             field_type = self.fields.get(key)
             if not field_type:
@@ -93,6 +124,13 @@
         return self

     def add_field(self, field_name: str, field_type, position="append"):
+        """Adds a field to the Signature with the specified field name and type.
+
+        :param field_name: The name of the field to add.
+        :param field_type: The type of field being added, can be InputField or OutputField.
+        :param position: Specifies whether to append or prepend the new field in the fields order.
+        :return: None
+        """
         if field_name in self.fields:
             raise ValueError(f"{field_name} already exists in fields.")
         if isinstance(field_type, (InputField, OutputField)):

Ran GitHub Actions for 7ad290f13bf8d1ba7a2b6f4e695b17c1974ed6d4:


Step 3: 🔁 Code Review

I have finished reviewing the code for completeness. I did not find errors for sweep/add_docstrings_to_signature.


🎉 Latest improvements to Sweep:


💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request. Join Our Discord