Skip to content

Commit

Permalink
Merge branch 'main' of github.com:stanfordnlp/dspy into kristaoo
Browse files Browse the repository at this point in the history
  • Loading branch information
klopsahlong committed Sep 10, 2024
2 parents a3d0ae2 + 71e5d35 commit df47949
Show file tree
Hide file tree
Showing 25 changed files with 380 additions and 191 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/docs-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Update DSPy Docs

on:
push:
branches:
- main
paths:
- 'docs/**'
pull_request:
paths:
- 'docs/**'

jobs:
build-test:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies and build
run: |
cd docs
npm install
npm run build
update-docs-subtree:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Git
run: |
git config user.name github-actions
git config user.email [email protected]
- name: Push docs subtree
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
git remote add docs-repo https://krypticmouse:${GH_PAT}@github.com/krypticmouse/dspy-docs.git
git subtree push --prefix=docs docs-repo master
2 changes: 1 addition & 1 deletion docs/api/language_model_clients/Groq.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sidebar_position: 9
### Usage

```python
lm = dspy.GROQ(model='mixtral-8x7b-32768', api_key ="gsk_***" )
lm = dspy.GroqLM(model='mixtral-8x7b-32768', api_key ="gsk_***" )
```

### Constructor
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions docs/docs/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,9 @@ Other custom configurations are similar to customizing the `dspy.BootstrapFewSho

### Including `dspy.Assert` and `dspy.Suggest` statements
```python
dspy.Assert(your_validation_fn(model_outputs), "your feedback message", target_module="YourDSPyModuleSignature")
dspy.Assert(your_validation_fn(model_outputs), "your feedback message", target_module="YourDSPyModule")

