Repository: stanfordnlp/dspy
Pull request #8478 (2025-07-01 23:11:33)

Add token streaming support for XMLAdapter
Support XMLAdapter in `StreamListener`

## Note

This PR is generated by [OpenAI codex](https://openai.com/codex/), I am testing how deep it can understand DSPy codebase with this specific use case.

It passes on my tests:

```
import asyncio

import dspy

# lm = dspy.LM("gemini/gemini-2.5-flash-preview-04-17", cache=False)
lm = dspy.LM("databricks/databricks-claude-3-7-sonnet", cache=False)
# lm = dspy.LM("openai/gpt-4o-mini", cache=False)
dspy.settings.configure(lm=lm, adapter=dspy.XMLAdapter())


class MyModule(dspy.Module):
    def __init__(self):
        super().__init__()

        self.predict1 = dspy.Predict("question->answer")
        self.predict2 = dspy.Predict("question, answer->judgement, score")

    def forward(self, question: str, **kwargs):
        answer = self.predict1(question=question)
        judgement = self.predict2(question=question, answer=answer)
        return judgement


predict = MyModule()
stream_listeners = [
    dspy.streaming.StreamListener(signature_field_name="answer"),
    dspy.streaming.StreamListener(signature_field_name="judgement"),
]
stream_predict = dspy.streamify(
    predict,
    stream_listeners=stream_listeners,
    include_final_prediction_in_output_stream=False,
)


async def main():
    with dspy.settings.context(adapter=dspy.XMLAdapter()):
        output = stream_predict(question="why did a chicken cross the kitchen?")

        return_value = None
        async for value in output:
            if isinstance(value, dspy.streaming.StatusMessage):
                print(value.message)
            elif isinstance(value, dspy.Prediction):
                print(value)
            elif isinstance(value, dspy.streaming.StreamResponse):
                print(value)
            else:
                print(value.choices[0].delta.content)
    return return_value


import time

start = time.time()
asyncio.run(main())
end = time.time()


print(f"Time taken: {end - start} seconds")

dspy.inspect_history()
```


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5e1d02ad..4cc863a1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -89,7 +89,7 @@ Once the pull request is approved, a team member will take care of merging.
 
 ## Environment Setup
 
-Python 3.9 or later is required.
+Python 3.10 or later is required.
 
 Setting up your DSPy development environment requires you to fork the DSPy repository and clone it locally.
 If you are not familiar with the GitHub fork process, please refer to [Fork a repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). After creating the fork, clone
diff --git a/README.md b/README.md
index 8bd03a3c..7dfa6af1 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 **Documentation:** [DSPy Docs](https://dspy.ai/)
 
-[![PyPI Downloads](https://static.pepy.tech/badge/dspy/month)](https://pepy.tech/projects/dspy)
+![PyPI Downloads](https://img.shields.io/pypi/dm/dspy)
 
 
 ----
diff --git a/docs/docs/api/adapters/Adapter.md b/docs/docs/api/adapters/Adapter.md
index 3d266633..7fa2fd96 100644
--- a/docs/docs/api/adapters/Adapter.md
+++ b/docs/docs/api/adapters/Adapter.md
@@ -24,4 +24,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/adapters/ChatAdapter.md b/docs/docs/api/adapters/ChatAdapter.md
index e76eeec0..fa5e6a4d 100644
--- a/docs/docs/api/adapters/ChatAdapter.md
+++ b/docs/docs/api/adapters/ChatAdapter.md
@@ -27,4 +27,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/adapters/JSONAdapter.md b/docs/docs/api/adapters/JSONAdapter.md
index 0043c95e..718264b6 100644
--- a/docs/docs/api/adapters/JSONAdapter.md
+++ b/docs/docs/api/adapters/JSONAdapter.md
@@ -27,4 +27,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/adapters/TwoStepAdapter.md b/docs/docs/api/adapters/TwoStepAdapter.md
index aea44e4b..ec14b471 100644
--- a/docs/docs/api/adapters/TwoStepAdapter.md
+++ b/docs/docs/api/adapters/TwoStepAdapter.md
@@ -24,4 +24,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/evaluation/CompleteAndGrounded.md b/docs/docs/api/evaluation/CompleteAndGrounded.md
index 5c3c4728..20bdb1d4 100644
--- a/docs/docs/api/evaluation/CompleteAndGrounded.md
+++ b/docs/docs/api/evaluation/CompleteAndGrounded.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/evaluation/Evaluate.md b/docs/docs/api/evaluation/Evaluate.md
index 17d369d7..2c7c4451 100644
--- a/docs/docs/api/evaluation/Evaluate.md
+++ b/docs/docs/api/evaluation/Evaluate.md
@@ -14,4 +14,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/evaluation/SemanticF1.md b/docs/docs/api/evaluation/SemanticF1.md
index aa419648..537a657d 100644
--- a/docs/docs/api/evaluation/SemanticF1.md
+++ b/docs/docs/api/evaluation/SemanticF1.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/evaluation/answer_exact_match.md b/docs/docs/api/evaluation/answer_exact_match.md
index f3faa5f6..3ee48387 100644
--- a/docs/docs/api/evaluation/answer_exact_match.md
+++ b/docs/docs/api/evaluation/answer_exact_match.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/evaluation/answer_passage_match.md b/docs/docs/api/evaluation/answer_passage_match.md
index 2fcbaf52..7a571bcf 100644
--- a/docs/docs/api/evaluation/answer_passage_match.md
+++ b/docs/docs/api/evaluation/answer_passage_match.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/models/Embedder.md b/docs/docs/api/models/Embedder.md
index 48a2459a..510a749a 100644
--- a/docs/docs/api/models/Embedder.md
+++ b/docs/docs/api/models/Embedder.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/models/LM.md b/docs/docs/api/models/LM.md
index cb9a7442..20429c00 100644
--- a/docs/docs/api/models/LM.md
+++ b/docs/docs/api/models/LM.md
@@ -26,4 +26,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/BestOfN.md b/docs/docs/api/modules/BestOfN.md
index 225efff9..ae778a6c 100644
--- a/docs/docs/api/modules/BestOfN.md
+++ b/docs/docs/api/modules/BestOfN.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/ChainOfThought.md b/docs/docs/api/modules/ChainOfThought.md
index 21a1fad9..978ffae3 100644
--- a/docs/docs/api/modules/ChainOfThought.md
+++ b/docs/docs/api/modules/ChainOfThought.md
@@ -33,4 +33,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/Module.md b/docs/docs/api/modules/Module.md
index dbce6fb2..c192fc70 100644
--- a/docs/docs/api/modules/Module.md
+++ b/docs/docs/api/modules/Module.md
@@ -31,4 +31,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/MultiChainComparison.md b/docs/docs/api/modules/MultiChainComparison.md
index 90f0a950..364b6632 100644
--- a/docs/docs/api/modules/MultiChainComparison.md
+++ b/docs/docs/api/modules/MultiChainComparison.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/Parallel.md b/docs/docs/api/modules/Parallel.md
index f935c2db..5c798527 100644
--- a/docs/docs/api/modules/Parallel.md
+++ b/docs/docs/api/modules/Parallel.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/Predict.md b/docs/docs/api/modules/Predict.md
index 161ff372..6ae87aa1 100644
--- a/docs/docs/api/modules/Predict.md
+++ b/docs/docs/api/modules/Predict.md
@@ -36,4 +36,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/ProgramOfThought.md b/docs/docs/api/modules/ProgramOfThought.md
index 91efe719..b8e76b50 100644
--- a/docs/docs/api/modules/ProgramOfThought.md
+++ b/docs/docs/api/modules/ProgramOfThought.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/ReAct.md b/docs/docs/api/modules/ReAct.md
index b06dcf92..bf108aac 100644
--- a/docs/docs/api/modules/ReAct.md
+++ b/docs/docs/api/modules/ReAct.md
@@ -34,4 +34,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/modules/Refine.md b/docs/docs/api/modules/Refine.md
index 152b1fcc..19d7f759 100644
--- a/docs/docs/api/modules/Refine.md
+++ b/docs/docs/api/modules/Refine.md
@@ -32,4 +32,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/BetterTogether.md b/docs/docs/api/optimizers/BetterTogether.md
index 43063924..33029b47 100644
--- a/docs/docs/api/optimizers/BetterTogether.md
+++ b/docs/docs/api/optimizers/BetterTogether.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/BootstrapFewShot.md b/docs/docs/api/optimizers/BootstrapFewShot.md
index 3dace819..a02c14a6 100644
--- a/docs/docs/api/optimizers/BootstrapFewShot.md
+++ b/docs/docs/api/optimizers/BootstrapFewShot.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/BootstrapFewShotWithRandomSearch.md b/docs/docs/api/optimizers/BootstrapFewShotWithRandomSearch.md
index f2df1530..24593f4a 100644
--- a/docs/docs/api/optimizers/BootstrapFewShotWithRandomSearch.md
+++ b/docs/docs/api/optimizers/BootstrapFewShotWithRandomSearch.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/BootstrapFinetune.md b/docs/docs/api/optimizers/BootstrapFinetune.md
index 8f1d3a33..7e149fa1 100644
--- a/docs/docs/api/optimizers/BootstrapFinetune.md
+++ b/docs/docs/api/optimizers/BootstrapFinetune.md
@@ -17,4 +17,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/BootstrapRS.md b/docs/docs/api/optimizers/BootstrapRS.md
index df523b4e..013356ed 100644
--- a/docs/docs/api/optimizers/BootstrapRS.md
+++ b/docs/docs/api/optimizers/BootstrapRS.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/COPRO.md b/docs/docs/api/optimizers/COPRO.md
index 89926b40..9f92e68c 100644
--- a/docs/docs/api/optimizers/COPRO.md
+++ b/docs/docs/api/optimizers/COPRO.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/Ensemble.md b/docs/docs/api/optimizers/Ensemble.md
index 58aef770..9cd5e00e 100644
--- a/docs/docs/api/optimizers/Ensemble.md
+++ b/docs/docs/api/optimizers/Ensemble.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/InferRules.md b/docs/docs/api/optimizers/InferRules.md
index 035c0b13..b8af99a2 100644
--- a/docs/docs/api/optimizers/InferRules.md
+++ b/docs/docs/api/optimizers/InferRules.md
@@ -20,4 +20,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/KNN.md b/docs/docs/api/optimizers/KNN.md
index d8c3fcf9..dcf4c7a2 100644
--- a/docs/docs/api/optimizers/KNN.md
+++ b/docs/docs/api/optimizers/KNN.md
@@ -14,4 +14,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/KNNFewShot.md b/docs/docs/api/optimizers/KNNFewShot.md
index 758a6f81..edcc54ea 100644
--- a/docs/docs/api/optimizers/KNNFewShot.md
+++ b/docs/docs/api/optimizers/KNNFewShot.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/LabeledFewShot.md b/docs/docs/api/optimizers/LabeledFewShot.md
index f1d96313..55d7773c 100644
--- a/docs/docs/api/optimizers/LabeledFewShot.md
+++ b/docs/docs/api/optimizers/LabeledFewShot.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/optimizers/MIPROv2.md b/docs/docs/api/optimizers/MIPROv2.md
index 34a6f2ea..c8669e9e 100644
--- a/docs/docs/api/optimizers/MIPROv2.md
+++ b/docs/docs/api/optimizers/MIPROv2.md
@@ -17,6 +17,7 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
 
 ## Example Usage
diff --git a/docs/docs/api/primitives/Example.md b/docs/docs/api/primitives/Example.md
index 1cfecea0..b312c581 100644
--- a/docs/docs/api/primitives/Example.md
+++ b/docs/docs/api/primitives/Example.md
@@ -23,4 +23,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/primitives/History.md b/docs/docs/api/primitives/History.md
index 3b5b717d..1be9232b 100644
--- a/docs/docs/api/primitives/History.md
+++ b/docs/docs/api/primitives/History.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/primitives/Image.md b/docs/docs/api/primitives/Image.md
index 463937c2..c9029842 100644
--- a/docs/docs/api/primitives/Image.md
+++ b/docs/docs/api/primitives/Image.md
@@ -21,4 +21,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/primitives/Prediction.md b/docs/docs/api/primitives/Prediction.md
index 9f78b2b2..9060950f 100644
--- a/docs/docs/api/primitives/Prediction.md
+++ b/docs/docs/api/primitives/Prediction.md
@@ -26,4 +26,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/primitives/Tool.md b/docs/docs/api/primitives/Tool.md
index aa71ba2e..441e8bca 100644
--- a/docs/docs/api/primitives/Tool.md
+++ b/docs/docs/api/primitives/Tool.md
@@ -22,4 +22,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/signatures/InputField.md b/docs/docs/api/signatures/InputField.md
index f9b679d7..534b3740 100644
--- a/docs/docs/api/signatures/InputField.md
+++ b/docs/docs/api/signatures/InputField.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/signatures/OutputField.md b/docs/docs/api/signatures/OutputField.md
index 16a096ca..ca9f4858 100644
--- a/docs/docs/api/signatures/OutputField.md
+++ b/docs/docs/api/signatures/OutputField.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/signatures/Signature.md b/docs/docs/api/signatures/Signature.md
index 14e7db8a..7272acd5 100644
--- a/docs/docs/api/signatures/Signature.md
+++ b/docs/docs/api/signatures/Signature.md
@@ -22,4 +22,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/tools/ColBERTv2.md b/docs/docs/api/tools/ColBERTv2.md
index 9cfc1327..1743b07a 100644
--- a/docs/docs/api/tools/ColBERTv2.md
+++ b/docs/docs/api/tools/ColBERTv2.md
@@ -14,4 +14,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/tools/Embeddings.md b/docs/docs/api/tools/Embeddings.md
index ca1939a6..36b5553e 100644
--- a/docs/docs/api/tools/Embeddings.md
+++ b/docs/docs/api/tools/Embeddings.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/tools/PythonInterpreter.md b/docs/docs/api/tools/PythonInterpreter.md
index baf6af1c..bfc304d0 100644
--- a/docs/docs/api/tools/PythonInterpreter.md
+++ b/docs/docs/api/tools/PythonInterpreter.md
@@ -16,4 +16,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/StatusMessage.md b/docs/docs/api/utils/StatusMessage.md
index 161b7a13..cab18fb3 100644
--- a/docs/docs/api/utils/StatusMessage.md
+++ b/docs/docs/api/utils/StatusMessage.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/StatusMessageProvider.md b/docs/docs/api/utils/StatusMessageProvider.md
index 19fac23a..e22db0dd 100644
--- a/docs/docs/api/utils/StatusMessageProvider.md
+++ b/docs/docs/api/utils/StatusMessageProvider.md
@@ -19,4 +19,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/StreamListener.md b/docs/docs/api/utils/StreamListener.md
index 5785c6f1..517c398f 100644
--- a/docs/docs/api/utils/StreamListener.md
+++ b/docs/docs/api/utils/StreamListener.md
@@ -15,4 +15,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/asyncify.md b/docs/docs/api/utils/asyncify.md
index b1fa70a8..e42be0aa 100644
--- a/docs/docs/api/utils/asyncify.md
+++ b/docs/docs/api/utils/asyncify.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/configure_cache.md b/docs/docs/api/utils/configure_cache.md
index 87516a94..a13ae7b4 100644
--- a/docs/docs/api/utils/configure_cache.md
+++ b/docs/docs/api/utils/configure_cache.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/disable_litellm_logging.md b/docs/docs/api/utils/disable_litellm_logging.md
index 2d7b2a07..6280e796 100644
--- a/docs/docs/api/utils/disable_litellm_logging.md
+++ b/docs/docs/api/utils/disable_litellm_logging.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/disable_logging.md b/docs/docs/api/utils/disable_logging.md
index 6bb9debb..f2517325 100644
--- a/docs/docs/api/utils/disable_logging.md
+++ b/docs/docs/api/utils/disable_logging.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/enable_litellm_logging.md b/docs/docs/api/utils/enable_litellm_logging.md
index fa7f7370..3bd6f89d 100644
--- a/docs/docs/api/utils/enable_litellm_logging.md
+++ b/docs/docs/api/utils/enable_litellm_logging.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/enable_logging.md b/docs/docs/api/utils/enable_logging.md
index 55ae12b0..5fd3e772 100644
--- a/docs/docs/api/utils/enable_logging.md
+++ b/docs/docs/api/utils/enable_logging.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/inspect_history.md b/docs/docs/api/utils/inspect_history.md
index 81357c6d..1de813af 100644
--- a/docs/docs/api/utils/inspect_history.md
+++ b/docs/docs/api/utils/inspect_history.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/load.md b/docs/docs/api/utils/load.md
index dec1eaa5..00fdd2c5 100644
--- a/docs/docs/api/utils/load.md
+++ b/docs/docs/api/utils/load.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/api/utils/streamify.md b/docs/docs/api/utils/streamify.md
index 57e1f7ed..22fb7951 100644
--- a/docs/docs/api/utils/streamify.md
+++ b/docs/docs/api/utils/streamify.md
@@ -12,4 +12,5 @@
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
diff --git a/docs/docs/cheatsheet.md b/docs/docs/cheatsheet.md
index 1ea4b969..76778cb6 100644
--- a/docs/docs/cheatsheet.md
+++ b/docs/docs/cheatsheet.md
@@ -2,127 +2,10 @@
 sidebar_position: 999
 ---
 
-
-!!! warning "This page is outdated and may not be fully accurate in DSPy 2.5 and 2.6"
-
-
 # DSPy Cheatsheet
 
 This page will contain snippets for frequent usage patterns.
 
-## DSPy DataLoaders
-
-Import and initializing a DataLoader Object:
-
-```python
-import dspy
-from dspy.datasets import DataLoader
-
-dl = DataLoader()
-```
-
-### Loading from HuggingFace Datasets
-
-```python
-code_alpaca = dl.from_huggingface("HuggingFaceH4/CodeAlpaca_20K")
-```
-
-You can access the dataset of the splits by calling key of the corresponding split:
-
-```python
-train_dataset = code_alpaca['train']
-test_dataset = code_alpaca['test']
-```
-
-### Loading specific splits from HuggingFace
-
-You can also manually specify splits you want to include as a parameters and it'll return a dictionary where keys are splits that you specified:
-
-```python
-code_alpaca = dl.from_huggingface(
-    "HuggingFaceH4/CodeAlpaca_20K",
-    split = ["train", "test"],
-)
-
-print(f"Splits in dataset: {code_alpaca.keys()}")
-```
-
-If you specify a single split then dataloader will return a List of `dspy.Example` instead of dictionary:
-
-```python
-code_alpaca = dl.from_huggingface(
-    "HuggingFaceH4/CodeAlpaca_20K",
-    split = "train",
-)
-
-print(f"Number of examples in split: {len(code_alpaca)}")
-```
-
-You can slice the split just like you do with HuggingFace Dataset too:
-
-```python
-code_alpaca_80 = dl.from_huggingface(
-    "HuggingFaceH4/CodeAlpaca_20K",
-    split = "train[:80%]",
-)
-
-print(f"Number of examples in split: {len(code_alpaca_80)}")
-
-code_alpaca_20_80 = dl.from_huggingface(
-    "HuggingFaceH4/CodeAlpaca_20K",
-    split = "train[20%:80%]",
-)
-
-print(f"Number of examples in split: {len(code_alpaca_20_80)}")
-```
-
-### Loading specific subset from HuggingFace
-
-If a dataset has a subset you can pass it as an arg like you do with `load_dataset` in HuggingFace:
-
-```python
-gms8k = dl.from_huggingface(
-    "gsm8k",
-    "main",
-    input_keys = ("question",),
-)
-
-print(f"Keys present in the returned dict: {list(gms8k.keys())}")
-
-print(f"Number of examples in train set: {len(gms8k['train'])}")
-print(f"Number of examples in test set: {len(gms8k['test'])}")
-```
-
-### Loading from CSV
-
-```python
-dolly_100_dataset = dl.from_csv("dolly_subset_100_rows.csv",)
-```
-
-You can choose only selected columns from the csv by specifying them in the arguments:
-
-```python
-dolly_100_dataset = dl.from_csv(
-    "dolly_subset_100_rows.csv",
-    fields=("instruction", "context", "response"),
-    input_keys=("instruction", "context")
-)
-```
-
-### Splitting a List of `dspy.Example`
-
-```python
-splits = dl.train_test_split(dataset, train_size=0.8) # `dataset` is a List of dspy.Example
-train_dataset = splits['train']
-test_dataset = splits['test']
-```
-
-### Sampling from List of `dspy.Example`
-
-```python
-sampled_example = dl.sample(dataset, n=5) # `dataset` is a List of dspy.Example
-```
-
 ## DSPy Programs
 
 ### dspy.Signature
@@ -131,8 +14,8 @@ sampled_example = dl.sample(dataset, n=5) # `dataset` is a List of dspy.Example
 class BasicQA(dspy.Signature):
     """Answer questions with short factoid answers."""
 
-    question = dspy.InputField()
-    answer = dspy.OutputField(desc="often between 1 and 5 words")
+    question: str = dspy.InputField()
+    answer: str = dspy.OutputField(desc="often between 1 and 5 words")
 ```
 
 ### dspy.ChainOfThought
@@ -225,13 +108,13 @@ class FactJudge(dspy.Signature):
     context = dspy.InputField(desc="Context for the prediction")
     question = dspy.InputField(desc="Question to be answered")
     answer = dspy.InputField(desc="Answer for the question")
-    factually_correct = dspy.OutputField(desc="Is the answer factually correct based on the context?", prefix="Factual[Yes/No]:")
+    factually_correct: bool = dspy.OutputField(desc="Is the answer factually correct based on the context?")
 
 judge = dspy.ChainOfThought(FactJudge)
 
 def factuality_metric(example, pred):
     factual = judge(context=example.context, question=example.question, answer=pred.answer)
-    return int(factual=="Yes")
+    return factual.factually_correct
 ```
 
 ## DSPy Evaluation
@@ -367,18 +250,6 @@ copro_teleprompter = COPRO(prompt_model=model_to_generate_prompts, metric=your_d
 compiled_program_optimized_signature = copro_teleprompter.compile(your_dspy_program, trainset=trainset, eval_kwargs=eval_kwargs)
 ```
 
-### MIPRO
-
-```python
-from dspy.teleprompt import MIPRO
-
-teleprompter = MIPRO(prompt_model=model_to_generate_prompts, task_model=model_that_solves_task, metric=your_defined_metric, num_candidates=num_new_prompts_generated, init_temperature=prompt_generation_temperature)
-
-kwargs = dict(num_threads=NUM_THREADS, display_progress=True, display_table=0)
-
-compiled_program_optimized_bayesian_signature = teleprompter.compile(your_dspy_program, trainset=trainset, num_trials=100, max_bootstrapped_demos=3, max_labeled_demos=5, eval_kwargs=kwargs)
-```
-
 ### MIPROv2
 
 Note: detailed documentation can be found [here](api/optimizers/MIPROv2.md). `MIPROv2` is the latest extension of `MIPRO` which includes updates such as (1) improvements to instruction proposal and (2) more efficient search with minibatching.
@@ -445,20 +316,6 @@ print(f"Evaluate optimized program...")
 evaluate(optimized_program, devset=devset[:])
 ```
 
-### Signature Optimizer with Types
-
-```python
-from dspy.teleprompt.signature_opt_typed import optimize_signature
-from dspy.evaluate.metrics import answer_exact_match
-from dspy.functional import TypedChainOfThought
-
-compiled_program = optimize_signature(
-    student=TypedChainOfThought("question -> answer"),
-    evaluator=Evaluate(devset=devset, metric=answer_exact_match, num_threads=10, display_progress=True),
-    n_iterations=50,
-).program
-```
-
 ### KNNFewShot
 
 ```python
@@ -484,6 +341,20 @@ your_dspy_program_compiled = fewshot_optuna_optimizer.compile(student=your_dspy_
 
 Other custom configurations are similar to customizing the `dspy.BootstrapFewShot` optimizer.
 
+
+### SIMBA
+
+SIMBA, which stands for Stochastic Introspective Mini-Batch Ascent, is a prompt optimizer that accepts arbitrary DSPy programs and proceeds in a sequence of mini-batches seeking to make incremental improvements to the prompt instructions or few-shot examples.
+
+```python
+from dspy.teleprompt import SIMBA
+
+simba = SIMBA(metric=your_defined_metric, max_steps=12, max_demos=10)
+
+optimized_program = simba.compile(student=your_dspy_program, trainset=trainset)
+```
+
+
 ## DSPy `Refine` and `BestofN`
 
 >`dspy.Suggest` and `dspy.Assert` are replaced by `dspy.Refine` and `dspy.BestofN` in DSPy 2.6.
diff --git a/docs/docs/faqs.md b/docs/docs/faqs.md
index c75063ae..3a5616fe 100644
--- a/docs/docs/faqs.md
+++ b/docs/docs/faqs.md
@@ -19,7 +19,7 @@ The **DSPy** philosophy and abstraction differ significantly from other librarie
 
 ## Basic Usage
 
-**How should I use DSPy for my task?** We wrote a [eight-step guide](/building-blocks/solving_your_task) on this. In short, using DSPy is an iterative process. You first define your task and the metrics you want to maximize, and prepare a few example inputs — typically without labels (or only with labels for the final outputs, if your metric requires them). Then, you build your pipeline by selecting built-in layers (`modules`) to use, giving each layer a `signature` (input/output spec), and then calling your modules freely in your Python code. Lastly, you use a DSPy `optimizer` to compile your code into high-quality instructions, automatic few-shot examples, or updated LM weights for your LM.
+**How should I use DSPy for my task?** We wrote a [eight-step guide](learn/index.md) on this. In short, using DSPy is an iterative process. You first define your task and the metrics you want to maximize, and prepare a few example inputs — typically without labels (or only with labels for the final outputs, if your metric requires them). Then, you build your pipeline by selecting built-in layers (`modules`) to use, giving each layer a `signature` (input/output spec), and then calling your modules freely in your Python code. Lastly, you use a DSPy `optimizer` to compile your code into high-quality instructions, automatic few-shot examples, or updated LM weights for your LM.
 
 **How do I convert my complex prompt into a DSPy pipeline?** See the same answer above.
 
@@ -34,11 +34,11 @@ You can specify multiple output fields. For the short-form signature, you can li
 
 - **How do I define my own metrics? Can metrics return a float?**
 
-You can define metrics as simply Python functions that process model generations and evaluate them based on user-defined requirements. Metrics can compare existent data (e.g. gold labels) to model predictions or they can be used to assess various components of an output using validation feedback from LMs (e.g. LLMs-as-Judges). Metrics can return `bool`, `int`, and `float` types scores. Check out the official [Metrics docs](/building-blocks/5-metrics) to learn more about defining custom metrics and advanced evaluations using AI feedback and/or DSPy programs.
+You can define metrics as simply Python functions that process model generations and evaluate them based on user-defined requirements. Metrics can compare existent data (e.g. gold labels) to model predictions or they can be used to assess various components of an output using validation feedback from LMs (e.g. LLMs-as-Judges). Metrics can return `bool`, `int`, and `float` types scores. Check out the official [Metrics docs](learn/evaluation/metrics.md) to learn more about defining custom metrics and advanced evaluations using AI feedback and/or DSPy programs.
 
 - **How expensive or slow is compiling??**
 
-To reflect compiling metrics, we highlight an experiment for reference, compiling the [`SimplifiedBaleen`](/tutorials/simplified-baleen) using the [`dspy.BootstrapFewShotWithRandomSearch`](/deep-dive/teleprompter/bootstrap-fewshot) optimizer on the `gpt-3.5-turbo-1106` model over 7 candidate programs and 10 threads. We report that compiling this program takes around 6 minutes with 3200 API calls, 2.7 million input tokens and 156,000 output tokens, reporting a total cost of $3 USD (at the current pricing of the OpenAI model).
+To reflect compiling metrics, we highlight an experiment for reference, compiling a program using the [BootstrapFewShotWithRandomSearch](api/optimizers/BootstrapFewShotWithRandomSearch.md) optimizer on the `gpt-3.5-turbo-1106` model over 7 candidate programs and 10 threads. We report that compiling this program takes around 6 minutes with 3200 API calls, 2.7 million input tokens and 156,000 output tokens, reporting a total cost of $3 USD (at the current pricing of the OpenAI model).
 
 Compiling DSPy `optimizers` naturally will incur additional LM calls, but we substantiate this overhead with minimalistic executions with the goal of maximizing performance. This invites avenues to enhance performance of smaller models by compiling DSPy programs with larger models to learn enhanced behavior during compile-time and propagate such behavior to the tested smaller model during inference-time.  
 
diff --git a/docs/docs/index.md b/docs/docs/index.md
index 4c5ff98c..943f8367 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -10,7 +10,7 @@ hide:
 
 # _Programming_—not prompting—_LMs_
 
-[![PyPI Downloads](https://static.pepy.tech/badge/dspy/month)](https://pepy.tech/projects/dspy)
+![PyPI Downloads](https://img.shields.io/pypi/dm/dspy)
 
 DSPy is a declarative framework for building modular AI software. It allows you to **iterate fast on structured code**, rather than brittle strings, and offers algorithms that **compile AI programs into effective prompts and weights** for your language models, whether you're building simple classifiers, sophisticated RAG pipelines, or Agent loops.
 
@@ -374,7 +374,7 @@ Given a few tens or hundreds of representative _inputs_ of your task and a _metr
         optimized_rag = tp.compile(RAG(), trainset=trainset, max_bootstrapped_demos=2, max_labeled_demos=2)
         ```
 
-        For a complete RAG example that you can run, start this [tutorial](/tutorials/rag/). It improves the quality of a RAG system over a subset of StackExchange communities by 10% relative gain.
+        For a complete RAG example that you can run, start this [tutorial](tutorials/rag/index.ipynb). It improves the quality of a RAG system over a subset of StackExchange communities by 10% relative gain.
 
     === "Optimizing weights for Classification"
         <details><summary>Click to show dataset setup code.</summary>
@@ -447,4 +447,4 @@ Compared to monolithic LMs, DSPy's modular paradigm enables a large community to
 
 The DSPy research effort started at Stanford NLP in Feb 2022, building on what we had learned from developing early [compound LM systems](https://bair.berkeley.edu/blog/2024/02/18/compound-ai-systems/) like [ColBERT-QA](https://arxiv.org/abs/2007.00814), [Baleen](https://arxiv.org/abs/2101.00436), and [Hindsight](https://arxiv.org/abs/2110.07752). The first version was released as [DSP](https://arxiv.org/abs/2212.14024) in Dec 2022 and evolved by Oct 2023 into [DSPy](https://arxiv.org/abs/2310.03714). Thanks to [250 contributors](https://github.com/stanfordnlp/dspy/graphs/contributors), DSPy has introduced tens of thousands of people to building and optimizing modular LM programs.
 
-Since then, DSPy's community has produced a large body of work on optimizers, like [MIPROv2](https://arxiv.org/abs/2406.11695), [BetterTogether](https://arxiv.org/abs/2407.10930), and [LeReT](https://arxiv.org/abs/2410.23214), on program architectures, like [STORM](https://arxiv.org/abs/2402.14207), [IReRa](https://arxiv.org/abs/2401.12178), and [DSPy Assertions](https://arxiv.org/abs/2312.13382), and on successful applications to new problems, like [PAPILLON](https://arxiv.org/abs/2410.17127), [PATH](https://arxiv.org/abs/2406.11706), [WangLab@MEDIQA](https://arxiv.org/abs/2404.14544), [UMD's Prompting Case Study](https://arxiv.org/abs/2406.06608), and [Haize's Red-Teaming Program](https://blog.haizelabs.com/posts/dspy/), in addition to many open-source projects, production applications, and other [use cases](/community/use-cases).
+Since then, DSPy's community has produced a large body of work on optimizers, like [MIPROv2](https://arxiv.org/abs/2406.11695), [BetterTogether](https://arxiv.org/abs/2407.10930), and [LeReT](https://arxiv.org/abs/2410.23214), on program architectures, like [STORM](https://arxiv.org/abs/2402.14207), [IReRa](https://arxiv.org/abs/2401.12178), and [DSPy Assertions](https://arxiv.org/abs/2312.13382), and on successful applications to new problems, like [PAPILLON](https://arxiv.org/abs/2410.17127), [PATH](https://arxiv.org/abs/2406.11706), [WangLab@MEDIQA](https://arxiv.org/abs/2404.14544), [UMD's Prompting Case Study](https://arxiv.org/abs/2406.06608), and [Haize's Red-Teaming Program](https://blog.haizelabs.com/posts/dspy/), in addition to many open-source projects, production applications, and other [use cases](community/use-cases.md).
diff --git a/docs/docs/learn/optimization/optimizers.md b/docs/docs/learn/optimization/optimizers.md
index cb428c81..6cf9ffe6 100644
--- a/docs/docs/learn/optimization/optimizers.md
+++ b/docs/docs/learn/optimization/optimizers.md
@@ -41,34 +41,34 @@ Optimizers can be accessed via `from dspy.teleprompt import *`.
 
 These optimizers extend the signature by automatically generating and including **optimized** examples within the prompt sent to the model, implementing few-shot learning.
 
-1. [**`LabeledFewShot`**](/api/optimizers/LabeledFewShot): Simply constructs few-shot examples (demos) from provided labeled input and output data points.  Requires `k` (number of examples for the prompt) and `trainset` to randomly select `k` examples from.
+1. [**`LabeledFewShot`**](../../api/optimizers/LabeledFewShot.md): Simply constructs few-shot examples (demos) from provided labeled input and output data points.  Requires `k` (number of examples for the prompt) and `trainset` to randomly select `k` examples from.
 
-2. [**`BootstrapFewShot`**](/api/optimizers/BootstrapFewshot): Uses a `teacher` module (which defaults to your program) to generate complete demonstrations for every stage of your program, along with labeled examples in `trainset`. Parameters include `max_labeled_demos` (the number of demonstrations randomly selected from the `trainset`) and `max_bootstrapped_demos` (the number of additional examples generated by the `teacher`). The bootstrapping process employs the metric to validate demonstrations, including only those that pass the metric in the "compiled" prompt. Advanced: Supports using a `teacher` program that is a *different* DSPy program that has compatible structure, for harder tasks.
+2. [**`BootstrapFewShot`**](../../api/optimizers/BootstrapFewShot.md): Uses a `teacher` module (which defaults to your program) to generate complete demonstrations for every stage of your program, along with labeled examples in `trainset`. Parameters include `max_labeled_demos` (the number of demonstrations randomly selected from the `trainset`) and `max_bootstrapped_demos` (the number of additional examples generated by the `teacher`). The bootstrapping process employs the metric to validate demonstrations, including only those that pass the metric in the "compiled" prompt. Advanced: Supports using a `teacher` program that is a *different* DSPy program that has compatible structure, for harder tasks.
 
-3. [**`BootstrapFewShotWithRandomSearch`**](/api/optimizers/BootstrapFewshotWithRandomSearch): Applies `BootstrapFewShot` several times with random search over generated demonstrations, and selects the best program over the optimization. Parameters mirror those of `BootstrapFewShot`, with the addition of `num_candidate_programs`, which specifies the number of random programs evaluated over the optimization, including candidates of the uncompiled program, `LabeledFewShot` optimized program, `BootstrapFewShot` compiled program with unshuffled examples and `num_candidate_programs` of `BootstrapFewShot` compiled programs with randomized example sets.
+3. [**`BootstrapFewShotWithRandomSearch`**](../../api/optimizers/BootstrapFewShotWithRandomSearch.md): Applies `BootstrapFewShot` several times with random search over generated demonstrations, and selects the best program over the optimization. Parameters mirror those of `BootstrapFewShot`, with the addition of `num_candidate_programs`, which specifies the number of random programs evaluated over the optimization, including candidates of the uncompiled program, `LabeledFewShot` optimized program, `BootstrapFewShot` compiled program with unshuffled examples and `num_candidate_programs` of `BootstrapFewShot` compiled programs with randomized example sets.
 
-4. [**`KNNFewShot`**](/api/optimizers/KNNFewShot/). Uses k-Nearest Neighbors algorithm to find the nearest training example demonstrations for a given input example. These nearest neighbor demonstrations are then used as the trainset for the BootstrapFewShot optimization process.
+4. [**`KNNFewShot`**](../../api/optimizers/KNNFewShot.md). Uses k-Nearest Neighbors algorithm to find the nearest training example demonstrations for a given input example. These nearest neighbor demonstrations are then used as the trainset for the BootstrapFewShot optimization process.
 
 
 ### Automatic Instruction Optimization
 
 These optimizers produce optimal instructions for the prompt and, in the case of MIPROv2 can also optimize the set of few-shot demonstrations.
 
-5. [**`COPRO`**](/api/optimizers/COPRO): Generates and refines new instructions for each step, and optimizes them with coordinate ascent (hill-climbing using the metric function and the `trainset`). Parameters include `depth` which is the number of iterations of prompt improvement the optimizer runs over.
+5. [**`COPRO`**](../../api/optimizers/COPRO.md): Generates and refines new instructions for each step, and optimizes them with coordinate ascent (hill-climbing using the metric function and the `trainset`). Parameters include `depth` which is the number of iterations of prompt improvement the optimizer runs over.
 
-6. [**`MIPROv2`**](/api/optimizers/MIPROv2): Generates instructions *and* few-shot examples in each step. The instruction generation is data-aware and demonstration-aware. Uses Bayesian Optimization to effectively search over the space of generation instructions/demonstrations across your modules.
+6. [**`MIPROv2`**](../../api/optimizers/MIPROv2.md): Generates instructions *and* few-shot examples in each step. The instruction generation is data-aware and demonstration-aware. Uses Bayesian Optimization to effectively search over the space of generation instructions/demonstrations across your modules.
 
 
 ### Automatic Finetuning
 
 This optimizer is used to fine-tune the underlying LLM(s).
 
-7. [**`BootstrapFinetune`**](/api/optimizers/BootstrapFinetune): Distills a prompt-based DSPy program into weight updates. The output is a DSPy program that has the same steps, but where each step is conducted by a finetuned model instead of a prompted LM.
+7. [**`BootstrapFinetune`**](../../api/optimizers/BootstrapFinetune.md): Distills a prompt-based DSPy program into weight updates. The output is a DSPy program that has the same steps, but where each step is conducted by a finetuned model instead of a prompted LM.
 
 
 ### Program Transformations
 
-8. [**`Ensemble`**](/api/optimizers/Ensemble): Ensembles a set of DSPy programs and either uses the full set or randomly samples a subset into a single program.
+8. [**`Ensemble`**](../../api/optimizers/Ensemble.md): Ensembles a set of DSPy programs and either uses the full set or randomly samples a subset into a single program.
 
 
 ## Which optimizer should I use?
@@ -79,13 +79,13 @@ That being said, here's the general guidance on getting started:
 
 - If you have **very few examples** (around 10), start with `BootstrapFewShot`.
 - If you have **more data** (50 examples or more), try  `BootstrapFewShotWithRandomSearch`.
-- If you prefer to do **instruction optimization only** (i.e. you want to keep your prompt 0-shot), use `MIPROv2` [configured for 0-shot optimization to optimize](/api/optimizers/MIPROv2#optimizing-instructions-only-with-miprov2-0-shot). 
+- If you prefer to do **instruction optimization only** (i.e. you want to keep your prompt 0-shot), use `MIPROv2` [configured for 0-shot optimization](../../api/optimizers/MIPROv2.md). 
 - If you’re willing to use more inference calls to perform **longer optimization runs** (e.g. 40 trials or more), and have enough data (e.g. 200 examples or more to prevent overfitting) then try `MIPROv2`. 
 - If you have been able to use one of these with a large LM (e.g., 7B parameters or above) and need a very **efficient program**, finetune a small LM for your task with `BootstrapFinetune`.
 
 ## How do I use an optimizer?
 
-They all share this general interface, with some differences in the keyword arguments (hyperparameters). Detailed documentation for key optimizers can be found [here](/api/optimizers/vfrs), and a full list can be found [here](https://dspy.ai/api/optimizers/BetterTogether/).
+They all share this general interface, with some differences in the keyword arguments (hyperparameters). A full list can be found in the [API reference](../../api/optimizers/BetterTogether.md).
 
 Let's see this with the most common one, `BootstrapFewShotWithRandomSearch`.
 
@@ -147,7 +147,7 @@ optimized_program = teleprompter.compile(YOUR_PROGRAM_HERE, trainset=YOUR_TRAINS
         optimized_rag = tp.compile(RAG(), trainset=trainset, max_bootstrapped_demos=2, max_labeled_demos=2)
         ```
 
-        For a complete RAG example that you can run, start this [tutorial](/tutorials/rag/). It improves the quality of a RAG system over a subset of StackExchange communities from 53% to 61%.
+        For a complete RAG example that you can run, start this [tutorial](../../tutorials/rag/index.ipynb). It improves the quality of a RAG system over a subset of StackExchange communities from 53% to 61%.
 
     === "Optimizing weights for Classification"
         <details><summary>Click to show dataset setup code.</summary>
diff --git a/docs/docs/learn/programming/language_models.md b/docs/docs/learn/programming/language_models.md
index 8a000563..38592af9 100644
--- a/docs/docs/learn/programming/language_models.md
+++ b/docs/docs/learn/programming/language_models.md
@@ -152,7 +152,7 @@ with dspy.context(lm=dspy.LM('openai/gpt-3.5-turbo')):
 ```
 **Possible Output:**
 ```text
-GPT-4o: The number of floors in the castle David Gregory inherited cannot be determined with the information provided.
+GPT-4o-mini: The number of floors in the castle David Gregory inherited cannot be determined with the information provided.
 GPT-3.5-turbo: The castle David Gregory inherited has 7 floors.
 ```
 
diff --git a/docs/docs/learn/programming/modules.md b/docs/docs/learn/programming/modules.md
index a52f2bc4..2f3fff2d 100644
--- a/docs/docs/learn/programming/modules.md
+++ b/docs/docs/learn/programming/modules.md
@@ -15,7 +15,7 @@ A **DSPy module** is a building block for programs that use LMs.
 
 ## How do I use a built-in module, like `dspy.Predict` or `dspy.ChainOfThought`?
 
-Let's start with the most fundamental module, `dspy.Predict`. Internally, all other DSPy modules are built using `dspy.Predict`. We'll assume you are already at least a little familiar with [DSPy signatures](/learn/programming/signatures), which are declarative specs for defining the behavior of any module we use in DSPy.
+Let's start with the most fundamental module, `dspy.Predict`. Internally, all other DSPy modules are built using `dspy.Predict`. We'll assume you are already at least a little familiar with [DSPy signatures](signatures.md), which are declarative specs for defining the behavior of any module we use in DSPy.
 
 To use a module, we first **declare** it by giving it a signature. Then we **call** the module with the input arguments, and extract the output fields!
 
diff --git a/docs/docs/production/index.md b/docs/docs/production/index.md
index 0aa80065..bd8d6c64 100644
--- a/docs/docs/production/index.md
+++ b/docs/docs/production/index.md
@@ -8,7 +8,7 @@
 
     DSPy is deployed in production by many enterprises and startups. Explore real-world case studies.
 
-    [:octicons-arrow-right-24: Use Cases](../community/use-cases)
+    [:octicons-arrow-right-24: Use Cases](../community/use-cases.md)
 
 - :material-magnify-expand:{ .lg .middle } __Monitoring & Observability__
 
@@ -16,7 +16,7 @@
 
     Monitor your DSPy programs using **MLflow Tracing**, based on OpenTelemetry.
 
-    [:octicons-arrow-right-24: Set Up Observability](../tutorials/observability/#tracing)
+    [:octicons-arrow-right-24: Set Up Observability](../tutorials/observability/index.md#tracing)
 
 - :material-ab-testing: __Reproducibility__
 
@@ -32,7 +32,7 @@
 
     When it's time to productionize, deploy your application easily with DSPy's integration with MLflow Model Serving.
 
-    [:octicons-arrow-right-24: Deployment Guide](../tutorials/deployment)
+    [:octicons-arrow-right-24: Deployment Guide](../tutorials/deployment/index.md)
 
 - :material-arrow-up-right-bold: __Scalability__
 
diff --git a/docs/docs/roadmap.md b/docs/docs/roadmap.md
index cf39fda6..209ba55d 100644
--- a/docs/docs/roadmap.md
+++ b/docs/docs/roadmap.md
@@ -111,7 +111,7 @@ Over the next six months, our goal is to dramatically improve each angle of thes
 
 Using DSPy well for solving a new task is just doing good machine learning with LMs, but teaching this is hard. On the one hand, it's an iterative process: you make some initial choices, which will be sub-optimal, and then you refine them incrementally. It's highly exploratory: it's often the case that no one knows yet how to best solve a problem in a DSPy-esque way. One the other hand, DSPy offers many emerging lessons from several years of building LM systems, in which the design space, the data regime, and many other factors are new both to ML experts and to the very large fraction of users that have no ML experience.
 
-Though current docs do address [a bunch of this](/building-blocks/solving_your_task) in isolated ways, one thing we've learned is that we should separate teaching the core DSPy language (which is ultimately pretty small) from teaching the emerging ML workflow that works well in a DSPy-esque setting. As a natural extension of this, we need to place more emphasis on steps prior and after to the explicit coding in DSPy, from data collection to deployment that serves and monitors the optimized DSPy program in practice. This is just starting but efforts will be ramping up led by Omar Khattab, Isaac Miller, and Herumb Shandilya.
+Though current docs do address [a bunch of this](learn/index.md) in isolated ways, one thing we've learned is that we should separate teaching the core DSPy language (which is ultimately pretty small) from teaching the emerging ML workflow that works well in a DSPy-esque setting. As a natural extension of this, we need to place more emphasis on steps prior and after to the explicit coding in DSPy, from data collection to deployment that serves and monitors the optimized DSPy program in practice. This is just starting but efforts will be ramping up led by Omar Khattab, Isaac Miller, and Herumb Shandilya.
 
 
 ## 4) Shifting towards more interactive optimization & tracking.
diff --git a/docs/docs/tutorials/build_ai_program/index.md b/docs/docs/tutorials/build_ai_program/index.md
index 729b9fd1..53dd0d28 100644
--- a/docs/docs/tutorials/build_ai_program/index.md
+++ b/docs/docs/tutorials/build_ai_program/index.md
@@ -1,11 +1,46 @@
-- [Building AI Agents with DSPy](/tutorials/customer_service_agent/)
-- [Building AI Applications by Customizing DSPy Modules](/tutorials/custom_module/)
-- [Retrieval-Augmented Generation (RAG)](/tutorials/rag/)
-- [Building RAG as Agent](/tutorials/agents/)
-- [Entity Extraction](/tutorials/entity_extraction/)
-- [Classification](/tutorials/classification/)
-- [Multi-Hop RAG](/tutorials/multihop_search/)
-- [Privacy-Conscious Delegation](/tutorials/papillon/)
-- [Program Of Thought](/tutorials/program_of_thought/)
-- [Image Generation Prompt iteration](/tutorials/image_generation_prompting/)
-- [Audio](/tutorials/audio/)
+# Build AI Programs with DSPy
+
+This section contains hands-on tutorials that guide you through building production-ready AI applications using DSPy. Each tutorial demonstrates practical use cases and shows you how to leverage DSPy's modular programming approach to create robust, maintainable AI systems.
+
+## Core Applications
+
+### [Building AI Agents with DSPy](../customer_service_agent/index.ipynb)
+Learn to create intelligent agents that can handle complex customer service scenarios. This tutorial shows how to build agents that can understand context, maintain conversation state, and provide helpful responses.
+
+### [Building AI Applications by Customizing DSPy Modules](../custom_module/index.ipynb)
+Discover how to create custom DSPy modules tailored to your specific needs. Learn the patterns for building reusable, composable components that can be shared across different applications.
+
+## Retrieval-Augmented Generation (RAG)
+
+### [Retrieval-Augmented Generation (RAG)](../rag/index.ipynb)
+Master the fundamentals of RAG systems with DSPy. Learn how to combine retrieval mechanisms with language models to build systems that can answer questions using external knowledge sources.
+
+### [Building RAG as Agent](../agents/index.ipynb)
+Take RAG to the next level by building `ReAct` agent-based systems that can reason about when and how to retrieve information, making your RAG systems more intelligent and adaptive.
+
+### [Multi-Hop RAG](../multihop_search/index.ipynb)
+Build sophisticated RAG systems that can perform multi-step reasoning across multiple information sources, perfect for complex research and analysis tasks.
+
+## Specialized Use Cases
+
+### [Entity Extraction](../entity_extraction/index.ipynb)
+Learn to build systems that can identify and extract specific entities from text, essential for information processing and data analysis applications.
+
+### [Classification](../classification/index.md)
+Build robust text classification systems using DSPy's modular approach with a topic classification example.
+
+### [Privacy-Conscious Delegation](../papillon/index.md)
+Explore advanced techniques for building AI systems that respect privacy constraints while maintaining high performance by combining a small local model and an advanced external model.
+
+## Advanced Reasoning
+
+### [Program Of Thought](../program_of_thought/index.ipynb)
+Learn to build systems that can generate and execute code to solve complex problems, combining the power of language models with programmatic reasoning.
+
+## Multimodal Applications
+
+### [Image Generation Prompt iteration](../image_generation_prompting/index.ipynb)
+Discover how to use DSPy to iteratively improve image generation prompts, creating better visual content through systematic optimization.
+
+### [Audio](../audio/index.ipynb)
+Explore audio processing applications with DSPy, learning to build systems that can understand, process, and generate audio content.
diff --git a/docs/docs/tutorials/core_development/index.md b/docs/docs/tutorials/core_development/index.md
index 906cd0cf..6736ccd8 100644
--- a/docs/docs/tutorials/core_development/index.md
+++ b/docs/docs/tutorials/core_development/index.md
@@ -1,9 +1,38 @@
-- [Use MCP in DSPy](/tutorials/mcp/)
-- [Output Refinement](/tutorials/output_refinement/best-of-n-and-refine/)
-- [Saving and Loading](/tutorials/saving/)
-- [Cache](/tutorials/cache/)
-- [Deployment](/tutorials/deployment/)
-- [Debugging & Observability](/tutorials/observability/)
-- [Tracking DSPy Optimizers](/tutorials/optimizer_tracking/)
-- [Streaming](/tutorials/streaming/)
-- [Async](/tutorials/async/)
\ No newline at end of file
+# Tools, Development, and Deployment
+
+This section covers essential DSPy features and best practices for professional AI development. Learn how to implement key functionalities like streaming, caching, deployment, and monitoring in your DSPy applications. These tutorials focus on the practical aspects of building production-ready systems.
+
+## Integration and Tooling
+
+### [Use MCP in DSPy](../mcp/index.md)
+Learn to integrate Model Context Protocol (MCP) with DSPy applications. This tutorial shows how to leverage MCP for enhanced context management and more sophisticated AI interactions.
+
+### [Output Refinement](../output_refinement/best-of-n-and-refine.md)
+Master techniques for improving output quality through refinement strategies. Learn how to implement best-of-N sampling and iterative refinement to get higher-quality results from your DSPy programs.
+
+## Data Management and Persistence
+
+### [Saving and Loading](../saving/index.md)
+Understand how to persist and restore DSPy programs and their optimized states. Learn best practices for model versioning, checkpoint management, and program serialization.
+
+### [Cache](../cache/index.md)
+Implement efficient caching strategies to improve performance and reduce API costs. Learn how to configure and use DSPy's caching mechanisms effectively in different scenarios.
+
+## Production Deployment
+
+### [Deployment](../deployment/index.md)
+Learn to deploy DSPy applications in production environments. This tutorial covers multiple deployment strategies such as FastAPI and MLflow.
+
+### [Streaming](../streaming/index.md)
+Implement real-time streaming capabilities in your DSPy applications. Learn how to handle streaming responses for better user experience in interactive applications.
+
+### [Async](../async/index.md)
+Build asynchronous DSPy applications for improved performance and scalability. Learn async/await patterns and concurrent execution strategies for high-throughput systems.
+
+## Monitoring and Optimization
+
+### [Debugging & Observability](../observability/index.md)
+Master debugging and monitoring techniques for DSPy applications. Learn to use comprehensive logging, tracing, and error handling for production systems.
+
+### [Tracking DSPy Optimizers](../optimizer_tracking/index.md)
+Learn to track and analyze optimizer performance and behavior. Understand how to monitor optimization processes and enhance the reproducibility of the optimization.
diff --git a/docs/docs/tutorials/deployment/index.md b/docs/docs/tutorials/deployment/index.md
index a8527a6f..bbc7d77a 100644
--- a/docs/docs/tutorials/deployment/index.md
+++ b/docs/docs/tutorials/deployment/index.md
@@ -65,7 +65,7 @@ You can configure the async capacity using the new `async_max_workers` setting.
 
 ??? "Streaming, in DSPy 2.6.0+"
 
-    Streaming is also supported in DSPy 2.6.0+, available as a release candidate via `pip install -U --pre dspy`.
+    Streaming is also supported in DSPy 2.6.0+, which can be installed via `pip install -U dspy`.
 
     We can use `dspy.streamify` to convert the dspy program to a streaming mode. This is useful when you want to stream
     the intermediate outputs (i.e. O1-style reasoning) to the client before the final prediction is ready. This uses
diff --git a/docs/docs/tutorials/games/index.ipynb b/docs/docs/tutorials/games/index.ipynb
index 83d70e4d..e2d31b9e 100644
--- a/docs/docs/tutorials/games/index.ipynb
+++ b/docs/docs/tutorials/games/index.ipynb
@@ -10,7 +10,7 @@
     "\n",
     "### Install dependencies and download data\n",
     "\n",
-    "Install the latest DSPy via `pip install -U --pre dspy` and follow along. This tutorial uses the AlfWorld dataset, which depends on DSPy 2.6.0 (pre-release).\n",
+    "Install the latest DSPy via `pip install -U dspy` and follow along. This tutorial uses the AlfWorld dataset, which depends on DSPy 2.6.0.\n",
     "\n",
     "You will also need the following dependencies:\n",
     "\n",
diff --git a/docs/docs/tutorials/index.md b/docs/docs/tutorials/index.md
index f253c924..90e0d226 100644
--- a/docs/docs/tutorials/index.md
+++ b/docs/docs/tutorials/index.md
@@ -13,34 +13,34 @@ Welcome to DSPy tutorials! We've organized our tutorials into three main categor
 
 
 - Build AI Programs with DSPy
-    - [Building AI Agents with DSPy](/tutorials/customer_service_agent/)
-    - [Building AI Applications by Customizing DSPy Modules](/tutorials/custom_module/)
-    - [Retrieval-Augmented Generation (RAG)](/tutorials/rag/)
-    - [Building RAG as Agent](/tutorials/agents/)
-    - [Entity Extraction](/tutorials/entity_extraction/)
-    - [Classification](/tutorials/classification/)
-    - [Multi-Hop RAG](/tutorials/multihop_search/)
-    - [Privacy-Conscious Delegation](/tutorials/papillon/)
-    - [Program Of Thought](/tutorials/program_of_thought/)
-    - [Image Generation Prompt iteration](/tutorials/image_generation_prompting/)
-    - [Audio](/tutorials/audio/)
+    - [Building AI Agents with DSPy](customer_service_agent/index.ipynb)
+    - [Building AI Applications by Customizing DSPy Modules](custom_module/index.ipynb)
+    - [Retrieval-Augmented Generation (RAG)](rag/index.ipynb)
+    - [Building RAG as Agent](agents/index.ipynb)
+    - [Entity Extraction](entity_extraction/index.ipynb)
+    - [Classification](classification/index.md)
+    - [Multi-Hop RAG](multihop_search/index.ipynb)
+    - [Privacy-Conscious Delegation](papillon/index.md)
+    - [Program Of Thought](program_of_thought/index.ipynb)
+    - [Image Generation Prompt iteration](image_generation_prompting/index.ipynb)
+    - [Audio](audio/index.ipynb)
 
 
 - Optimize AI Programs with DSPy
-    - [Math Reasoning](/tutorials/math/)
-    - [Classification Finetuning](/tutorials/classification_finetuning/)
-    - [Advanced Tool Use](/tutorials/tool_use/)
-    - [Finetuning Agents](/tutorials/games/)
+    - [Math Reasoning](math/index.ipynb)
+    - [Classification Finetuning](classification_finetuning/index.ipynb)
+    - [Advanced Tool Use](tool_use/index.ipynb)
+    - [Finetuning Agents](games/index.ipynb)
 
 - Tools, Development, and Deployment
-    - [Use MCP in DSPy](/tutorials/mcp/)
-    - [Output Refinement](/tutorials/output_refinement/best-of-n-and-refine/)
-    - [Saving and Loading](/tutorials/saving/)
-    - [Cache](/tutorials/cache/)
-    - [Deployment](/tutorials/deployment/)
-    - [Debugging & Observability](/tutorials/observability/)
-    - [Tracking DSPy Optimizers](/tutorials/optimizer_tracking/)
-    - [Streaming](/tutorials/streaming/)
-    - [Async](/tutorials/async/)
+    - [Use MCP in DSPy](mcp/index.md)
+    - [Output Refinement](output_refinement/best-of-n-and-refine.md)
+    - [Saving and Loading](saving/index.md)
+    - [Cache](cache/index.md)
+    - [Deployment](deployment/index.md)
+    - [Debugging & Observability](observability/index.md)
+    - [Tracking DSPy Optimizers](optimizer_tracking/index.md)
+    - [Streaming](streaming/index.md)
+    - [Async](async/index.md)
 
 
diff --git a/docs/docs/tutorials/llms_txt_generation/index.md b/docs/docs/tutorials/llms_txt_generation/index.md
index 6e2872a1..3fe9c419 100644
--- a/docs/docs/tutorials/llms_txt_generation/index.md
+++ b/docs/docs/tutorials/llms_txt_generation/index.md
@@ -113,6 +113,8 @@ import requests
 import os
 from pathlib import Path
 
+os.environ["GITHUB_ACCESS_TOKEN"] = "<your_access_token>"
+
 def get_github_file_tree(repo_url):
     """Get repository file structure from GitHub API."""
     # Extract owner/repo from URL
@@ -120,7 +122,9 @@ def get_github_file_tree(repo_url):
     owner, repo = parts[-2], parts[-1]
     
     api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/main?recursive=1"
-    response = requests.get(api_url)
+    response = requests.get(api_url, headers={
+        "Authorization": f"Bearer {os.environ.get('GITHUB_ACCESS_TOKEN')}"
+    })
     
     if response.status_code == 200:
         tree_data = response.json()
@@ -135,7 +139,9 @@ def get_github_file_content(repo_url, file_path):
     owner, repo = parts[-2], parts[-1]
     
     api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/{file_path}"
-    response = requests.get(api_url)
+    response = requests.get(api_url, headers={
+        "Authorization": f"Bearer {os.environ.get('GITHUB_ACCESS_TOKEN')}"
+    })
     
     if response.status_code == 200:
         import base64
diff --git a/docs/docs/tutorials/observability/index.md b/docs/docs/tutorials/observability/index.md
index 1e338936..488bed25 100644
--- a/docs/docs/tutorials/observability/index.md
+++ b/docs/docs/tutorials/observability/index.md
@@ -190,7 +190,7 @@ the [MLflow Tracing Guide](https://mlflow.org/docs/3.0.0rc0/tracing).
 
 !!! info Learn more about MLflow
 
-    MLflow is an end-to-end LLMOps platform that offers extensive features like experiment tracking, evaluation, and deployment. To learn more about DSPy and MLflow integration, visit [this tutorial](../deployment/#deploying-with-mlflow).
+    MLflow is an end-to-end LLMOps platform that offers extensive features like experiment tracking, evaluation, and deployment. To learn more about DSPy and MLflow integration, visit [this tutorial](../deployment/index.md#deploying-with-mlflow).
 
 
 ## Building a Custom Logging Solution
diff --git a/docs/docs/tutorials/optimize_ai_program/index.md b/docs/docs/tutorials/optimize_ai_program/index.md
index 19c369e3..e8399a95 100644
--- a/docs/docs/tutorials/optimize_ai_program/index.md
+++ b/docs/docs/tutorials/optimize_ai_program/index.md
@@ -1,4 +1,21 @@
-- [Math Reasoning](/tutorials/math/)
-- [Classification Finetuning](/tutorials/classification_finetuning/)
-- [Advanced Tool Use](/tutorials/tool_use/)
-- [Finetuning Agents](/tutorials/games/)
\ No newline at end of file
+# Optimize AI Programs with DSPy
+
+This section focuses on DSPy's powerful optimization capabilities, demonstrating how to systematically improve your AI programs using various optimizers. These tutorials are lighter on programming concepts and instead showcase how DSPy optimizers can automatically enhance the quality and performance of your applications.
+
+## Mathematical and Reasoning Tasks
+
+### [Math Reasoning](../math/index.ipynb)
+Learn how to optimize DSPy programs for mathematical reasoning tasks. This tutorial demonstrates how optimizers can dramatically improve performance on complex math problems by finding better prompting strategies and few-shot examples.
+
+## Model Optimization
+
+### [Classification Finetuning](../classification_finetuning/index.ipynb)
+Discover how to use DSPy's finetuning optimizers to distill knowledge from large language models into smaller, more efficient models. Learn the complete workflow from prompt optimization to model finetuning for classification tasks.
+
+## Advanced Tool Integration
+
+### [Advanced Tool Use](../tool_use/index.ipynb)
+Explore how to optimize AI programs that use external tools and APIs. This tutorial shows how DSPy optimizers can learn to use tools more effectively, improving both accuracy and efficiency in tool-calling scenarios.
+
+### [Finetuning Agents](../games/index.ipynb)
+Learn to optimize complex agent-based systems through finetuning. This tutorial demonstrates how to improve agent performance in interactive environments like games, where strategic thinking and adaptation are crucial.
diff --git a/docs/docs/tutorials/rl_ai_program/index.md b/docs/docs/tutorials/rl_ai_program/index.md
index 7733f073..20ad0539 100644
--- a/docs/docs/tutorials/rl_ai_program/index.md
+++ b/docs/docs/tutorials/rl_ai_program/index.md
@@ -1,1 +1,11 @@
-See the links on the side bar.
\ No newline at end of file
+# Experimental RL Optimization for DSPy
+
+This section explores cutting-edge reinforcement learning (RL) approaches for optimizing DSPy programs. These experimental techniques represent the frontier of AI program optimization, combining the power of RL with DSPy's modular programming paradigm to achieve even better performance on complex tasks.
+
+## Advanced RL Optimization Techniques
+
+### [RL for Privacy-Conscious Delegation](../rl_papillon/index.ipynb)
+Explore how reinforcement learning can optimize privacy-conscious AI systems. This tutorial demonstrates how RL agents can learn to balance task performance with privacy constraints, making intelligent decisions about when and how to delegate sensitive operations.
+
+### [RL for Multi-Hop Research](../rl_multihop/index.ipynb)
+Learn to apply reinforcement learning to multi-hop reasoning tasks. This advanced tutorial shows how RL can optimize the search strategy in complex information retrieval scenarios, learning to navigate through multiple information sources more effectively.
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 1f77f071..364f9372 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -7,6 +7,7 @@ repo_name: stanfordnlp/dspy
 
 edit_uri: blob/main/docs/docs/
 docs_dir: "docs/"
+
 nav:
     - Get Started: index.md
     - Learn DSPy:
@@ -216,7 +217,7 @@ plugins:
                         show_warnings: false
                         html_meta: false
     - mkdocs-jupyter:
-        ignore_h1_titles: True
+        ignore_h1_titles: true
     - redirects:
         redirect_maps:
             # Redirect /intro/ to the main page
diff --git a/docs/scripts/generate_api_docs.py b/docs/scripts/generate_api_docs.py
index 10c33fe5..38e378e2 100644
--- a/docs/scripts/generate_api_docs.py
+++ b/docs/scripts/generate_api_docs.py
@@ -134,6 +134,7 @@ def generate_doc_page(name: str, module_path: str, obj: Any, is_root: bool = Fal
         members:
 {methods_list}"""
 
+    # We need to put ::: at last to avoid unclosed div. See https://github.com/danielfrg/mkdocs-jupyter/issues/231 for more details.
     return f"""<!-- START_API_REF -->
 ::: {module_path}.{name}
     handler: python
@@ -146,6 +147,7 @@ def generate_doc_page(name: str, module_path: str, obj: Any, is_root: bool = Fal
         show_object_full_path: false
         separate_signature: false
         inherited_members: true
+:::
 <!-- END_API_REF -->
 """
 
diff --git a/dspy/__init__.py b/dspy/__init__.py
index 9113e4f5..25ba18f3 100644
--- a/dspy/__init__.py
+++ b/dspy/__init__.py
@@ -6,9 +6,10 @@ from dspy.teleprompt import *
 
 from dspy.evaluate import Evaluate  # isort: skip
 from dspy.clients import *  # isort: skip
-from dspy.adapters import Adapter, ChatAdapter, JSONAdapter, XMLAdapter, TwoStepAdapter, Image, Audio, History, BaseType, Tool, ToolCalls  # isort: skip
+from dspy.adapters import Adapter, ChatAdapter, JSONAdapter, XMLAdapter, TwoStepAdapter, Image, Audio, History, Type, Tool, ToolCalls  # isort: skip
 from dspy.utils.logging_utils import configure_dspy_loggers, disable_logging, enable_logging
 from dspy.utils.asyncify import asyncify
+from dspy.utils.syncify import syncify
 from dspy.utils.saving import load
 from dspy.streaming.streamify import streamify
 from dspy.utils.usage_tracker import track_usage
diff --git a/dspy/adapters/__init__.py b/dspy/adapters/__init__.py
index 6d342df3..c2c6d23a 100644
--- a/dspy/adapters/__init__.py
+++ b/dspy/adapters/__init__.py
@@ -2,13 +2,13 @@ from dspy.adapters.base import Adapter
 from dspy.adapters.chat_adapter import ChatAdapter
 from dspy.adapters.json_adapter import JSONAdapter
 from dspy.adapters.two_step_adapter import TwoStepAdapter
-from dspy.adapters.types import Audio, BaseType, History, Image, Tool, ToolCalls
+from dspy.adapters.types import Audio, History, Image, Tool, ToolCalls, Type
 from dspy.adapters.xml_adapter import XMLAdapter
 
 __all__ = [
     "Adapter",
     "ChatAdapter",
-    "BaseType",
+    "Type",
     "History",
     "Image",
     "Audio",
diff --git a/dspy/adapters/base.py b/dspy/adapters/base.py
index 1cdc1edf..3e0372c0 100644
--- a/dspy/adapters/base.py
+++ b/dspy/adapters/base.py
@@ -17,8 +17,9 @@ if TYPE_CHECKING:
 
 
 class Adapter:
-    def __init__(self, callbacks: list[BaseCallback] | None = None):
+    def __init__(self, callbacks: list[BaseCallback] | None = None, use_native_function_calling: bool = False):
         self.callbacks = callbacks or []
+        self.use_native_function_calling = use_native_function_calling
 
     def __init_subclass__(cls, **kwargs) -> None:
         super().__init_subclass__(**kwargs)
@@ -33,9 +34,8 @@ class Adapter:
         lm_kwargs: dict[str, Any],
         signature: Type[Signature],
         inputs: dict[str, Any],
-        use_native_function_calling: bool = False,
     ) -> dict[str, Any]:
-        if use_native_function_calling:
+        if self.use_native_function_calling:
             tool_call_input_field_name = self._get_tool_call_input_field_name(signature)
             tool_call_output_field_name = self._get_tool_call_output_field_name(signature)
 
@@ -57,6 +57,9 @@ class Adapter:
                 lm_kwargs["tools"] = litellm_tools
 
                 signature_for_native_function_calling = signature.delete(tool_call_output_field_name)
+                signature_for_native_function_calling = signature_for_native_function_calling.delete(
+                    tool_call_input_field_name
+                )
 
                 return signature_for_native_function_calling
 
@@ -64,12 +67,13 @@ class Adapter:
 
     def _call_postprocess(
         self,
-        signature: Type[Signature],
+        processed_signature: Type[Signature],
+        original_signature: Type[Signature],
         outputs: list[dict[str, Any]],
     ) -> list[dict[str, Any]]:
         values = []
 
-        tool_call_output_field_name = self._get_tool_call_output_field_name(signature)
+        tool_call_output_field_name = self._get_tool_call_output_field_name(original_signature)
 
         for output in outputs:
             output_logprobs = None
@@ -82,10 +86,14 @@ class Adapter:
                 tool_calls = output.get("tool_calls")
 
             if text:
-                value = self.parse(signature, text)
+                value = self.parse(processed_signature, text)
+                for field_name in original_signature.output_fields.keys():
+                    if field_name not in value:
+                        # We need to set the field not present in the processed signature to None for consistency.
+                        value[field_name] = None
             else:
                 value = {}
-                for field_name in signature.output_fields.keys():
+                for field_name in original_signature.output_fields.keys():
                     value[field_name] = None
 
             if tool_calls and tool_call_output_field_name:
@@ -117,7 +125,7 @@ class Adapter:
         inputs = self.format(processed_signature, demos, inputs)
 
         outputs = lm(messages=inputs, **lm_kwargs)
-        return self._call_postprocess(signature, outputs)
+        return self._call_postprocess(processed_signature, signature, outputs)
 
     async def acall(
         self,
@@ -131,7 +139,7 @@ class Adapter:
         inputs = self.format(processed_signature, demos, inputs)
 
         outputs = await lm.acall(messages=inputs, **lm_kwargs)
-        return self._call_postprocess(signature, outputs)
+        return self._call_postprocess(processed_signature, signature, outputs)
 
     def format(
         self,
diff --git a/dspy/adapters/chat_adapter.py b/dspy/adapters/chat_adapter.py
index ac407e47..4ace4214 100644
--- a/dspy/adapters/chat_adapter.py
+++ b/dspy/adapters/chat_adapter.py
@@ -15,7 +15,6 @@ from dspy.adapters.utils import (
 )
 from dspy.clients.lm import LM
 from dspy.signatures.signature import Signature
-from dspy.utils.callback import BaseCallback
 from dspy.utils.exceptions import AdapterParseError
 
 field_header_pattern = re.compile(r"\[\[ ## (\w+) ## \]\]")
@@ -27,9 +26,6 @@ class FieldInfoWithName(NamedTuple):
 
 
 class ChatAdapter(Adapter):
-    def __init__(self, callbacks: list[BaseCallback] | None = None):
-        super().__init__(callbacks)
-
     def __call__(
         self,
         lm: LM,
diff --git a/dspy/adapters/json_adapter.py b/dspy/adapters/json_adapter.py
index 9b14691c..51470e2e 100644
--- a/dspy/adapters/json_adapter.py
+++ b/dspy/adapters/json_adapter.py
@@ -9,6 +9,7 @@ import regex
 from pydantic.fields import FieldInfo
 
 from dspy.adapters.chat_adapter import ChatAdapter, FieldInfoWithName
+from dspy.adapters.types.tool import ToolCalls
 from dspy.adapters.utils import (
     format_field_value,
     get_annotation_name,
@@ -18,6 +19,7 @@ from dspy.adapters.utils import (
 )
 from dspy.clients.lm import LM
 from dspy.signatures.signature import Signature, SignatureMeta
+from dspy.utils.callback import BaseCallback
 from dspy.utils.exceptions import AdapterParseError
 
 logger = logging.getLogger(__name__)
@@ -37,6 +39,10 @@ def _has_open_ended_mapping(signature: SignatureMeta) -> bool:
 
 
 class JSONAdapter(ChatAdapter):
+    def __init__(self, callbacks: list[BaseCallback] | None = None, use_native_function_calling: bool = True):
+        # JSONAdapter uses native function calling by default.
+        super().__init__(callbacks=callbacks, use_native_function_calling=use_native_function_calling)
+
     def _json_adapter_call_common(self, lm, lm_kwargs, signature, demos, inputs, call_fn):
         """Common call logic to be used for both sync and async calls."""
         provider = lm.model.split("/", 1)[0] or "openai"
@@ -45,7 +51,10 @@ class JSONAdapter(ChatAdapter):
         if not params or "response_format" not in params:
             return call_fn(lm, lm_kwargs, signature, demos, inputs)
 
-        if _has_open_ended_mapping(signature):
+        has_tool_calls = any(field.annotation == ToolCalls for field in signature.output_fields.values())
+        if _has_open_ended_mapping(signature) or (not self.use_native_function_calling and has_tool_calls):
+            # We found that structured output mode doesn't work well with dspy.ToolCalls as output field.
+            # So we fall back to json mode if native function calling is disabled and ToolCalls is present.
             lm_kwargs["response_format"] = {"type": "json_object"}
             return call_fn(lm, lm_kwargs, signature, demos, inputs)
 
@@ -62,7 +71,9 @@ class JSONAdapter(ChatAdapter):
             return result
 
         try:
-            structured_output_model = _get_structured_outputs_response_format(signature)
+            structured_output_model = _get_structured_outputs_response_format(
+                signature, self.use_native_function_calling
+            )
             lm_kwargs["response_format"] = structured_output_model
             return super().__call__(lm, lm_kwargs, signature, demos, inputs)
         except Exception:
@@ -91,16 +102,6 @@ class JSONAdapter(ChatAdapter):
             lm_kwargs["response_format"] = {"type": "json_object"}
             return await super().acall(lm, lm_kwargs, signature, demos, inputs)
 
-    def _call_preprocess(
-        self,
-        lm: "LM",
-        lm_kwargs: dict[str, Any],
-        signature: Type[Signature],
-        inputs: dict[str, Any],
-        use_native_function_calling: bool = True,
-    ) -> dict[str, Any]:
-        return super()._call_preprocess(lm, lm_kwargs, signature, inputs, use_native_function_calling)
-
     def format_field_structure(self, signature: Type[Signature]) -> str:
         parts = []
         parts.append("All interactions will be structured in the following way, with the appropriate values filled in.")
@@ -206,7 +207,10 @@ class JSONAdapter(ChatAdapter):
         raise NotImplementedError
 
 
-def _get_structured_outputs_response_format(signature: SignatureMeta) -> type[pydantic.BaseModel]:
+def _get_structured_outputs_response_format(
+    signature: SignatureMeta,
+    use_native_function_calling: bool = True,
+) -> type[pydantic.BaseModel]:
     """
     Builds a Pydantic model from a DSPy signature's output_fields and ensures the generated JSON schema
     is compatible with OpenAI Structured Outputs (all objects have a "required" key listing every property,
@@ -227,6 +231,9 @@ def _get_structured_outputs_response_format(signature: SignatureMeta) -> type[py
     fields = {}
     for name, field in signature.output_fields.items():
         annotation = field.annotation
+        if use_native_function_calling and annotation == ToolCalls:
+            # Skip ToolCalls field if native function calling is enabled.
+            continue
         default = field.default if hasattr(field, "default") else ...
         fields[name] = (annotation, default)
 
diff --git a/dspy/adapters/two_step_adapter.py b/dspy/adapters/two_step_adapter.py
index 405cc99e..27b8d5e7 100644
--- a/dspy/adapters/two_step_adapter.py
+++ b/dspy/adapters/two_step_adapter.py
@@ -39,7 +39,8 @@ class TwoStepAdapter(Adapter):
     ```
     """
 
-    def __init__(self, extraction_model: LM):
+    def __init__(self, extraction_model: LM, **kwargs):
+        super().__init__(**kwargs)
         if not isinstance(extraction_model, LM):
             raise ValueError("extraction_model must be an instance of LM")
         self.extraction_model = extraction_model
diff --git a/dspy/adapters/types/__init__.py b/dspy/adapters/types/__init__.py
index 68607e52..bc57d50d 100644
--- a/dspy/adapters/types/__init__.py
+++ b/dspy/adapters/types/__init__.py
@@ -1,7 +1,7 @@
 from dspy.adapters.types.audio import Audio
-from dspy.adapters.types.base_type import BaseType
+from dspy.adapters.types.base_type import Type
 from dspy.adapters.types.history import History
 from dspy.adapters.types.image import Image
 from dspy.adapters.types.tool import Tool, ToolCalls
 
-__all__ = ["History", "Image", "Audio", "BaseType", "Tool", "ToolCalls"]
+__all__ = ["History", "Image", "Audio", "Type", "Tool", "ToolCalls"]
diff --git a/dspy/adapters/types/audio.py b/dspy/adapters/types/audio.py
index b4637a06..83e04156 100644
--- a/dspy/adapters/types/audio.py
+++ b/dspy/adapters/types/audio.py
@@ -7,7 +7,7 @@ from typing import Any, Union
 import pydantic
 import requests
 
-from dspy.adapters.types.base_type import BaseType
+from dspy.adapters.types.base_type import Type
 
 try:
     import soundfile as sf
@@ -17,7 +17,7 @@ except ImportError:
     SF_AVAILABLE = False
 
 
-class Audio(BaseType):
+class Audio(Type):
     data: str
     audio_format: str
 
diff --git a/dspy/adapters/types/base_type.py b/dspy/adapters/types/base_type.py
index 4ac90970..e3536922 100644
--- a/dspy/adapters/types/base_type.py
+++ b/dspy/adapters/types/base_type.py
@@ -9,7 +9,7 @@ CUSTOM_TYPE_START_IDENTIFIER = "<<CUSTOM-TYPE-START-IDENTIFIER>>"
 CUSTOM_TYPE_END_IDENTIFIER = "<<CUSTOM-TYPE-END-IDENTIFIER>>"
 
 
-class BaseType(pydantic.BaseModel):
+class Type(pydantic.BaseModel):
     """Base class to support creating custom types for DSPy signatures.
 
     This is the parent class of DSPy custom types, e.g, dspy.Image. Subclasses must implement the `format` method to
@@ -18,7 +18,7 @@ class BaseType(pydantic.BaseModel):
     Example:
 
         ```python
-        class Image(BaseType):
+        class Image(Type):
             url: str
 
             def format(self) -> list[dict[str, Any]]:
@@ -85,7 +85,7 @@ def split_message_content_for_custom_types(messages: list[dict[str, Any]]) -> li
 
     This is implemented by finding the `<<CUSTOM-TYPE-START-IDENTIFIER>>` and `<<CUSTOM-TYPE-END-IDENTIFIER>>`
     in the user message content and splitting the content around them. The `<<CUSTOM-TYPE-START-IDENTIFIER>>`
-    and `<<CUSTOM-TYPE-END-IDENTIFIER>>` are the reserved identifiers for the custom types as in `dspy.BaseType`.
+    and `<<CUSTOM-TYPE-END-IDENTIFIER>>` are the reserved identifiers for the custom types as in `dspy.Type`.
 
     Args:
         messages: a list of messages sent to the LM. The format is the same as [OpenAI API's messages
diff --git a/dspy/adapters/types/image.py b/dspy/adapters/types/image.py
index e8b44ffe..32fb786e 100644
--- a/dspy/adapters/types/image.py
+++ b/dspy/adapters/types/image.py
@@ -8,7 +8,7 @@ from urllib.parse import urlparse
 import pydantic
 import requests
 
-from dspy.adapters.types.base_type import BaseType
+from dspy.adapters.types.base_type import Type
 
 try:
     from PIL import Image as PILImage
@@ -18,7 +18,7 @@ except ImportError:
     PIL_AVAILABLE = False
 
 
-class Image(BaseType):
+class Image(Type):
     url: str
 
     model_config = {
diff --git a/dspy/adapters/types/tool.py b/dspy/adapters/types/tool.py
index c2ca4990..8e4aeda7 100644
--- a/dspy/adapters/types/tool.py
+++ b/dspy/adapters/types/tool.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, get_origin, get_ty
 from jsonschema import ValidationError, validate
 from pydantic import BaseModel, TypeAdapter, create_model
 
-from dspy.adapters.types.base_type import BaseType
+from dspy.adapters.types.base_type import Type
 from dspy.dsp.utils.settings import settings
 from dspy.utils.callback import with_callbacks
 
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
 _TYPE_MAPPING = {"string": str, "integer": int, "number": float, "boolean": bool, "array": list, "object": dict}
 
 
-class Tool(BaseType):
+class Tool(Type):
     """Tool class.
 
     This class is used to simplify the creation of tools for tool calling (function calling) in LLMs. Only supports
@@ -254,7 +254,7 @@ class Tool(BaseType):
         return f"{self.name}{desc} {arg_desc}"
 
 
-class ToolCalls(BaseType):
+class ToolCalls(Type):
     class ToolCall(BaseModel):
         name: str
         args: dict[str, Any]
@@ -303,7 +303,8 @@ class ToolCalls(BaseType):
                             "name": tool_call.name,
                             "arguments": tool_call.args,
                         },
-                    } for tool_call in self.tool_calls
+                    }
+                    for tool_call in self.tool_calls
                 ],
             }
         ]
diff --git a/dspy/adapters/utils.py b/dspy/adapters/utils.py
index 6632d325..476765da 100644
--- a/dspy/adapters/utils.py
+++ b/dspy/adapters/utils.py
@@ -10,7 +10,7 @@ import pydantic
 from pydantic import TypeAdapter
 from pydantic.fields import FieldInfo
 
-from dspy.adapters.types.base_type import BaseType
+from dspy.adapters.types.base_type import Type
 from dspy.signatures.utils import get_dspy_field_type
 
 
@@ -204,7 +204,7 @@ def get_field_description_string(fields: dict) -> str:
         field_message += f" ({get_annotation_name(v.annotation)})"
         desc = v.json_schema_extra["desc"] if v.json_schema_extra["desc"] != f"${{{k}}}" else ""
 
-        custom_types = BaseType.extract_custom_type_from_annotation(v.annotation)
+        custom_types = Type.extract_custom_type_from_annotation(v.annotation)
         for custom_type in custom_types:
             if len(custom_type.description()) > 0:
                 desc += f"\n    Type description of {get_annotation_name(custom_type)}: {custom_type.description()}"
diff --git a/dspy/predict/program_of_thought.py b/dspy/predict/program_of_thought.py
index e55b8d06..86fb5fee 100644
--- a/dspy/predict/program_of_thought.py
+++ b/dspy/predict/program_of_thought.py
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
 class ProgramOfThought(Module):
     """
     A DSPy module that runs Python programs to solve a problem.
-    This module reuires deno to be installed. Please install deno following https://docs.deno.com/runtime/getting_started/installation/
+    This module requires deno to be installed. Please install deno following https://docs.deno.com/runtime/getting_started/installation/
 
     Example:
     ```
diff --git a/dspy/primitives/base_module.py b/dspy/primitives/base_module.py
index d7005142..5b309b12 100644
--- a/dspy/primitives/base_module.py
+++ b/dspy/primitives/base_module.py
@@ -69,8 +69,8 @@ class BaseModule:
     def named_sub_modules(self, type_=None, skip_compiled=False) -> Generator[tuple[str, "BaseModule"], None, None]:
         """Find all sub-modules in the module, as well as their names.
 
-        Say self.children[4]['key'].sub_module is a sub-module. Then the name will be
-        'children[4][key].sub_module'. But if the sub-module is accessible at different
+        Say `self.children[4]['key'].sub_module` is a sub-module. Then the name will be
+        `children[4]['key'].sub_module`. But if the sub-module is accessible at different
         paths, only one of the paths will be returned.
         """
         if type_ is None:
@@ -248,7 +248,7 @@ class BaseModule:
         path = Path(path)
 
         if path.suffix == ".json":
-            with open(path) as f:
+            with open(path, encoding="utf-8") as f:
                 state = ujson.loads(f.read())
         elif path.suffix == ".pkl":
             with open(path, "rb") as f:
diff --git a/dspy/primitives/module.py b/dspy/primitives/module.py
index e2928165..c95a6021 100644
--- a/dspy/primitives/module.py
+++ b/dspy/primitives/module.py
@@ -123,7 +123,7 @@ class Module(BaseModule, metaclass=ProgramMeta):
         return_failed_examples: bool = False,
         provide_traceback: bool | None = None,
         disable_progress_bar: bool = False,
-    ):
+    ) -> list[Example] | tuple[list[Example], list[Example], list[Exception]]:
         """
         Processes a list of dspy.Example instances in parallel using the Parallel module.
 
diff --git a/dspy/primitives/python_interpreter.py b/dspy/primitives/python_interpreter.py
index c3ba85e7..dccecb30 100644
--- a/dspy/primitives/python_interpreter.py
+++ b/dspy/primitives/python_interpreter.py
@@ -127,6 +127,7 @@ class PythonInterpreter:
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE,
                     text=True,
+                    encoding="UTF-8",
                     env=os.environ.copy()
                 )
             except FileNotFoundError as e:
diff --git a/dspy/primitives/runner.js b/dspy/primitives/runner.js
index d513d3a8..4b10bf54 100644
--- a/dspy/primitives/runner.js
+++ b/dspy/primitives/runner.js
@@ -147,7 +147,11 @@ sys.stderr = old_stderr
       // output += capturedStderr;
     } else {
       // If the code returned a real value, just return that
-      output = result;
+      try {
+        output = result.toJs();
+      } catch (e) {
+        output = result;
+      }
     }
 
     console.log(JSON.stringify({ output }));
diff --git a/dspy/signatures/signature.py b/dspy/signatures/signature.py
index 21f18f7a..1b92e60a 100644
--- a/dspy/signatures/signature.py
+++ b/dspy/signatures/signature.py
@@ -355,7 +355,7 @@ class Signature(BaseModel, metaclass=SignatureMeta):
         return signature_copy
 
 
-def ensure_signature(signature: str | Type[Signature], instructions=None) -> Signature:
+def ensure_signature(signature: str | Type[Signature], instructions=None) -> Type[Signature]:
     if signature is None:
         return None
     if isinstance(signature, str):
diff --git a/dspy/streaming/streaming_listener.py b/dspy/streaming/streaming_listener.py
index 1cedeeae..91489e20 100644
--- a/dspy/streaming/streaming_listener.py
+++ b/dspy/streaming/streaming_listener.py
@@ -7,12 +7,15 @@ from litellm import ModelResponseStream
 
 from dspy.adapters.chat_adapter import ChatAdapter
 from dspy.adapters.json_adapter import JSONAdapter
+from dspy.adapters.xml_adapter import XMLAdapter
 from dspy.dsp.utils.settings import settings
 from dspy.streaming.messages import StreamResponse
 
 if TYPE_CHECKING:
     from dspy.primitives.module import Module
 
+ADAPTER_SUPPORT_STREAMING = [ChatAdapter, XMLAdapter, JSONAdapter]
+
 
 class StreamListener:
     """Class that listens to the stream to capture the streeaming of a specific output field of a predictor."""
@@ -45,11 +48,23 @@ class StreamListener:
         self.cache_hit = False
         self.allow_reuse = allow_reuse
 
-        self.json_adapter_start_identifier = f'"{self.signature_field_name}":'
-        self.json_adapter_end_identifier = re.compile(r"\w*\"(,|\s*})")
-
-        self.chat_adapter_start_identifier = f"[[ ## {self.signature_field_name} ## ]]"
-        self.chat_adapter_end_identifier = re.compile(r"\[\[ ## (\w+) ## \]\]")
+        self.adapter_identifiers = {
+            "ChatAdapter": {
+                "start_identifier": f"[[ ## {self.signature_field_name} ## ]]",
+                "end_identifier": re.compile(r"\[\[ ## (\w+) ## \]\]"),
+                "start_indicator": "[",
+            },
+            "JSONAdapter": {
+                "start_identifier": f'"{self.signature_field_name}":',
+                "end_identifier": re.compile(r"\w*\"(,|\s*})"),
+                "start_indicator": '"',
+            },
+            "XMLAdapter": {
+                "start_identifier": f"<{self.signature_field_name}>",
+                "end_identifier": re.compile(rf"</{self.signature_field_name}>"),
+                "start_indicator": "<",
+            },
+        }
 
     def _buffered_message_end_with_start_identifier(self, concat_message: str, start_identifier: str) -> str:
         for i in range(len(concat_message)):
@@ -58,21 +73,15 @@ class StreamListener:
         return False
 
     def receive(self, chunk: ModelResponseStream):
-        if isinstance(settings.adapter, JSONAdapter):
-            start_identifier = self.json_adapter_start_identifier
-            end_identifier = self.json_adapter_end_identifier
-
-            start_indicator = '"'
-        elif isinstance(settings.adapter, ChatAdapter) or settings.adapter is None:
-            start_identifier = self.chat_adapter_start_identifier
-            end_identifier = self.chat_adapter_end_identifier
-
-            start_indicator = "["
-        else:
+        adapter_name = settings.adapter.__class__.__name__ if settings.adapter else "ChatAdapter"
+        if adapter_name not in self.adapter_identifiers:
             raise ValueError(
-                f"Unsupported adapter for streaming: {settings.adapter}, please use either ChatAdapter or "
-                "JSONAdapter for streaming purposes."
+                f"Unsupported adapter for streaming: {adapter_name}, please use one of the following adapters: "
+                f"{', '.join([a.__name__ for a in ADAPTER_SUPPORT_STREAMING])}"
             )
+        start_identifier = self.adapter_identifiers[adapter_name]["start_identifier"]
+        end_identifier = self.adapter_identifiers[adapter_name]["end_identifier"]
+        start_indicator = self.adapter_identifiers[adapter_name]["start_indicator"]
 
         if self.stream_end:
             if self.allow_reuse:
@@ -175,13 +184,18 @@ class StreamListener:
             else:
                 boundary_index = len(last_tokens)
             return last_tokens[:boundary_index]
+        elif isinstance(settings.adapter, XMLAdapter):
+            boundary_index = last_tokens.find(f"</{self.signature_field_name}>")
+            if boundary_index == -1:
+                boundary_index = len(last_tokens)
+            return last_tokens[:boundary_index]
         elif isinstance(settings.adapter, ChatAdapter) or settings.adapter is None:
             boundary_index = last_tokens.find("[[")
             return last_tokens[:boundary_index]
         else:
             raise ValueError(
-                f"Unsupported adapter for streaming: {settings.adapter}, please use either ChatAdapter or "
-                "JSONAdapter for streaming purposes."
+                f"Unsupported adapter for streaming: {settings.adapter}, please use one of the following adapters: "
+                f"{', '.join([a.__name__ for a in ADAPTER_SUPPORT_STREAMING])}"
             )
 
 
diff --git a/dspy/teleprompt/bootstrap.py b/dspy/teleprompt/bootstrap.py
index 23495bba..d7031194 100644
--- a/dspy/teleprompt/bootstrap.py
+++ b/dspy/teleprompt/bootstrap.py
@@ -245,11 +245,12 @@ class BootstrapFewShot(Teleprompter):
 
             # Update the traces
             for name, demos in name2traces.items():
-                from datasets.fingerprint import Hasher
 
                 # If there are multiple traces for the same predictor in the sample example,
                 # sample 50/50 from the first N-1 traces or the last trace.
                 if len(demos) > 1:
+                    from datasets.fingerprint import Hasher
+
                     rng = random.Random(Hasher.hash(tuple(demos)))
                     demos = [rng.choice(demos[:-1]) if rng.random() < 0.5 else demos[-1]]
                 self.name2traces[name].extend(demos)
diff --git a/dspy/teleprompt/mipro_optimizer_v2.py b/dspy/teleprompt/mipro_optimizer_v2.py
index 4ab73b60..eb1a4d80 100644
--- a/dspy/teleprompt/mipro_optimizer_v2.py
+++ b/dspy/teleprompt/mipro_optimizer_v2.py
@@ -1,6 +1,5 @@
 import logging
 import random
-import select
 import sys
 import textwrap
 import time
@@ -395,9 +394,17 @@ class MIPROv2(Teleprompter):
         # Wait for input with timeout
         start_time = time.time()
         while time.time() - start_time < 20:
-            if select.select([sys.stdin], [], [], 0.1)[0]:
-                user_input = sys.stdin.readline().strip().lower()
-                return user_input == "y"
+            if sys.platform == "win32":
+                import msvcrt
+                if msvcrt.kbhit():
+                    user_input = msvcrt.getch().decode("utf-8").strip().lower()
+                    print(user_input)  # Echo the input
+                    return user_input == "y"
+            else:
+                import select
+                if select.select([sys.stdin], [], [], 0.1)[0]:
+                    user_input = sys.stdin.readline().strip().lower()
+                    return user_input == "y"
             time.sleep(0.1)
 
         print("\nNo input received within 20 seconds. Proceeding with execution...")
diff --git a/dspy/utils/__init__.py b/dspy/utils/__init__.py
index d5809b7f..0c53086f 100644
--- a/dspy/utils/__init__.py
+++ b/dspy/utils/__init__.py
@@ -7,6 +7,7 @@ from dspy.utils import exceptions
 from dspy.utils.callback import BaseCallback, with_callbacks
 from dspy.utils.dummies import DummyLM, DummyVectorizer, dummy_rm
 from dspy.utils.inspect_history import pretty_print_history
+from dspy.utils.syncify import syncify
 
 
 def download(url):
diff --git a/dspy/utils/syncify.py b/dspy/utils/syncify.py
new file mode 100644
index 00000000..01aee61c
--- /dev/null
+++ b/dspy/utils/syncify.py
@@ -0,0 +1,60 @@
+import asyncio
+from types import MethodType
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from dspy.primitives.module import Module
+
+
+def run_async(coro):
+    """Run an async coroutine from a synchronous context."""
+    try:
+        loop = asyncio.get_running_loop()
+    except RuntimeError:
+        loop = None
+
+    if loop and loop.is_running():
+        # If we're in a running event loop (e.g., Jupyter), use asyncio.create_task and run until done
+        import nest_asyncio
+
+        nest_asyncio.apply()
+        return asyncio.get_event_loop().run_until_complete(coro)
+    else:
+        return asyncio.run(coro)
+
+
+def syncify(program: "Module", in_place: bool = True) -> "Module":
+    """Convert an async DSPy module to a sync program.
+
+    There are two modes of this function:
+
+    - `in_place=True` (recommended): Modify the module in place. But this may not work if you already have a `forward`
+        method which does different things from `aforward`.
+    - `in_place=False`: Return a wrapper module. This changes the module's architecture, but it's more robust.
+
+    Args:
+        program: The async program to convert, must have an `aforward` method implemented.
+        in_place: If True, modify the module in place. Otherwise, return a wrapper module.
+
+    Returns:
+        The sync program, which has a `forward` method that can be called from a synchronous context.
+    """
+    if in_place:
+
+        def forward(self, *args, **kwargs):
+            return run_async(self.aforward(*args, **kwargs))
+
+        # Create the `forward` method in place.
+        program.forward = MethodType(forward, program)
+        return program
+    else:
+        from dspy.primitives.module import Module
+
+        class SyncWrapper(Module):
+            def __init__(self, program: "Module"):
+                self.program = program
+
+            def forward(self, *args, **kwargs):
+                return run_async(self.program.aforward(*args, **kwargs))
+
+        return SyncWrapper(program)


Test patch:
diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
index 6510c69a..4003781e 100644
--- a/.github/workflows/run_tests.yml
+++ b/.github/workflows/run_tests.yml
@@ -82,11 +82,49 @@ jobs:
         with:
           args: check --fix-only
       - name: Run tests with pytest
-        run: uv run -p .venv pytest tests/
+        run: uv run -p .venv pytest -vv tests/
       - name: Install optional dependencies
         run: uv sync -p .venv --extra dev --extra test_extras
       - name: Run extra tests
         run: uv run -p .venv pytest tests/ -m extra --extra
+  
+  llm_call_test:
+    name: Run Tests with Real LM
+    runs-on: ubuntu-latest
+    services:
+      ollama:
+        image: ollama/ollama:latest
+        ports:
+          - 11434:11434
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+      - name: Install uv with caching
+        uses: astral-sh/setup-uv@v5
+        with:
+          enable-cache: true
+          cache-dependency-glob: |
+            **/pyproject.toml
+            **/uv.lock
+      - name: Create and activate virtual environment
+        run: |
+          uv venv .venv
+          echo "${{ github.workspace }}/.venv/bin" >> $GITHUB_PATH
+      - name: Install dependencies
+        run: |
+          uv sync --dev -p .venv --extra dev
+          uv pip list
+      - name: Pull LLM
+        run: |
+          timeout 60 bash -c 'until curl -f http://localhost:11434/api/version; do sleep 2; done'
+          curl -X POST http://localhost:11434/api/pull \
+            -H "Content-Type: application/json" \
+            -d '{"name": "llama3.2:3b"}'
+          echo "LM_FOR_TEST=ollama/llama3.2:3b" >> $GITHUB_ENV
+      - name: Run tests
+        run: uv run -p .venv pytest -m llm_call --llm_call -vv --durations=5 tests/
 
   build_package:
     name: Build Package
diff --git a/tests/adapters/test_base_type.py b/tests/adapters/test_base_type.py
index 1776d319..b7d30864 100644
--- a/tests/adapters/test_base_type.py
+++ b/tests/adapters/test_base_type.py
@@ -1,11 +1,10 @@
-
 import pydantic
 
 import dspy
 
 
 def test_basic_extract_custom_type_from_annotation():
-    class Event(dspy.BaseType):
+    class Event(dspy.Type):
         event_name: str
         start_date_time: str
         end_date_time: str | None
@@ -17,7 +16,7 @@ def test_basic_extract_custom_type_from_annotation():
         email: str = dspy.InputField()
         event: Event = dspy.OutputField()
 
-    assert dspy.BaseType.extract_custom_type_from_annotation(ExtractEvent.output_fields["event"].annotation) == [Event]
+    assert dspy.Type.extract_custom_type_from_annotation(ExtractEvent.output_fields["event"].annotation) == [Event]
 
     class ExtractEvents(dspy.Signature):
         """Extract all events from the email content."""
@@ -25,19 +24,17 @@ def test_basic_extract_custom_type_from_annotation():
         email: str = dspy.InputField()
         events: list[Event] = dspy.OutputField()
 
-    assert dspy.BaseType.extract_custom_type_from_annotation(ExtractEvents.output_fields["events"].annotation) == [
-        Event
-    ]
+    assert dspy.Type.extract_custom_type_from_annotation(ExtractEvents.output_fields["events"].annotation) == [Event]
 
 
 def test_extract_custom_type_from_annotation_with_nested_type():
-    class Event(dspy.BaseType):
+    class Event(dspy.Type):
         event_name: str
         start_date_time: str
         end_date_time: str | None
         location: str | None
 
-    class EventIdentifier(dspy.BaseType):
+    class EventIdentifier(dspy.Type):
         model_config = pydantic.ConfigDict(frozen=True)  # Make it hashable
         event_id: str
         event_name: str
@@ -48,7 +45,7 @@ def test_extract_custom_type_from_annotation_with_nested_type():
         email: str = dspy.InputField()
         events: list[dict[EventIdentifier, Event]] = dspy.OutputField()
 
-    assert dspy.BaseType.extract_custom_type_from_annotation(ExtractEvents.output_fields["events"].annotation) == [
+    assert dspy.Type.extract_custom_type_from_annotation(ExtractEvents.output_fields["events"].annotation) == [
         EventIdentifier,
         Event,
     ]
diff --git a/tests/adapters/test_chat_adapter.py b/tests/adapters/test_chat_adapter.py
index e599da58..457ba0b0 100644
--- a/tests/adapters/test_chat_adapter.py
+++ b/tests/adapters/test_chat_adapter.py
@@ -3,7 +3,7 @@ from unittest import mock
 
 import pydantic
 import pytest
-from litellm.utils import Choices, Message, ModelResponse
+from litellm.utils import ChatCompletionMessageToolCall, Choices, Function, Message, ModelResponse
 
 import dspy
 
@@ -422,3 +422,70 @@ async def test_chat_adapter_fallback_to_json_adapter_on_exception_async():
         # The parse should succeed
         result = await adapter.acall(lm, {}, signature, [], {"question": "What is the capital of France?"})
         assert result == [{"answer": "Paris"}]
+
+
+def test_chat_adapter_toolcalls_native_function_calling():
+    class MySignature(dspy.Signature):
+        question: str = dspy.InputField()
+        tools: list[dspy.Tool] = dspy.InputField()
+        answer: str = dspy.OutputField()
+        tool_calls: dspy.ToolCalls = dspy.OutputField()
+
+    def get_weather(city: str) -> str:
+        return f"The weather in {city} is sunny"
+
+    tools = [dspy.Tool(get_weather)]
+
+    adapter = dspy.JSONAdapter(use_native_function_calling=True)
+
+    # Case 1: Tool calls are present in the response, while content is None.
+    with mock.patch("litellm.completion") as mock_completion:
+        mock_completion.return_value = ModelResponse(
+            choices=[
+                Choices(
+                    finish_reason="tool_calls",
+                    index=0,
+                    message=Message(
+                        content=None,
+                        role="assistant",
+                        tool_calls=[
+                            ChatCompletionMessageToolCall(
+                                function=Function(arguments='{"city":"Paris"}', name="get_weather"),
+                                id="call_pQm8ajtSMxgA0nrzK2ivFmxG",
+                                type="function",
+                            )
+                        ],
+                    ),
+                ),
+            ],
+            model="openai/gpt-4o-mini",
+        )
+        result = adapter(
+            dspy.LM(model="openai/gpt-4o-mini", cache=False),
+            {},
+            MySignature,
+            [],
+            {"question": "What is the weather in Paris?", "tools": tools},
+        )
+
+        assert result[0]["tool_calls"] == dspy.ToolCalls(
+            tool_calls=[dspy.ToolCalls.ToolCall(name="get_weather", args={"city": "Paris"})]
+        )
+        # `answer` is not present, so we set it to None
+        assert result[0]["answer"] is None
+
+    # Case 2: Tool calls are not present in the response, while content is present.
+    with mock.patch("litellm.completion") as mock_completion:
+        mock_completion.return_value = ModelResponse(
+            choices=[Choices(message=Message(content="{'answer': 'Paris'}"))],
+            model="openai/gpt-4o-mini",
+        )
+        result = adapter(
+            dspy.LM(model="openai/gpt-4o-mini", cache=False),
+            {},
+            MySignature,
+            [],
+            {"question": "What is the weather in Paris?", "tools": tools},
+        )
+        assert result[0]["answer"] == "Paris"
+        assert result[0]["tool_calls"] is None
diff --git a/tests/adapters/test_json_adapter.py b/tests/adapters/test_json_adapter.py
index eb90a754..d58e5f9e 100644
--- a/tests/adapters/test_json_adapter.py
+++ b/tests/adapters/test_json_adapter.py
@@ -2,7 +2,7 @@ from unittest import mock
 
 import pydantic
 import pytest
-from litellm.utils import Choices, Message, ModelResponse
+from litellm.utils import ChatCompletionMessageToolCall, Choices, Function, Message, ModelResponse
 
 import dspy
 
@@ -650,3 +650,102 @@ async def test_error_message_on_json_adapter_failure_async():
                 await program.acall(question="Dummy question!")
 
             assert "ValueError!" in str(error.value)
+
+
+def test_json_adapter_toolcalls_native_function_calling():
+    class MySignature(dspy.Signature):
+        question: str = dspy.InputField()
+        tools: list[dspy.Tool] = dspy.InputField()
+        answer: str = dspy.OutputField()
+        tool_calls: dspy.ToolCalls = dspy.OutputField()
+
+    def get_weather(city: str) -> str:
+        return f"The weather in {city} is sunny"
+
+    tools = [dspy.Tool(get_weather)]
+
+    adapter = dspy.JSONAdapter(use_native_function_calling=True)
+
+    # Case 1: Tool calls are present in the response, while content is None.
+    with mock.patch("litellm.completion") as mock_completion:
+        mock_completion.return_value = ModelResponse(
+            choices=[
+                Choices(
+                    finish_reason="tool_calls",
+                    index=0,
+                    message=Message(
+                        content=None,
+                        role="assistant",
+                        tool_calls=[
+                            ChatCompletionMessageToolCall(
+                                function=Function(arguments='{"city":"Paris"}', name="get_weather"),
+                                id="call_pQm8ajtSMxgA0nrzK2ivFmxG",
+                                type="function",
+                            )
+                        ],
+                    ),
+                ),
+            ],
+            model="openai/gpt-4o-mini",
+        )
+        result = adapter(
+            dspy.LM(model="openai/gpt-4o-mini", cache=False),
+            {},
+            MySignature,
+            [],
+            {"question": "What is the weather in Paris?", "tools": tools},
+        )
+
+        assert result[0]["tool_calls"] == dspy.ToolCalls(
+            tool_calls=[dspy.ToolCalls.ToolCall(name="get_weather", args={"city": "Paris"})]
+        )
+        # `answer` is not present, so we set it to None
+        assert result[0]["answer"] is None
+
+    # Case 2: Tool calls are not present in the response, while content is present.
+    with mock.patch("litellm.completion") as mock_completion:
+        mock_completion.return_value = ModelResponse(
+            choices=[Choices(message=Message(content="{'answer': 'Paris'}"))],
+            model="openai/gpt-4o-mini",
+        )
+        result = adapter(
+            dspy.LM(model="openai/gpt-4o-mini", cache=False),
+            {},
+            MySignature,
+            [],
+            {"question": "What is the weather in Paris?", "tools": tools},
+        )
+        assert result[0]["answer"] == "Paris"
+        assert result[0]["tool_calls"] is None
+
+
+def test_json_adapter_toolcalls_no_native_function_calling():
+    class MySignature(dspy.Signature):
+        question: str = dspy.InputField()
+        tools: list[dspy.Tool] = dspy.InputField()
+        answer: str = dspy.OutputField()
+        tool_calls: dspy.ToolCalls = dspy.OutputField()
+
+    def get_weather(city: str) -> str:
+        return f"The weather in {city} is sunny"
+
+    tools = [dspy.Tool(get_weather)]
+
+    # Patch _get_structured_outputs_response_format to track calls
+    with mock.patch("dspy.adapters.json_adapter._get_structured_outputs_response_format") as mock_structured:
+        # Patch litellm.completion to return a dummy response
+        with mock.patch("litellm.completion") as mock_completion:
+            mock_completion.return_value = ModelResponse(
+                choices=[Choices(message=Message(content="{'answer': 'sunny', 'tool_calls': {'tool_calls': []}}"))],
+                model="openai/gpt-4o-mini",
+            )
+            adapter = dspy.JSONAdapter(use_native_function_calling=False)
+            lm = dspy.LM(model="openai/gpt-4o-mini", cache=False)
+            adapter(lm, {}, MySignature, [], {"question": "What is the weather in Tokyo?", "tools": tools})
+
+        # _get_structured_outputs_response_format is not called because without using native function calling,
+        # JSONAdapter falls back to json mode for stable quality.
+        mock_structured.assert_not_called()
+        mock_completion.assert_called_once()
+        _, call_kwargs = mock_completion.call_args
+        assert call_kwargs["response_format"] == {"type": "json_object"}
diff --git a/tests/adapters/test_tool.py b/tests/adapters/test_tool.py
index a4ab072d..20f59604 100644
--- a/tests/adapters/test_tool.py
+++ b/tests/adapters/test_tool.py
@@ -366,6 +366,7 @@ async def test_async_concurrent_calls():
     assert end_time - start_time < 0.3
 
 
+@pytest.mark.filterwarnings("ignore::RuntimeWarning")
 def test_async_tool_call_in_sync_mode():
     tool = Tool(async_dummy_function)
     with dspy.context(allow_tool_async_sync_conversion=False):
diff --git a/tests/clients/test_lm.py b/tests/clients/test_lm.py
index e1ff625d..b63406ba 100644
--- a/tests/clients/test_lm.py
+++ b/tests/clients/test_lm.py
@@ -301,7 +301,7 @@ def test_logprobs_included_when_requested():
         )
         result = lm("question")
         assert result[0]["text"] == "test answer"
-        assert result[0]["logprobs"].dict() == {
+        assert result[0]["logprobs"].model_dump() == {
             "content": [
                 {
                     "token": "test",
diff --git a/tests/conftest.py b/tests/conftest.py
index dd57d03f..372585df 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,10 +1,11 @@
 import copy
+import os
 
 import pytest
 
 from tests.test_utils.server import litellm_test_server, read_litellm_test_server_request_logs  # noqa: F401
 
-SKIP_DEFAULT_FLAGS = ["reliability", "extra"]
+SKIP_DEFAULT_FLAGS = ["reliability", "extra", "llm_call"]
 
 
 @pytest.fixture(autouse=True)
@@ -49,3 +50,11 @@ def pytest_collection_modifyitems(config, items):
         for item in items:
             if flag in item.keywords:
                 item.add_marker(skip_mark)
+
+
+@pytest.fixture
+def lm_for_test():
+    model = os.environ.get("LM_FOR_TEST", None)
+    if model is None:
+        pytest.skip("LM_FOR_TEST is not set in the environment variables")
+    return model
diff --git a/tests/primitives/test_base_module.py b/tests/primitives/test_base_module.py
index ee6255bb..5706d248 100644
--- a/tests/primitives/test_base_module.py
+++ b/tests/primitives/test_base_module.py
@@ -230,30 +230,30 @@ def test_load_with_version_mismatch(tmp_path):
         logger.removeHandler(handler)
 
 
-@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="Skip the test if OPENAI_API_KEY is not set.")
-def test_single_module_call_with_usage_tracker():
-    dspy.settings.configure(lm=dspy.LM("openai/gpt-4o-mini", cache=False), track_usage=True)
+@pytest.mark.llm_call
+def test_single_module_call_with_usage_tracker(lm_for_test):
+    dspy.settings.configure(lm=dspy.LM(lm_for_test, cache=False), track_usage=True)
 
     predict = dspy.ChainOfThought("question -> answer")
     output = predict(question="What is the capital of France?")
 
     lm_usage = output.get_lm_usage()
     assert len(lm_usage) == 1
-    assert lm_usage["openai/gpt-4o-mini"]["prompt_tokens"] > 0
-    assert lm_usage["openai/gpt-4o-mini"]["completion_tokens"] > 0
-    assert lm_usage["openai/gpt-4o-mini"]["total_tokens"] > 0
+    assert lm_usage[lm_for_test]["prompt_tokens"] > 0
+    assert lm_usage[lm_for_test]["completion_tokens"] > 0
+    assert lm_usage[lm_for_test]["total_tokens"] > 0
 
     # Test no usage being tracked when cache is enabled
-    dspy.settings.configure(lm=dspy.LM("openai/gpt-4o-mini", cache=True), track_usage=True)
+    dspy.settings.configure(lm=dspy.LM(lm_for_test, cache=True), track_usage=True)
     for _ in range(2):
         output = predict(question="What is the capital of France?")
 
     assert len(output.get_lm_usage()) == 0
 
 
-@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="Skip the test if OPENAI_API_KEY is not set.")
-def test_multi_module_call_with_usage_tracker():
-    dspy.settings.configure(lm=dspy.LM("openai/gpt-4o-mini", cache=False), track_usage=True)
+@pytest.mark.llm_call
+def test_multi_module_call_with_usage_tracker(lm_for_test):
+    dspy.settings.configure(lm=dspy.LM(lm_for_test, cache=False), track_usage=True)
 
     class MyProgram(dspy.Module):
         def __init__(self):
@@ -270,12 +270,13 @@ def test_multi_module_call_with_usage_tracker():
 
     lm_usage = output.get_lm_usage()
     assert len(lm_usage) == 1
-    assert lm_usage["openai/gpt-4o-mini"]["prompt_tokens"] > 0
-    assert lm_usage["openai/gpt-4o-mini"]["prompt_tokens"] > 0
-    assert lm_usage["openai/gpt-4o-mini"]["completion_tokens"] > 0
-    assert lm_usage["openai/gpt-4o-mini"]["total_tokens"] > 0
+    assert lm_usage[lm_for_test]["prompt_tokens"] > 0
+    assert lm_usage[lm_for_test]["prompt_tokens"] > 0
+    assert lm_usage[lm_for_test]["completion_tokens"] > 0
+    assert lm_usage[lm_for_test]["total_tokens"] > 0
 
 
+# TODO: prepare second model for testing this unit test in ci
 @pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="Skip the test if OPENAI_API_KEY is not set.")
 def test_usage_tracker_in_parallel():
     class MyProgram(dspy.Module):
diff --git a/tests/signatures/test_signature.py b/tests/signatures/test_signature.py
index 9d58e1f7..2a18d5ac 100644
--- a/tests/signatures/test_signature.py
+++ b/tests/signatures/test_signature.py
@@ -1,3 +1,4 @@
+from types import UnionType
 from typing import Any, Dict, List, Optional, Tuple, Union
 
 import pydantic
@@ -436,3 +437,139 @@ def test_custom_type_from_different_module():
     path_obj = Path("/test/path")
     pred = dspy.Predict(test_signature)(input=path_obj)
     assert pred.output == "/test/path"
+
+def test_pep604_union_type_inline():
+    sig = Signature(
+        "input1: str | None, input2: None | int -> output_union: int | str"
+    )
+
+    # input1 and input2 test that both 'T | None' and 'None | T' are interpreted as Optional types,
+    # regardless of the order of None in the union expression.
+
+    assert "input1" in sig.input_fields
+    input1_annotation = sig.input_fields["input1"].annotation
+    assert input1_annotation == Optional[str] or (
+        getattr(input1_annotation, "__origin__", None) is Union
+        and str in input1_annotation.__args__
+        and type(None) in input1_annotation.__args__
+    )
+
+    assert "input2" in sig.input_fields
+    input2_annotation = sig.input_fields["input2"].annotation
+    assert input2_annotation == Optional[int] or (
+        getattr(input2_annotation, "__origin__", None) is Union
+        and int in input2_annotation.__args__
+        and type(None) in input2_annotation.__args__
+    )
+
+    assert "output_union" in sig.output_fields
+    output_union_annotation = sig.output_fields["output_union"].annotation
+    assert (
+        getattr(output_union_annotation, "__origin__", None) is Union
+        and int in output_union_annotation.__args__
+        and str in output_union_annotation.__args__
+    )
+
+
+def test_pep604_union_type_inline_equivalence():
+    sig1 = Signature("input: str | None -> output: int | str")
+    sig2 = Signature("input: Optional[str] -> output: Union[int, str]")
+
+    # PEP 604 union types in inline signatures should be equivalent to Optional and Union types
+    assert sig1.equals(sig2)
+
+    # Check that the annotations are equivalent
+    assert sig1.input_fields["input"].annotation == sig2.input_fields["input"].annotation
+    assert sig1.output_fields["output"].annotation == sig2.output_fields["output"].annotation
+
+
+def test_pep604_union_type_inline_nested():
+    sig = Signature(
+        "input: str | (int | float) | None -> output: str"
+    )
+    assert "input" in sig.input_fields
+    input_annotation = sig.input_fields["input"].annotation
+
+    # Check for the correct union: Union[str, int, float, NoneType]
+    assert getattr(input_annotation, "__origin__", None) is Union
+    assert set(input_annotation.__args__) == {str, int, float, type(None)}
+
+
+def test_pep604_union_type_class_nested():
+    class Sig1(Signature):
+        input: str | (int | float) | None = InputField()
+        output: str = OutputField()
+
+    assert "input" in Sig1.input_fields
+    input_annotation = Sig1.input_fields["input"].annotation
+
+    # Check for the correct union: UnionType[str, int, float, NoneType]
+    assert isinstance(input_annotation, UnionType)
+    assert set(input_annotation.__args__) == {str, int, float, type(None)}
+
+
+def test_pep604_union_type_class_equivalence():
+    class Sig1(Signature):
+        input: str | None = InputField()
+        output: int | str = OutputField()
+
+    class Sig2(Signature):
+        input: Optional[str] = InputField()  # noqa: UP045
+        output: Union[int, str] = OutputField()  # noqa: UP007
+
+    # PEP 604 union types in class signatures should be equivalent to Optional and Union types
+    assert Sig1.equals(Sig2)
+
+    # Check that the annotations are equivalent
+    assert Sig1.input_fields["input"].annotation == Sig2.input_fields["input"].annotation
+    assert Sig1.output_fields["output"].annotation == Sig2.output_fields["output"].annotation
+
+    # Check that the pep604 annotations are of type UnionType
+    assert isinstance(Sig1.input_fields["input"].annotation, UnionType)
+    assert isinstance(Sig1.output_fields["output"].annotation, UnionType)
+
+
+def test_pep604_union_type_insert():
+    class PEP604Signature(Signature):
+        input: str | None = InputField()
+        output: int | str = OutputField()
+
+    # This test ensures that inserting a field into a signature with a PEP 604 UnionType works
+
+    # Insert a new input field at the start
+    NewSig = PEP604Signature.prepend("new_input", InputField(), float | int)
+    assert "new_input" in NewSig.input_fields
+
+    new_input_annotation = NewSig.input_fields["new_input"].annotation
+    assert isinstance(new_input_annotation, UnionType)
+    assert set(new_input_annotation.__args__) == {float, int}
+
+    # The original union type field should still be present and correct
+    input_annotation = NewSig.input_fields["input"].annotation
+    output_annotation = NewSig.output_fields["output"].annotation
+
+    assert isinstance(input_annotation, UnionType)
+    assert str in input_annotation.__args__ and type(None) in input_annotation.__args__
+
+    assert isinstance(output_annotation, UnionType)
+    assert set(output_annotation.__args__) == {int, str}
+
+
+def test_pep604_union_type_with_custom_types():
+    class CustomType(pydantic.BaseModel):
+        value: str
+
+    sig = Signature(
+        "input: CustomType | None -> output: int | str",
+        custom_types={"CustomType": CustomType}
+    )
+
+    assert sig.input_fields["input"].annotation == Union[CustomType, None]
+    assert sig.output_fields["output"].annotation == Union[int, str]
+
+    lm = DummyLM([{"output": "processed"}])
+    dspy.settings.configure(lm=lm)
+
+    custom_obj = CustomType(value="test")
+    pred = dspy.Predict(sig)(input=custom_obj)
+    assert pred.output == "processed"
diff --git a/tests/streaming/test_streaming.py b/tests/streaming/test_streaming.py
index d79402d2..fe67bd6d 100644
--- a/tests/streaming/test_streaming.py
+++ b/tests/streaming/test_streaming.py
@@ -1,5 +1,4 @@
 import asyncio
-import os
 import time
 from unittest import mock
 from unittest.mock import AsyncMock
@@ -131,9 +130,9 @@ async def test_custom_status_streaming():
         assert status_messages[2].message == "Predict starting!"
 
 
-@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OpenAI API key not found in environment variables")
+@pytest.mark.llm_call
 @pytest.mark.anyio
-async def test_stream_listener_chat_adapter():
+async def test_stream_listener_chat_adapter(lm_for_test):
     class MyProgram(dspy.Module):
         def __init__(self):
             self.predict1 = dspy.Predict("question->answer")
@@ -154,7 +153,7 @@ async def test_stream_listener_chat_adapter():
         include_final_prediction_in_output_stream=False,
     )
     # Turn off the cache to ensure the stream is produced.
-    with dspy.context(lm=dspy.LM("openai/gpt-4o-mini", cache=False)):
+    with dspy.context(lm=dspy.LM(lm_for_test, cache=False)):
         output = program(x="why did a chicken cross the kitchen?")
         all_chunks = []
         async for value in output:
@@ -194,9 +193,9 @@ async def test_default_status_streaming_in_async_program():
     assert status_messages[1].message == "Tool calling finished! Querying the LLM with tool calling results..."
 
 
-@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OpenAI API key not found in environment variables")
+@pytest.mark.llm_call
 @pytest.mark.anyio
-async def test_stream_listener_json_adapter():
+async def test_stream_listener_json_adapter(lm_for_test):
     class MyProgram(dspy.Module):
         def __init__(self):
             self.predict1 = dspy.Predict("question->answer")
@@ -217,7 +216,7 @@ async def test_stream_listener_json_adapter():
         include_final_prediction_in_output_stream=False,
     )
     # Turn off the cache to ensure the stream is produced.
-    with dspy.context(lm=dspy.LM("openai/gpt-4o-mini", cache=False), adapter=dspy.JSONAdapter()):
+    with dspy.context(lm=dspy.LM(lm_for_test, cache=False), adapter=dspy.JSONAdapter()):
         output = program(x="why did a chicken cross the kitchen?")
         all_chunks = []
         async for value in output:
@@ -261,8 +260,8 @@ async def test_streaming_handles_space_correctly():
     assert all_chunks[0].chunk == "How are you doing?"
 
 
-@pytest.mark.skipif(not os.getenv("OPENAI_API_KEY"), reason="OpenAI API key not found in environment variables")
-def test_sync_streaming():
+@pytest.mark.llm_call
+def test_sync_streaming(lm_for_test):
     class MyProgram(dspy.Module):
         def __init__(self):
             self.predict1 = dspy.Predict("question->answer")
@@ -284,7 +283,7 @@ def test_sync_streaming():
         async_streaming=False,
     )
     # Turn off the cache to ensure the stream is produced.
-    with dspy.context(lm=dspy.LM("openai/gpt-4o-mini", cache=False)):
+    with dspy.context(lm=dspy.LM(lm_for_test, cache=False)):
         output = program(x="why did a chicken cross the kitchen?")
         all_chunks = []
         for value in output:
@@ -388,7 +387,7 @@ async def test_stream_listener_returns_correct_chunk_chat_adapter():
     async def completion_side_effect(*args, **kwargs):
         return stream_generators.pop(0)()  # return new async generator instance
 
-    with mock.patch("litellm.acompletion", side_effect=completion_side_effect) as mock_completion:
+    with mock.patch("litellm.acompletion", side_effect=completion_side_effect):
         program = dspy.streamify(
             MyProgram(),
             stream_listeners=[
@@ -484,7 +483,7 @@ async def test_stream_listener_returns_correct_chunk_json_adapter():
 
     with mock.patch(
         "litellm.acompletion", new_callable=AsyncMock, side_effect=[gpt_4o_mini_stream_1(), gpt_4o_mini_stream_2()]
-    ) as mock_completion:
+    ):
         program = dspy.streamify(
             MyProgram(),
             stream_listeners=[
@@ -763,3 +762,78 @@ async def test_stream_listener_allow_reuse():
     concat_message = "".join([chunk.chunk for chunk in all_chunks])
     # The listener functions twice.
     assert concat_message == "To get to the other side!To get to the other side!"
+
+@pytest.mark.anyio
+async def test_stream_listener_returns_correct_chunk_xml_adapter():
+    class MyProgram(dspy.Module):
+        def __init__(self):
+            super().__init__()
+            self.predict1 = dspy.Predict("question->answer")
+            self.predict2 = dspy.Predict("question,answer->judgement")
+
+        def forward(self, question, **kwargs):
+            answer = self.predict1(question=question, **kwargs).answer
+            judgement = self.predict2(question=question, answer=answer, **kwargs)
+            return judgement
+
+    async def xml_stream_1(*args, **kwargs):
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="answer"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="To"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" get"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" to"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" the"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" other"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" side"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="!"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="/answer"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="completed"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+
+    async def xml_stream_2(*args, **kwargs):
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="judgement"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="The"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" answer"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" is"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=" humorous"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="."))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="/judgement"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="<"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content="completed"))])
+        yield ModelResponseStream(model="gpt-4o-mini", choices=[StreamingChoices(delta=Delta(content=">"))])
+
+    stream_generators = [xml_stream_1, xml_stream_2]
+
+    async def completion_side_effect(*args, **kwargs):
+        return stream_generators.pop(0)()
+
+    with mock.patch("litellm.acompletion", side_effect=completion_side_effect):
+        program = dspy.streamify(
+            MyProgram(),
+            stream_listeners=[
+                dspy.streaming.StreamListener(signature_field_name="answer"),
+                dspy.streaming.StreamListener(signature_field_name="judgement"),
+            ],
+        )
+        with dspy.context(lm=dspy.LM("openai/gpt-4o-mini", cache=False), adapter=dspy.XMLAdapter()):
+            output = program(question="why did a chicken cross the kitchen?")
+            all_chunks = []
+            async for value in output:
+                if isinstance(value, dspy.streaming.StreamResponse):
+                    all_chunks.append(value)
+
+    assert all_chunks[0].predict_name == "predict1"
+    assert all_chunks[0].signature_field_name == "answer"
+    assert all_chunks[0].chunk == "To get to the other side!"
+
+    assert all_chunks[1].predict_name == "predict2"
+    assert all_chunks[1].signature_field_name == "judgement"
+    assert all_chunks[1].chunk == "The answer is humorous."
diff --git a/tests/utils/test_syncify.py b/tests/utils/test_syncify.py
new file mode 100644
index 00000000..6f75d9e8
--- /dev/null
+++ b/tests/utils/test_syncify.py
@@ -0,0 +1,57 @@
+import asyncio
+
+import dspy
+
+
+def test_syncify_in_place():
+    class MyProgram(dspy.Module):
+        async def aforward(self, x: int) -> int:
+            await asyncio.sleep(0.01)
+            return x + 1
+
+    sync_program = dspy.syncify(MyProgram())
+    assert sync_program(1) == 2
+    assert sync_program(2) == 3
+
+
+def test_syncify_with_wrapper():
+    class MyProgram(dspy.Module):
+        async def aforward(self, x: int) -> int:
+            await asyncio.sleep(0.01)
+            return x + 1
+
+    sync_program = dspy.syncify(MyProgram(), in_place=False)
+    assert sync_program(1) == 2
+    assert sync_program(2) == 3
+
+
+def test_syncify_works_with_optimizers():
+    class MyProgram(dspy.Module):
+        def __init__(self):
+            self.predict = dspy.Predict("question->answer")
+
+        async def aforward(self, question: str):
+            return await self.predict.acall(question=question)
+
+    async_program = MyProgram()
+
+    def dummy_metric(gold, pred, traces=None):
+        return True
+
+    # We only test the optimizer completes without errors, so the LM response doesn't matter.
+    lm = dspy.utils.DummyLM([{"answer": "dummy"} for _ in range(100)])
+    dspy.configure(lm=lm)
+
+    dataset = [dspy.Example(question="question", answer="answer").with_inputs("question") for _ in range(10)]
+
+    optimizer = dspy.BootstrapFewShot(metric=dummy_metric, max_bootstrapped_demos=2, max_labeled_demos=0)
+
+    # Test syncify in place
+    sync_program = dspy.syncify(async_program, in_place=True)
+    optimized_program = optimizer.compile(sync_program, trainset=dataset)
+    assert len(optimized_program.predictors()[0].demos) == 2
+
+    # Test syncify with wrapper
+    sync_program = dspy.syncify(async_program, in_place=False)
+    optimized_program = optimizer.compile(sync_program, trainset=dataset)
+    assert len(optimized_program.predictors()[0].demos) == 2
diff --git a/tests/utils/test_usage_tracker.py b/tests/utils/test_usage_tracker.py
index 70244b32..00c9e43b 100644
--- a/tests/utils/test_usage_tracker.py
+++ b/tests/utils/test_usage_tracker.py
@@ -1,7 +1,3 @@
-import os
-
-import pytest
-
 import dspy
 from dspy.utils.usage_tracker import UsageTracker, track_usage
 
@@ -137,12 +133,8 @@ def test_track_usage_with_multiple_models():
     assert total_usage["gpt-3.5-turbo"]["total_tokens"] == 900
 
 
-@pytest.mark.skipif(
-    not os.getenv("OPENAI_API_KEY"),
-    reason="Skip the test if OPENAI_API_KEY is not set.",
-)
-def test_track_usage_context_manager():
-    lm = dspy.LM("openai/gpt-4o-mini", cache=False)
+def test_track_usage_context_manager(lm_for_test):
+    lm = dspy.LM(lm_for_test, cache=False)
     dspy.settings.configure(lm=lm)
 
     predict = dspy.ChainOfThought("question -> answer")
@@ -151,12 +143,12 @@ def test_track_usage_context_manager():
         predict(question="What is the capital of Italy?")
 
     assert len(tracker.usage_data) > 0
-    assert len(tracker.usage_data["openai/gpt-4o-mini"]) == 2
+    assert len(tracker.usage_data[lm_for_test]) == 2
 
     total_usage = tracker.get_total_tokens()
-    assert "openai/gpt-4o-mini" in total_usage
+    assert lm_for_test in total_usage
     assert len(total_usage.keys()) == 1
-    assert isinstance(total_usage["openai/gpt-4o-mini"], dict)
+    assert isinstance(total_usage[lm_for_test], dict)
 
 
 def test_merge_usage_entries_with_new_keys():