@@ -20,27 +20,13 @@ def test_forbid_configure_call_in_child_thread():
2020 dspy .configure (lm = dspy .LM ("openai/gpt-4o" ), adapter = dspy .JSONAdapter (), callbacks = [lambda x : x ])
2121
2222 def worker ():
23- with pytest .raises (RuntimeError , match = "Cannot call dspy.configure in a child thread " ):
23+ with pytest .raises (RuntimeError , match = "Cannot call dspy.configure" ):
2424 dspy .configure (lm = dspy .LM ("openai/gpt-4o-mini" ), callbacks = [])
2525
2626 with ThreadPoolExecutor (max_workers = 1 ) as executor :
2727 executor .submit (worker )
2828
2929
30- @pytest .mark .asyncio
31- async def test_forbid_configure_call_in_async_function ():
32- with pytest .raises (
33- RuntimeError ,
34- match = r"dspy.settings.configure\(\.\.\.\) cannot be called a second time from*" ,
35- ):
36- dspy .configure (lm = dspy .LM ("openai/gpt-4o" ), adapter = dspy .JSONAdapter (), callbacks = [lambda x : x ])
37- dspy .configure (lm = dspy .LM ("openai/gpt-4o" ), adapter = dspy .JSONAdapter (), callbacks = [lambda x : x ])
38-
39- with dspy .context (lm = dspy .LM ("openai/gpt-4o-mini" ), callbacks = []):
40- # context is allowed
41- pass
42-
43-
4430def test_dspy_context ():
4531 dspy .configure (lm = dspy .LM ("openai/gpt-4o" ), adapter = dspy .JSONAdapter (), callbacks = [lambda x : x ])
4632 with dspy .context (lm = dspy .LM ("openai/gpt-4o-mini" ), callbacks = []):
@@ -165,3 +151,43 @@ async def aforward(self, question: str) -> str:
165151 # The main thread is not affected by the context
166152 assert dspy .settings .lm .model == "openai/gpt-4.1"
167153 assert dspy .settings .trace == []
154+
155+
156+ @pytest .mark .asyncio
157+ async def test_dspy_configure_allowance_async ():
158+ def bar1 ():
159+ # `dspy.configure` is disallowed in different async tasks from the initial one.
160+ # In this case, foo1 (async) calls bar1 (sync), and bar1 uses the async task from foo1.
161+ with pytest .raises (RuntimeError ) as e :
162+ dspy .configure (lm = dspy .LM ("openai/gpt-4o" ))
163+ assert "dspy.settings.configure(...) can only be called from the same async" in str (e .value )
164+
165+ async def foo1 ():
166+ bar1 ()
167+ await asyncio .sleep (0.1 )
168+
169+ async def foo2 ():
170+ # `dspy.configure` is disallowed in different async tasks from the initial one.
171+ with pytest .raises (RuntimeError ) as e :
172+ dspy .configure (lm = dspy .LM ("openai/gpt-4o" ))
173+ assert "dspy.settings.configure(...) can only be called from the same async" in str (e .value )
174+ await asyncio .sleep (0.1 )
175+
176+ async def foo3 ():
177+ # `dspy.context` is allowed in different async tasks from the initial one.
178+ with dspy .context (lm = dspy .LM ("openai/gpt-4o" )):
179+ await asyncio .sleep (0.1 )
180+
181+ async def foo4 ():
182+ # foo4 is directly invoked by the entry task, so it has the same async task as the entry task.
183+ dspy .configure (lm = dspy .LM ("openai/gpt-4o" ))
184+ await asyncio .sleep (0.1 )
185+
186+ # `dspy.configure` is allowed to be called multiple times in the same async task.
187+ dspy .configure (lm = dspy .LM ("openai/gpt-4o-mini" ))
188+ dspy .configure (lm = dspy .LM ("openai/gpt-4o" ))
189+ dspy .configure (adapter = dspy .JSONAdapter ())
190+
191+ await asyncio .gather (foo1 (), foo2 (), foo3 ())
192+
193+ foo4 ()
0 commit comments