Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
154 lines
5.2 KiB
Python
154 lines
5.2 KiB
Python
from __future__ import annotations
|
|
|
|
from collections import OrderedDict
|
|
|
|
from app.agents.schemas.skills import SkillShortlistEntry
|
|
from app.agents.skills.matcher import score_text_match
|
|
from app.agents.skills.policy import choose_injection_mode, render_skill_shortlist_context
|
|
from app.agents.skills.registry import get_skill_registry
|
|
from app.services.skill_service import SkillService
|
|
|
|
|
|
class RuntimeSkillRetriever:
|
|
def __init__(self, db):
|
|
self.db = db
|
|
|
|
async def shortlist(
|
|
self,
|
|
*,
|
|
user_id: str,
|
|
query_text: str,
|
|
memory_context: str | None = None,
|
|
retrospectives: list[dict] | None = None,
|
|
include_learned: bool = True,
|
|
limit: int = 3,
|
|
) -> list[SkillShortlistEntry]:
|
|
deduped: "OrderedDict[str, SkillShortlistEntry]" = OrderedDict()
|
|
retrospective_text = "\n".join(
|
|
(item.get("summary") or item.get("summary_text") or "")
|
|
for item in (retrospectives or [])
|
|
if isinstance(item, dict)
|
|
)
|
|
|
|
service = SkillService(self.db)
|
|
for skill in await service.list_runtime_candidates(user_id, include_learned=include_learned):
|
|
score, matched_terms = score_text_match(
|
|
query_text,
|
|
skill.name,
|
|
skill.description,
|
|
skill.instructions,
|
|
retrospective_text,
|
|
memory_context,
|
|
)
|
|
if score <= 0:
|
|
continue
|
|
entry = SkillShortlistEntry(
|
|
skill_name=skill.name,
|
|
source="database",
|
|
source_id=skill.id,
|
|
scope=[skill.agent_type, skill.visibility],
|
|
status=skill.status,
|
|
effectiveness=skill.effectiveness,
|
|
score=score,
|
|
matched_terms=matched_terms,
|
|
rationale=(
|
|
"Shadow skill matched current request; keep metadata-only injection."
|
|
if skill.status == "shadow"
|
|
else "Matched against DB skill metadata and instructions."
|
|
),
|
|
summary=skill.description or (skill.instructions[:160] if skill.instructions else None),
|
|
injection_mode=(
|
|
"metadata_only"
|
|
if skill.status == "shadow"
|
|
else choose_injection_mode(score, bool(skill.description or skill.instructions))
|
|
),
|
|
)
|
|
self._upsert(deduped, entry)
|
|
|
|
registry = get_skill_registry()
|
|
if not registry.list_all():
|
|
try:
|
|
registry.load_all()
|
|
except Exception:
|
|
pass
|
|
|
|
for skill in registry.list_all():
|
|
score, matched_terms = score_text_match(
|
|
query_text,
|
|
skill.name,
|
|
skill.description,
|
|
" ".join(skill.tags),
|
|
" ".join(skill.triggers),
|
|
skill.content[:400],
|
|
retrospective_text,
|
|
memory_context,
|
|
)
|
|
if score <= 0:
|
|
continue
|
|
entry = SkillShortlistEntry(
|
|
skill_name=skill.name,
|
|
source=skill.source,
|
|
source_id=skill.source_id or skill.id,
|
|
scope=skill.scope or list(skill.tags),
|
|
status=skill.status,
|
|
effectiveness=skill.effectiveness,
|
|
score=score,
|
|
matched_terms=matched_terms,
|
|
rationale="Matched against local or external skill metadata.",
|
|
summary=skill.description or skill.content[:160],
|
|
injection_mode=choose_injection_mode(
|
|
score,
|
|
bool(skill.description or skill.content),
|
|
),
|
|
)
|
|
self._upsert(deduped, entry)
|
|
|
|
return sorted(deduped.values(), key=lambda item: item.score, reverse=True)[:limit]
|
|
|
|
@staticmethod
|
|
def _upsert(
|
|
deduped: "OrderedDict[str, SkillShortlistEntry]",
|
|
entry: SkillShortlistEntry,
|
|
) -> None:
|
|
existing = deduped.get(entry.skill_name)
|
|
if existing is None or existing.score < entry.score:
|
|
deduped[entry.skill_name] = entry
|
|
|
|
|
|
def build_shortlisted_skill_context(
|
|
shortlist: list[dict] | list[SkillShortlistEntry] | None,
|
|
*,
|
|
agent_type: str | None = None,
|
|
) -> str:
|
|
if not shortlist:
|
|
return ""
|
|
|
|
entries: list[SkillShortlistEntry] = []
|
|
for item in shortlist:
|
|
entry = item if isinstance(item, SkillShortlistEntry) else SkillShortlistEntry.model_validate(item)
|
|
if agent_type and entry.scope and agent_type not in entry.scope:
|
|
continue
|
|
entries.append(entry)
|
|
|
|
return render_skill_shortlist_context(entries)
|
|
|
|
|
|
async def shortlist_skills_for_request(
|
|
db,
|
|
*,
|
|
user_id: str,
|
|
user_query: str,
|
|
memory_context: str | None = None,
|
|
retrospectives: list[dict] | None = None,
|
|
include_learned: bool = True,
|
|
limit: int = 3,
|
|
) -> list[SkillShortlistEntry]:
|
|
return await RuntimeSkillRetriever(db).shortlist(
|
|
user_id=user_id,
|
|
query_text=user_query,
|
|
memory_context=memory_context,
|
|
retrospectives=retrospectives,
|
|
include_learned=include_learned,
|
|
limit=limit,
|
|
)
|