deep q/a skill and skill selection
This commit is contained in:
parent
5c0f0e5830
commit
1a88b0355a
2 changed files with 133 additions and 3 deletions
|
|
@ -228,10 +228,44 @@ def get_agent_skills() -> list[Skill]:
|
||||||
"Find information about Python best practices",
|
"Find information about Python best practices",
|
||||||
"Show me the full API documentation",
|
"Show me the full API documentation",
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
Skill(
|
||||||
|
id="deep-qa",
|
||||||
|
name="Deep Question Answering",
|
||||||
|
description="Multi-step question decomposition and research for complex queries (can take a long time)",
|
||||||
|
tags=["question-answering", "research", "multi-agent", "complex-queries"],
|
||||||
|
input_modes=["application/json"],
|
||||||
|
output_modes=["application/json"],
|
||||||
|
examples=[
|
||||||
|
"What are the architectural patterns used in haiku.rag and how do they compare?",
|
||||||
|
"Analyze the trade-offs between the simple QA and research agents",
|
||||||
|
"What are all the configuration options and their effects?",
|
||||||
|
],
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_skill_preference(task_history: list[Message]) -> str:
|
||||||
|
"""Extract skill preference from task history metadata.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_history: Task history messages
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Skill ID if found in metadata, otherwise "document-qa" (default)
|
||||||
|
"""
|
||||||
|
for msg in task_history:
|
||||||
|
if msg.get("role") == "user":
|
||||||
|
for part in msg.get("parts", []):
|
||||||
|
if part.get("kind") == "data":
|
||||||
|
metadata = part.get("metadata", {})
|
||||||
|
if metadata.get("type") == "skill_preference":
|
||||||
|
skill = part.get("data", {}).get("skill")
|
||||||
|
if skill:
|
||||||
|
return skill
|
||||||
|
return "document-qa"
|
||||||
|
|
||||||
|
|
||||||
def extract_question_from_task(task_history: list[Message]) -> str | None:
|
def extract_question_from_task(task_history: list[Message]) -> str | None:
|
||||||
"""Extract the user's question from task history.
|
"""Extract the user's question from task history.
|
||||||
|
|
||||||
|
|
@ -340,12 +374,17 @@ def create_a2a_app(db_path: Path):
|
||||||
|
|
||||||
await self.storage.update_task(task["id"], state="working")
|
await self.storage.update_task(task["id"], state="working")
|
||||||
|
|
||||||
# Extract the user's question
|
# Extract skill preference and question
|
||||||
question = extract_question_from_task(task.get("history", []))
|
task_history = task.get("history", [])
|
||||||
|
skill = extract_skill_preference(task_history)
|
||||||
|
question = extract_question_from_task(task_history)
|
||||||
|
|
||||||
if not question:
|
if not question:
|
||||||
await self.storage.update_task(task["id"], state="failed")
|
await self.storage.update_task(task["id"], state="failed")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.info(f"Task {task['id']} using skill: {skill}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load conversation context
|
# Load conversation context
|
||||||
context = await self.storage.load_context(task["context_id"]) or []
|
context = await self.storage.load_context(task["context_id"]) or []
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import pytest
|
||||||
|
|
||||||
from haiku.rag.a2a import (
|
from haiku.rag.a2a import (
|
||||||
extract_question_from_task,
|
extract_question_from_task,
|
||||||
|
extract_skill_preference,
|
||||||
|
get_agent_skills,
|
||||||
load_message_history,
|
load_message_history,
|
||||||
save_message_history,
|
save_message_history,
|
||||||
)
|
)
|
||||||
|
|
@ -260,3 +262,92 @@ async def test_a2a_app_has_skills(temp_db_path):
|
||||||
keyword in skill_text
|
keyword in skill_text
|
||||||
for keyword in ["search", "question", "answer", "document", "knowledge"]
|
for keyword in ["search", "question", "answer", "document", "knowledge"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_agent_skills():
|
||||||
|
"""Test that agent skills include both document-qa and deep-qa."""
|
||||||
|
skills = get_agent_skills()
|
||||||
|
|
||||||
|
assert len(skills) == 2
|
||||||
|
|
||||||
|
skill_ids = [skill["id"] for skill in skills]
|
||||||
|
assert "document-qa" in skill_ids
|
||||||
|
assert "deep-qa" in skill_ids
|
||||||
|
|
||||||
|
# Check document-qa skill
|
||||||
|
doc_qa = next(s for s in skills if s["id"] == "document-qa")
|
||||||
|
assert "Document Question Answering" in doc_qa["name"]
|
||||||
|
assert "semantic search" in doc_qa["description"]
|
||||||
|
assert "question-answering" in doc_qa["tags"]
|
||||||
|
|
||||||
|
# Check deep-qa skill
|
||||||
|
deep_qa = next(s for s in skills if s["id"] == "deep-qa")
|
||||||
|
assert "Deep Question Answering" in deep_qa["name"]
|
||||||
|
assert "Multi-step" in deep_qa["description"]
|
||||||
|
assert "research" in deep_qa["tags"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_extract_skill_preference_with_metadata():
|
||||||
|
"""Test extracting skill preference from message metadata."""
|
||||||
|
from fasta2a.schema import DataPart
|
||||||
|
|
||||||
|
task_history: list[Message] = [
|
||||||
|
Message(
|
||||||
|
role="user",
|
||||||
|
parts=[
|
||||||
|
TextPart(kind="text", text="Complex question"),
|
||||||
|
DataPart(
|
||||||
|
kind="data",
|
||||||
|
data={"skill": "deep-qa"},
|
||||||
|
metadata={"type": "skill_preference"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
kind="message",
|
||||||
|
message_id=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
skill = extract_skill_preference(task_history)
|
||||||
|
assert skill == "deep-qa"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_extract_skill_preference_default():
|
||||||
|
"""Test that skill preference defaults to document-qa."""
|
||||||
|
task_history: list[Message] = [
|
||||||
|
Message(
|
||||||
|
role="user",
|
||||||
|
parts=[TextPart(kind="text", text="What is Python?")],
|
||||||
|
kind="message",
|
||||||
|
message_id=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
skill = extract_skill_preference(task_history)
|
||||||
|
assert skill == "document-qa"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_extract_skill_preference_no_skill_in_data():
|
||||||
|
"""Test skill preference when DataPart exists but has no skill."""
|
||||||
|
from fasta2a.schema import DataPart
|
||||||
|
|
||||||
|
task_history: list[Message] = [
|
||||||
|
Message(
|
||||||
|
role="user",
|
||||||
|
parts=[
|
||||||
|
TextPart(kind="text", text="Question"),
|
||||||
|
DataPart(
|
||||||
|
kind="data",
|
||||||
|
data={"other": "value"},
|
||||||
|
metadata={"type": "skill_preference"},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
kind="message",
|
||||||
|
message_id=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
skill = extract_skill_preference(task_history)
|
||||||
|
assert skill == "document-qa"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue