Skip to content

Commit 3ec1dbe

Browse files
committed
feat: implement DefaultKeywordResolver and DefaultSkillPolicy
1 parent b64813f commit 3ec1dbe

1 file changed

Lines changed: 73 additions & 4 deletions

File tree

  • src/google/adk_community/plugins/taxonomy

src/google/adk_community/plugins/taxonomy/policy.py

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,42 @@ async def resolve_taxonomies(
6969
return list(active_domains)
7070

7171

72+
class DefaultKeywordResolver(TaxonomyResolver):
73+
"""Declarative, configuration-driven keyword/phrase resolver.
74+
75+
Scans user prompt history for triggering phrases defined directly inside each
76+
taxonomy term's triggers list or alt_labels, resolving active domains natively.
77+
"""
78+
79+
def __init__(self, registry: Any):
80+
self.registry = registry
81+
82+
async def resolve_taxonomies(self, context: ReadonlyContext, llm_request: LlmRequest) -> list[str]:
83+
active_domains: set[str] = set()
84+
85+
for term_id in self.registry.list_ids():
86+
term = self.registry.get_term(term_id)
87+
if term:
88+
triggers = getattr(term, "triggers", [])
89+
if not triggers and hasattr(term, "model_extra"):
90+
triggers = (term.model_extra or {}).get("triggers", [])
91+
92+
# Fall back to alt_labels as secondary keyword triggers
93+
if not triggers and hasattr(term, "alt_labels"):
94+
triggers = term.alt_labels
95+
96+
if triggers:
97+
for turn in llm_request.contents:
98+
for part in turn.parts:
99+
if part.text:
100+
text_upper = part.text.upper()
101+
if any(str(phrase).upper() in text_upper for phrase in triggers):
102+
active_domains.add(term_id)
103+
break
104+
105+
return list(active_domains)
106+
107+
72108
class SkillPolicy(ABC):
73109
"""Abstract policy engine determining skill execution permissions and instruction shaping.
74110
@@ -175,13 +211,38 @@ def _get_taxonomy_binds(skill: Skill) -> list[str]:
175211
return list(binds)
176212

177213

214+
def _interpolate_variables(text: str, active_taxonomies: list[str], registry: Optional[Any]) -> str:
215+
if not text or not registry:
216+
return text
217+
218+
import re
219+
pattern = r"\{taxonomy:([a-zA-Z0-9_-]+)\}"
220+
221+
def replace(match):
222+
var_name = match.group(1)
223+
for tax_id in active_taxonomies:
224+
term = registry.get_term(tax_id)
225+
if term:
226+
variables = getattr(term, "variables", {})
227+
if not variables and hasattr(term, "model_extra"):
228+
variables = (term.model_extra or {}).get("variables", {})
229+
if variables and var_name in variables:
230+
return str(variables[var_name])
231+
return ""
232+
233+
return re.sub(pattern, replace, text)
234+
235+
178236
class DefaultSkillPolicy(SkillPolicy):
179237
"""Default skill policy using taxonomy-bind set-intersection matching.
180238
181239
If a skill has no taxonomy binds defined, it is treated as unrestricted/allowed by default.
182240
If it has binds, at least one bind must intersect with the active taxonomy set.
183241
"""
184242

243+
def __init__(self, registry: Optional[Any] = None):
244+
self.registry = registry
245+
185246
def is_skill_allowed(
186247
self,
187248
skill: Skill,
@@ -201,17 +262,25 @@ def shape_instructions(
201262
context: ReadonlyContext,
202263
original_instructions: str,
203264
) -> str:
204-
# No-op pass-through for default behavior
205-
return original_instructions
265+
active_taxonomies = context.state.get("_active_taxonomies") or []
266+
return _interpolate_variables(original_instructions, active_taxonomies, self.registry)
267+
268+
def shape_description(
269+
self,
270+
skill: Skill,
271+
context: ReadonlyContext,
272+
original_description: str,
273+
) -> str:
274+
active_taxonomies = context.state.get("_active_taxonomies") or []
275+
return _interpolate_variables(original_description, active_taxonomies, self.registry)
206276

207277
def shape_system_instruction(
208278
self,
209279
context: ReadonlyContext,
210280
active_taxonomies: list[str],
211281
original_instructions: str,
212282
) -> str:
213-
# No-op pass-through for default behavior
214-
return original_instructions
283+
return _interpolate_variables(original_instructions, active_taxonomies, self.registry)
215284

216285
def prioritize_skills(
217286
self,

0 commit comments

Comments
 (0)