Enhance MCP development features and introduce skills management
- Added configuration options for requiring human approval before applying LLM-generated MCP patches. - Updated Docker setup to include skills directory. - Integrated skills management into the backend, allowing for procedural guides and skill matching. - Refactored database initialization to apply Alembic migrations. - Enhanced task approval process to handle MCP patch applications with optional approval. - Introduced new schemas for skills and updated existing APIs to support skills functionality. This commit lays the groundwork for improved agent capabilities and better management of MCP development processes.
This commit is contained in:
@@ -37,6 +37,7 @@ from app.services.device_defaults import resolve_slot_defaults
|
||||
from app.services.investigation_context import gather_investigation_context
|
||||
from app.services.approval_filter import split_proposed_fixes
|
||||
from app.services.troubleshooting_rules import rule_guidance_block
|
||||
from app.services.skill_registry import match_skills, skill_guidance_block
|
||||
from app.services.llm import triage_model, triage_model_info
|
||||
from app.services.llm_usage import LlmUsageTracker, merge_run_history
|
||||
from app.services.model_config import get_routing_config
|
||||
@@ -75,6 +76,7 @@ class AgentState(TypedDict, total=False):
|
||||
prior_context: dict | None
|
||||
investigation_context: dict | None
|
||||
matched_rule: dict | None
|
||||
matched_skills: list[dict] | None
|
||||
follow_up: str | None
|
||||
run_number: int
|
||||
prior_runs: list[dict]
|
||||
@@ -348,6 +350,7 @@ async def triage_node(state: AgentState) -> AgentState:
|
||||
f"Onboard devices to check: {json.dumps(device_summary)}"
|
||||
f"{_investigation_context_block(state.get('investigation_context'))}"
|
||||
f"{rule_guidance_block(state.get('matched_rule'))}"
|
||||
f"{skill_guidance_block(state.get('matched_skills'))}"
|
||||
f"{_prior_context_block(state.get('prior_context'), state.get('follow_up'))}"
|
||||
)
|
||||
triage_info = await triage_model_info()
|
||||
@@ -449,6 +452,7 @@ async def reason_node(state: AgentState) -> AgentState:
|
||||
f"Title: {state['title']}\nIssue: {state['issue']}\nTriage: {json.dumps(triage)}\n\n{diag_text}"
|
||||
f"{_investigation_context_block(state.get('investigation_context'))}"
|
||||
f"{rule_guidance_block(state.get('matched_rule'))}"
|
||||
f"{skill_guidance_block(state.get('matched_skills'))}"
|
||||
f"{_prior_context_block(state.get('prior_context'), state.get('follow_up'))}"
|
||||
)
|
||||
messages = [SystemMessage(content=prompt), HumanMessage(content=ctx)]
|
||||
@@ -604,6 +608,10 @@ async def report_node(state: AgentState) -> AgentState:
|
||||
}
|
||||
if state.get("matched_rule"):
|
||||
report["matched_rule"] = state["matched_rule"]
|
||||
if state.get("matched_skills"):
|
||||
report["matched_skills"] = [
|
||||
{k: v for k, v in s.items() if k != "body"} for s in state["matched_skills"]
|
||||
]
|
||||
|
||||
report = merge_run_history(state.get("prior_runs") or [], run_number, report, llm_summary)
|
||||
|
||||
@@ -786,6 +794,7 @@ async def run_task_agent(task_id: int) -> None:
|
||||
|
||||
devices: list[dict] = []
|
||||
matched_rule: dict | None = None
|
||||
matched_skills: list[dict] = []
|
||||
vessel_name: str | None = None
|
||||
vessel_site: str | None = None
|
||||
vessel_public_ip: str | None = None
|
||||
@@ -795,6 +804,18 @@ async def run_task_agent(task_id: int) -> None:
|
||||
all_devices, routing_text, title=task.title
|
||||
)
|
||||
matched_rule = rule.as_dict() if rule else None
|
||||
rule_id = matched_rule.get("id") if matched_rule else None
|
||||
matched_skills = [
|
||||
{
|
||||
"id": s.id,
|
||||
"name": s.name,
|
||||
"description": s.description,
|
||||
"body": s.body,
|
||||
"score": s.score,
|
||||
"priority": s.priority,
|
||||
}
|
||||
for s in match_skills(task.title, routing_text, matched_rule_id=rule_id)
|
||||
]
|
||||
vres = await db.execute(select(Vessel).where(Vessel.id == task.vessel_id))
|
||||
vessel = vres.scalar_one_or_none()
|
||||
if vessel:
|
||||
@@ -807,6 +828,20 @@ async def run_task_agent(task_id: int) -> None:
|
||||
if d:
|
||||
devices = [_device_dict(d)]
|
||||
|
||||
if not matched_skills:
|
||||
rule_id = matched_rule.get("id") if matched_rule else None
|
||||
matched_skills = [
|
||||
{
|
||||
"id": s.id,
|
||||
"name": s.name,
|
||||
"description": s.description,
|
||||
"body": s.body,
|
||||
"score": s.score,
|
||||
"priority": s.priority,
|
||||
}
|
||||
for s in match_skills(task.title, routing_text, matched_rule_id=rule_id)
|
||||
]
|
||||
|
||||
task.status = TaskStatus.running
|
||||
task.details = details
|
||||
await db.commit()
|
||||
@@ -842,6 +877,14 @@ async def run_task_agent(task_id: int) -> None:
|
||||
f"Matched rule: {matched_rule.get('name')} → {', '.join(matched_rule.get('devices') or [])}",
|
||||
matched_rule,
|
||||
)
|
||||
if matched_skills:
|
||||
names = ", ".join(s["name"] for s in matched_skills)
|
||||
await _emit(
|
||||
task_id,
|
||||
"skill",
|
||||
f"Matched skills: {names}",
|
||||
{"skills": [{k: v for k, v in s.items() if k != "body"} for s in matched_skills]},
|
||||
)
|
||||
|
||||
remaining = await balance.latest_remaining_usd()
|
||||
from app.config import settings
|
||||
@@ -873,6 +916,7 @@ async def run_task_agent(task_id: int) -> None:
|
||||
"run_number": run_number,
|
||||
"prior_runs": prior_runs if continue_mode else [],
|
||||
"matched_rule": matched_rule,
|
||||
"matched_skills": matched_skills or None,
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user