yakilee Claude Opus 4.6 commited on
Commit
a8ebe97
·
1 Parent(s): 601f310

feat: configure Parlant guidelines

Browse files

Add 11 guidelines across 5 journey phases plus 3 global guidelines.
Each has condition/action text and tool associations. Includes INGEST
extraction, PRESCREEN search/refine/relax, VALIDATE_TRIALS evaluation,
GAP_FOLLOWUP analysis, SUMMARY generation, and medical disclaimer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

trialpath/agent/guidelines.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Parlant guideline configuration for the TrialPath agent."""
2
+ from parlant.sdk import Agent, Guideline
3
+
4
+ from trialpath.agent.tools import (
5
+ analyze_gaps,
6
+ evaluate_trial_eligibility,
7
+ extract_patient_profile,
8
+ generate_search_anchors,
9
+ refine_search_query,
10
+ relax_search_query,
11
+ search_clinical_trials,
12
+ )
13
+
14
+ GUIDELINE_SPECS: list[dict] = [
15
+ # INGEST guidelines
16
+ {
17
+ "condition": "the patient uploads medical documents",
18
+ "action": (
19
+ "Extract a structured patient profile from the uploaded documents "
20
+ "using the extract_patient_profile tool"
21
+ ),
22
+ "tools": [extract_patient_profile],
23
+ "phase": "INGEST",
24
+ },
25
+ {
26
+ "condition": "the extracted patient profile is missing critical data like stage or ECOG",
27
+ "action": (
28
+ "Ask the patient to provide the missing critical information "
29
+ "or upload additional documents"
30
+ ),
31
+ "tools": [],
32
+ "phase": "INGEST",
33
+ },
34
+ # PRESCREEN guidelines
35
+ {
36
+ "condition": "the patient profile is confirmed and complete",
37
+ "action": (
38
+ "Generate search anchors from the patient profile and search "
39
+ "for matching clinical trials using generate_search_anchors "
40
+ "then search_clinical_trials"
41
+ ),
42
+ "tools": [generate_search_anchors, search_clinical_trials],
43
+ "phase": "PRESCREEN",
44
+ },
45
+ {
46
+ "condition": "the trial search returns more than 50 results",
47
+ "action": (
48
+ "Refine the search query to reduce the result set "
49
+ "using the refine_search_query tool"
50
+ ),
51
+ "tools": [refine_search_query],
52
+ "phase": "PRESCREEN",
53
+ },
54
+ {
55
+ "condition": "the trial search returns 0 results",
56
+ "action": (
57
+ "Relax the search query to broaden the result set "
58
+ "using the relax_search_query tool"
59
+ ),
60
+ "tools": [relax_search_query],
61
+ "phase": "PRESCREEN",
62
+ },
63
+ # VALIDATE_TRIALS guideline
64
+ {
65
+ "condition": "there are trial candidates to evaluate",
66
+ "action": (
67
+ "Evaluate each trial candidate's eligibility using the "
68
+ "evaluate_trial_eligibility tool with dual-model approach"
69
+ ),
70
+ "tools": [evaluate_trial_eligibility],
71
+ "phase": "VALIDATE_TRIALS",
72
+ },
73
+ # GAP_FOLLOWUP guideline
74
+ {
75
+ "condition": "eligibility evaluation reveals unknown criteria or gaps",
76
+ "action": (
77
+ "Analyze gaps across all evaluated trials and present actionable "
78
+ "next steps using the analyze_gaps tool"
79
+ ),
80
+ "tools": [analyze_gaps],
81
+ "phase": "GAP_FOLLOWUP",
82
+ },
83
+ # SUMMARY guideline
84
+ {
85
+ "condition": "all trials have been evaluated and gaps analyzed",
86
+ "action": (
87
+ "Generate a comprehensive summary report with eligible, uncertain, "
88
+ "and ineligible trial counts plus a doctor packet for export"
89
+ ),
90
+ "tools": [],
91
+ "phase": "SUMMARY",
92
+ },
93
+ # Global guidelines
94
+ {
95
+ "condition": "the patient asks about a specific NCT trial by ID",
96
+ "action": (
97
+ "Look up the specific trial using the search_clinical_trials tool "
98
+ "with the provided NCT ID"
99
+ ),
100
+ "tools": [search_clinical_trials],
101
+ "phase": "GLOBAL",
102
+ },
103
+ {
104
+ "condition": "the patient seems confused or asks for help",
105
+ "action": (
106
+ "Explain the current step in the journey, what data is needed, "
107
+ "and what will happen next in simple, empathetic language"
108
+ ),
109
+ "tools": [],
110
+ "phase": "GLOBAL",
111
+ },
112
+ {
113
+ "condition": "the conversation involves medical information or clinical decisions",
114
+ "action": (
115
+ "Include a disclaimer that this tool is for informational purposes only "
116
+ "and does not constitute medical advice. Recommend consulting with "
117
+ "their healthcare provider"
118
+ ),
119
+ "tools": [],
120
+ "phase": "GLOBAL",
121
+ },
122
+ ]
123
+
124
+
125
+ def configure_guidelines(agent: Agent) -> list[Guideline]:
126
+ """Configure all guidelines on the given agent.
127
+
128
+ Returns the list of created Guideline objects.
129
+ """
130
+ guidelines = []
131
+ for spec in GUIDELINE_SPECS:
132
+ guideline = agent.create_guideline(
133
+ condition=spec["condition"],
134
+ action=spec["action"],
135
+ tools=spec["tools"],
136
+ )
137
+ guidelines.append(guideline)
138
+ return guidelines
trialpath/tests/test_guidelines.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TDD tests for Parlant guideline configuration."""
2
+ from unittest.mock import MagicMock, call
3
+
4
+ import pytest
5
+
6
+ from trialpath.agent.guidelines import GUIDELINE_SPECS, configure_guidelines
7
+
8
+
9
+ class TestGuidelineConfiguration:
10
+ """Test guideline setup."""
11
+
12
+ @pytest.fixture
13
+ def mock_agent(self):
14
+ agent = MagicMock()
15
+ agent.create_guideline.return_value = MagicMock()
16
+ return agent
17
+
18
+ def test_correct_number_of_guidelines(self, mock_agent):
19
+ """Should create exactly 11 guidelines."""
20
+ guidelines = configure_guidelines(mock_agent)
21
+ assert len(guidelines) == 11
22
+ assert mock_agent.create_guideline.call_count == 11
23
+
24
+ def test_each_guideline_has_condition_and_action(self):
25
+ """Each spec should have condition and action strings."""
26
+ for spec in GUIDELINE_SPECS:
27
+ assert "condition" in spec
28
+ assert "action" in spec
29
+ assert isinstance(spec["condition"], str)
30
+ assert isinstance(spec["action"], str)
31
+ assert len(spec["condition"]) > 10
32
+ assert len(spec["action"]) > 10
33
+
34
+ def test_ingest_guidelines_count(self):
35
+ """Should have 2 INGEST phase guidelines."""
36
+ ingest = [s for s in GUIDELINE_SPECS if s["phase"] == "INGEST"]
37
+ assert len(ingest) == 2
38
+
39
+ def test_prescreen_guidelines_count(self):
40
+ """Should have 3 PRESCREEN phase guidelines."""
41
+ prescreen = [s for s in GUIDELINE_SPECS if s["phase"] == "PRESCREEN"]
42
+ assert len(prescreen) == 3
43
+
44
+ def test_validate_trials_guidelines_count(self):
45
+ """Should have 1 VALIDATE_TRIALS guideline."""
46
+ validate = [s for s in GUIDELINE_SPECS if s["phase"] == "VALIDATE_TRIALS"]
47
+ assert len(validate) == 1
48
+
49
+ def test_gap_followup_guidelines_count(self):
50
+ """Should have 1 GAP_FOLLOWUP guideline."""
51
+ gap = [s for s in GUIDELINE_SPECS if s["phase"] == "GAP_FOLLOWUP"]
52
+ assert len(gap) == 1
53
+
54
+ def test_summary_guidelines_count(self):
55
+ """Should have 1 SUMMARY guideline."""
56
+ summary = [s for s in GUIDELINE_SPECS if s["phase"] == "SUMMARY"]
57
+ assert len(summary) == 1
58
+
59
+ def test_global_guidelines_count(self):
60
+ """Should have 3 global guidelines."""
61
+ global_g = [s for s in GUIDELINE_SPECS if s["phase"] == "GLOBAL"]
62
+ assert len(global_g) == 3
63
+
64
+ def test_tool_associations(self):
65
+ """Guidelines with tools should reference correct tool entries."""
66
+ from parlant.sdk import ToolEntry
67
+
68
+ for spec in GUIDELINE_SPECS:
69
+ for tool in spec["tools"]:
70
+ assert isinstance(tool, ToolEntry), (
71
+ f"Tool {tool} in guideline '{spec['condition'][:30]}...' "
72
+ f"is not a ToolEntry"
73
+ )
74
+
75
+ def test_medical_disclaimer_guideline_exists(self):
76
+ """Should have a medical disclaimer guideline."""
77
+ disclaimer = [
78
+ s for s in GUIDELINE_SPECS
79
+ if "disclaimer" in s["action"].lower() or "medical advice" in s["action"].lower()
80
+ ]
81
+ assert len(disclaimer) >= 1
82
+
83
+ def test_configure_passes_tools_to_agent(self, mock_agent):
84
+ """configure_guidelines should pass tools list to create_guideline."""
85
+ configure_guidelines(mock_agent)
86
+
87
+ # Check that at least one call included tools
88
+ calls_with_tools = [
89
+ c for c in mock_agent.create_guideline.call_args_list
90
+ if c.kwargs.get("tools")
91
+ ]
92
+ assert len(calls_with_tools) > 0