dspy.Suggest(your_validation_fn(model_outputs), "your feedback message", target_module="YourDSPyModuleSignature")
dspy.Suggest(your_validation_fn(model_outputs), "your feedback message", target_module="YourDSPyModule")
```

### Activating DSPy Program with Assertions
Expand Down
152 changes: 108 additions & 44 deletions docs/docs/dspy-usecases.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ To make this more systematic and much more powerful, **DSPy** does two things. F

When we build neural networks, we don't write manual _for-loops_ over lists of _hand-tuned_ floats. Instead, you might use a framework like [PyTorch](https://pytorch.org/) to compose layers (e.g., `Convolution` or `Dropout`) and then use optimizers (e.g., SGD or Adam) to learn the parameters of the network.

Ditto! **DSPy** gives you the right general-purpose modules (e.g., `ChainOfThought`, `ReAct`, etc.), which replace string-based prompting tricks. To replace prompt hacking and one-off synthetic data generators, **DSPy** also gives you general optimizers (`BootstrapFewShotWithRandomSearch` or `MIPRO`), which are algorithms that update parameters in your program. Whenever you modify your code, your data, your assertions, or your metric, you can _compile_ your program again and **DSPy** will create new effective prompts that fit your changes.
Ditto! **DSPy** gives you the right general-purpose modules (e.g. `ChainOfThought`, `ReAct`, etc.), which replace string-based prompting tricks. To replace prompt hacking and one-off synthetic data generators, **DSPy** also gives you general optimizers (`BootstrapFewShotWithRandomSearch` or `MIPRO`), which are algorithms that update parameters in your program. Whenever you modify your code, your data, your assertions, or your metric, you can _compile_ your program again and **DSPy** will create new effective prompts that fit your changes.
7 changes: 6 additions & 1 deletion docs/docs/roadmap.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
draft: true
---


# Roadmap Sketch: DSPy 2.5+

It’s been a year since DSPy evolved out of Demonstrate–Search–Predict (DSP), whose research started at Stanford NLP all the way back in February 2022. Thanks to 200 wonderful contributors, DSPy has introduced tens of thousands of people to building modular LM programs and optimizing their prompts and weights automatically. In this time, DSPy has grown to 160,000 monthly downloads and 16,000 stars on GitHub, becoming synonymous with prompt optimization in many circles and inspiring at least a half-dozen cool new libraries.
Expand All @@ -21,7 +26,7 @@ To a first approximation, DSPy’s current user-facing language has the minimum

## Team & Organization

DSPy is fairly unusual in its technical objectives, contributors, and audience. Though DSPy takes inspiration from PyTorch, a library for building and optimizing DNNs, there is one major difference: PyTorch was introduced well after DNNs were mature ML concepts, but DSPy seeks to establish and advance core LM Programs research: the framework is propelled by constant academic research from programming abstractions (like the original [Demonstrate–Search–Predict]() concepts, DSPy [Signatures](), or [LM Assertions]()) to NLP systems (like [STORM](), [PATH](), and [IReRa]()) to prompt optimizers (like [MIPRO]()) and RL (like [BetterTogether]()), among many other related directions.
DSPy is fairly unusual in its technical objectives, contributors, and audience. Though DSPy takes inspiration from PyTorch, a library for building and optimizing DNNs, there is one major difference: PyTorch was introduced well after DNNs were mature ML concepts, but DSPy seeks to establish and advance core LM Programs research: the framework is propelled by constant academic research from programming abstractions (like the original **Demonstrate–Search–Predict** concepts, DSPy **Signatures**, or **LM Assertions**) to NLP systems (like **STORM**, **PATH**, and **IReRa**) to prompt optimizers (like **MIPRO**) and RL (like **BetterTogether**), among many other related directions.

This research all composes into a concrete, practical library, thanks to dozens of industry contributors, many of whom are deploying apps in production using DSPy. Because of this, DSPy reaches not only of grad students and ML engineers, but also many non-ML engineers, from early adopter SWEs to hobbyists exploring new ways of using LMs. The following team, with help from many folks in the OSS community, is working towards the objectives in this Roadmap.

Expand Down
2 changes: 1 addition & 1 deletion dsp/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .databricks import *
from .dummy_lm import *
from .google import *
from .googlevertexai import *
from .google_vertex_ai import *
from .gpt3 import *
from .groq_client import *
from .hf import HFModel
Expand Down
File renamed without changes.
File renamed without changes.
75 changes: 65 additions & 10 deletions dspy/evaluate/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import threading
import types
from typing import Any

import pandas as pd
import tqdm
Expand All @@ -12,11 +13,25 @@

try:
from IPython.display import HTML
from IPython.display import display as ipython_display
from IPython.display import display as display

except ImportError:
ipython_display = print

def HTML(x) -> str: # noqa: N802
def display(obj: Any):
"""
Display the specified Python object in the console.
:param obj: The Python object to display.
"""
print(obj)

def HTML(x: str) -> str:
"""
Obtain the HTML representation of the specified string.
"""
# NB: This method exists purely for code compatibility with the IPython HTML() function in
# environments where IPython is not available. In such environments where IPython is not
# available, this method will simply return the input string.
return x


Expand Down Expand Up @@ -228,9 +243,9 @@ def wrapped_program(example_idx, example):
df_to_display = result_df.head(display_table).copy()
truncated_rows = len(result_df) - display_table

styled_df = configure_dataframe_display(df_to_display, metric_name)
df_to_display = stylize_metric_name(df_to_display, metric_name)

ipython_display(styled_df)
display_dataframe(df_to_display)

if truncated_rows > 0:
# Simplified message about the truncated rows
Expand All @@ -244,7 +259,7 @@ def wrapped_program(example_idx, example):
... {truncated_rows} more rows not displayed ...
</div>
"""
ipython_display(HTML(message))
display(HTML(message))

if return_all_scores and return_outputs:
return round(100 * ncorrect / ntotal, 2), results, [score for *_, score in predicted_devset]
Expand Down Expand Up @@ -281,14 +296,39 @@ def truncate_cell(content) -> str:
return content


def configure_dataframe_display(df, metric_name) -> pd.DataFrame:
"""Set various pandas display options for DataFrame."""
def stylize_metric_name(df: pd.DataFrame, metric_name: str) -> pd.DataFrame:
"""
Stylize the cell contents of a pandas DataFrame corresponding to the specified metric name.
:param df: The pandas DataFrame for which to stylize cell contents.
:param metric_name: The name of the metric for which to stylize DataFrame cell contents.
"""
df[metric_name] = df[metric_name].apply(lambda x: f"✔️ [{x}]" if x else str)
return df


def display_dataframe(df: pd.DataFrame):
"""
Display the specified Pandas DataFrame in the console.
:param df: The Pandas DataFrame to display.
"""
if is_in_ipython_notebook_environment():
display(configure_dataframe_for_ipython_notebook_display(df))
else:
# Pretty print the DataFrame to the console
with pd.option_context(
"display.max_rows", None, "display.max_columns", None
): # more options can be specified also
print(df)


def configure_dataframe_for_ipython_notebook_display(df: pd.DataFrame) -> pd.DataFrame:
"""Set various pandas display options for DataFrame in an IPython notebook environment."""
pd.options.display.max_colwidth = None
pd.set_option("display.max_colwidth", 20) # Adjust the number as needed
pd.set_option("display.width", 400) # Adjust

df[metric_name] = df[metric_name].apply(lambda x: f"✔️ [{x}]" if x else str(x))

# Return styled DataFrame
return df.style.set_table_styles(
[
Expand All @@ -305,6 +345,21 @@ def configure_dataframe_display(df, metric_name) -> pd.DataFrame:
)


def is_in_ipython_notebook_environment():
"""
Check if the current environment is an IPython notebook environment.
:return: True if the current environment is an IPython notebook environment, False otherwise.
"""
try:
from IPython import get_ipython

# This is a best-effort check to see if we are in an IPython notebook environment
return "IPKernelApp" in getattr(get_ipython(), "config", {})
except ImportError:
return False


# FIXME: TODO: The merge_dicts stuff above is way too quick and dirty.
# TODO: the display_table can't handle False but can handle 0!
# Not sure how it works with True exactly, probably fails too.
11 changes: 5 additions & 6 deletions dspy/predict/chain_of_thought.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from dspy.primitives.program import Module
from dspy.signatures.signature import ensure_signature


# TODO: This shouldn't inherit from Predict. It should be a module that has one or two predictors.
# Let's focus on the activated case. It's a predictor with the expanded signature.
# Now, when deactivated, it's a predictor with the original signature.
Expand All @@ -19,13 +18,13 @@ def __init__(self, signature, rationale_type=None, activated=True, **config):
*_keys, last_key = signature.output_fields.keys()

prefix = "Reasoning: Let's think step by step in order to"
desc = "${produce the " + last_key + "}. We ..."

if dspy.settings.experimental:
desc = "${produce the output fields}. We ..."
else:
desc = f"${{produce the {last_key}}}. We ..."

rationale_type = rationale_type or dspy.OutputField(prefix=prefix, desc=desc)

# Add "rationale" field to the output signature.
extended_signature = signature.prepend("rationale", rationale_type, type_=str)
self._predict = dspy.Predict(extended_signature, **config)
self._predict.extended_signature = extended_signature
Expand All @@ -35,16 +34,16 @@ def forward(self, **kwargs):

signature = kwargs.pop("new_signature", self._predict.extended_signature if self.activated else self.signature)
return self._predict(signature=signature, **kwargs)
# return super().forward(signature=signature, **kwargs)

@property
def demos(self):
return self._predict.demos

@property
def extended_signature(self):
return self._predict.extended_signature


"""
TODO: In principle, we can update the field's prefix during forward too to fill any thing based on the input args.
Expand Down
2 changes: 1 addition & 1 deletion dspy/predict/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _create_new_signature(self, signature):
actual_prefix = value.json_schema_extra["prefix"].split(":")[0] + ":"
signature = signature.append(f"past_{key}", dspy.InputField(
prefix="Previous " + actual_prefix,
desc=f"past {actual_prefix} with errors",
desc=f"past {actual_prefix[:-1]} with errors",
format=value.json_schema_extra.get("format"),
))

Expand Down
8 changes: 6 additions & 2 deletions dspy/primitives/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def wrapper(*args, **kwargs):
for i in range(len(dsp.settings.trace) - 1, -1, -1):
trace_element = dsp.settings.trace[i]
mod = trace_element[0]
if mod.signature == error_target_module:
if mod == error_target_module:
error_state = e.state[i]
dspy.settings.backtrack_to = mod
break
Expand All @@ -257,7 +257,11 @@ def wrapper(*args, **kwargs):
):
dspy.settings.predictor_feedbacks[dspy.settings.backtrack_to].append(error_msg)

output_fields = error_state[0].new_signature.output_fields
# use `new_signature` if available (CoT)
if hasattr(error_state[0], 'new_signature'):
output_fields = error_state[0].new_signature.output_fields
else:
output_fields = error_state[0].signature.output_fields
past_outputs = {}
for field_name in output_fields.keys():
past_outputs[field_name] = getattr(
Expand Down
18 changes: 18 additions & 0 deletions dspy/primitives/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ def named_predictors(self):

def predictors(self):
return [param for _, param in self.named_predictors()]

def set_lm(self, lm):
import dspy
assert dspy.settings.experimental, "Setting the lm is an experimental feature."

for _, param in self.named_predictors():
param.lm = lm

def get_lm(self):
import dspy
assert dspy.settings.experimental, "Getting the lm is an experimental feature."

all_used_lms = [param.lm for _, param in self.named_predictors()]

if len(set(all_used_lms)) == 1:
return all_used_lms[0]

raise ValueError("Multiple LMs are being used in the module.")

def __repr__(self):
s = []
Expand Down
2 changes: 1 addition & 1 deletion dspy/propose/grounded_proposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def forward(
if self.verbose: print(f"PROGRAM DESCRIPTION: {program_description}")

# Identify all modules
init_pattern = r"def __init__\(.*?\):([\s\S]*?)(?=^\s*def|\Z)"
init_pattern = r"def __init__\([\s\S]*?\):([\s\S]*?)(?=^\s*def|\Z)"
init_content_match = re.search(init_pattern, self.program_code_string)
init_content = init_content_match.group(0)
pattern = r"^(.*dspy\.(ChainOfThought|Predict).*)$" # TODO: make it so that this extends out to any dspy Module
Expand Down
Loading

0 comments on commit df47949

Please sign in to comment.