@@ -174,16 +174,146 @@ Automatic storage of:
174174
175175## Testing
176176
177- Run the test suite:
177+ ### Unit Tests
178+
179+ Run unit tests (no API keys required):
178180
179181``` bash
180- # Unit tests
181182make unit sample-deep-agent
183+ ```
184+
185+ ### Integration Tests
186+
187+ Integration tests require API keys and make real API calls:
188+
189+ ``` bash
190+ # Set up environment variables first
191+ export SILICONFLOW_API_KEY=your_key_here
192+ export TAVILY_API_KEY=your_key_here
182193
183- # Integration tests (requires API keys)
194+ # Run all integration tests
184195make integration sample-deep-agent
185196
186- # All tests
197+ # Run specific HITL integration test
198+ cd apps/sample-deep-agent
199+ uv run pytest tests/integration/test_hitl.py::TestHITLWorkflow::test_comprehensive_hitl_workflow -v -s
200+ ```
201+
202+ ### Human-in-the-Loop (HITL) Testing
203+
204+ The agent includes comprehensive HITL integration tests that verify interrupt functionality with real LLM calls.
205+
206+ #### HITL Configuration
207+
208+ Configure interrupts by passing ` interrupt_on ` and ` subagent_interrupts ` to ` make_graph() ` :
209+
210+ ``` python
211+ from sample_deep_agent.graph import make_graph
212+
213+ # Define interrupt configuration
214+ interrupt_on = {
215+ " task" : {" allowed_decisions" : [" approve" , " reject" ]}, # Only approve/reject
216+ " write_todos" : False , # Don't interrupt write_todos
217+ " think_tool" : False , # Don't interrupt think_tool
218+ " deep_web_search" : True , # Interrupt at top level
219+ }
220+
221+ subagent_interrupts = {
222+ " research-agent" : {
223+ " deep_web_search" : True , # Interrupt in subagent too
224+ " think_tool" : False , # Don't interrupt think_tool in subagent
225+ }
226+ }
227+
228+ # Create agent with HITL configuration
229+ agent = make_graph(
230+ config = {" configurable" : {" max_todos" : 1 }},
231+ interrupt_on = interrupt_on,
232+ subagent_interrupts = subagent_interrupts
233+ )
234+ ```
235+
236+ #### Interrupt Decision Types
237+
238+ Three types of decisions are supported:
239+
240+ 1 . ** Approve** : Execute tool with original arguments
241+ ``` python
242+ {" type" : " approve" }
243+ ```
244+
245+ 2 . ** Reject** : Skip tool execution (agent receives error message)
246+ ``` python
247+ {" type" : " reject" }
248+ ```
249+
250+ 3 . ** Edit** : Modify arguments before execution
251+ ``` python
252+ {
253+ " type" : " edit" ,
254+ " edited_action" : {
255+ " name" : " tool_name" ,
256+ " args" : {" modified" : " arguments" }
257+ }
258+ }
259+ ```
260+
261+ #### HITL Workflow Example
262+
263+ ``` python
264+ import uuid
265+ from langchain.messages import HumanMessage
266+ from langgraph.types import Command
267+
268+ # Use thread_id for state persistence (required for HITL)
269+ thread_id = str (uuid.uuid4())
270+ thread_config = {" configurable" : {" thread_id" : thread_id}}
271+
272+ # Initial invocation
273+ result = await agent.ainvoke(
274+ {" messages" : [HumanMessage(content = " What are the core features of LangChain v1?" )]},
275+ config = thread_config
276+ )
277+
278+ # Handle interrupts
279+ while result.get(" __interrupt__" ):
280+ interrupts = result[" __interrupt__" ][0 ].value
281+ action_requests = interrupts[" action_requests" ]
282+
283+ # Make decisions for each action
284+ decisions = []
285+ for action in action_requests:
286+ if action[" name" ] == " task" :
287+ decisions.append({" type" : " approve" })
288+ elif action[" name" ] == " deep_web_search" :
289+ decisions.append({" type" : " reject" })
290+ else :
291+ decisions.append({" type" : " approve" })
292+
293+ # Resume with decisions (must use same thread_config)
294+ result = await agent.ainvoke(
295+ Command(resume = {" decisions" : decisions}),
296+ config = thread_config
297+ )
298+
299+ # Get final result
300+ final_message = result[" messages" ][- 1 ]
301+ print (final_message.content)
302+ ```
303+
304+ #### Key Features Tested
305+
306+ - ✅ Allowed decisions configuration (restrict to approve/reject only)
307+ - ✅ Top-level tool approval/rejection
308+ - ✅ Subagent-specific interrupt overrides
309+ - ✅ Multiple concurrent tool interrupts
310+ - ✅ Agent resilience when tools are rejected
311+ - ✅ Verification that rejected tools don't execute
312+
313+ ### All Tests
314+
315+ ``` bash
316+ # Run all tests across the monorepo
187317make test
188318```
189319
0 commit comments