Skip to content

Commit adee012

Browse files
authored
Merge branch 'main' into feat/auto_detect_skills
2 parents 362a85a + 36b93b0 commit adee012

File tree

5 files changed

+140
-8
lines changed

5 files changed

+140
-8
lines changed

veadk/agent.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class Agent(LlmAgent):
158158
enable_dataset_gen: bool = False
159159

160160
enable_dynamic_load_skills: bool = False
161+
_skills_with_checklist: Dict[str, Any] = {}
161162

162163
def model_post_init(self, __context: Any) -> None:
163164
super().model_post_init(None) # for sub_agents init
@@ -303,6 +304,21 @@ def model_post_init(self, __context: Any) -> None:
303304

304305
if self.skills:
305306
self.load_skills()
307+
from veadk.skills.utils import create_init_skill_check_list_callback
308+
309+
init_callback = create_init_skill_check_list_callback(
310+
self._skills_with_checklist
311+
)
312+
if self.before_tool_callback:
313+
if isinstance(self.before_tool_callback, list):
314+
self.before_tool_callback.append(init_callback)
315+
else:
316+
self.before_tool_callback = [
317+
self.before_tool_callback,
318+
init_callback,
319+
]
320+
else:
321+
self.before_tool_callback = init_callback
306322

307323
if self.example_store:
308324
from google.adk.tools.example_tool import ExampleTool
@@ -440,9 +456,25 @@ def load_skills(self):
440456
self.instruction += "\nYou have the following skills:\n"
441457

442458
for skill in self.skills_dict.values():
459+
skills[skill.name] = skill
460+
if skills:
461+
self._skills_with_checklist = skills
462+
463+
self.instruction += "\nYou have the following skills:\n"
464+
465+
has_checklist = False
466+
for skill in skills.values():
443467
self.instruction += (
444468
f"- name: {skill.name}\n- description: {skill.description}\n\n"
445469
)
470+
if skill.checklist:
471+
has_checklist = True
472+
473+
if has_checklist:
474+
self.instruction += (
475+
"Some skills have a checklist that you must complete step by step. "
476+
"Use the `update_check_list` tool to mark each item as completed.\n\n"
477+
)
446478

447479
if self.skills_mode not in [
448480
"skills_sandbox",

veadk/skills/skill.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
from pydantic import BaseModel
16-
from typing import Optional
16+
from typing import Optional, List, Dict
1717

1818

1919
class Skill(BaseModel):
@@ -22,3 +22,7 @@ class Skill(BaseModel):
2222
path: str # local path or tos path
2323
skill_space_id: Optional[str] = None
2424
bucket_name: Optional[str] = None
25+
checklist: List[Dict[str, str]] = []
26+
27+
def get_checklist_items(self) -> List[str]:
28+
return [item.get("item", item.get("id", "")) for item in self.checklist]

veadk/skills/utils.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,74 @@
1717
import os
1818
import frontmatter
1919

20+
from google.adk.tools import BaseTool, ToolContext
21+
from typing import Any, Dict, Optional, Callable
22+
2023
from veadk.skills.skill import Skill
2124
from veadk.utils.logger import get_logger
2225
from veadk.utils.volcengine_sign import ve_request
2326

2427
logger = get_logger(__name__)
2528

2629

30+
def update_check_list(
31+
tool_context: ToolContext, skill_name: str, check_item: str, state: bool
32+
):
33+
"""
34+
Update the checklist item state for a specific skill.
35+
Use this tool to mark checklist items as completed during skill execution.
36+
37+
eg:
38+
update_check_list(skill_name="skill-creator", check_item="analyze_content", state=True)
39+
"""
40+
agent_name = tool_context.agent_name
41+
if agent_name not in tool_context.state:
42+
tool_context.state[agent_name] = {}
43+
if skill_name not in tool_context.state[agent_name]:
44+
tool_context.state[agent_name][skill_name] = {}
45+
if "check_list" not in tool_context.state[agent_name][skill_name]:
46+
tool_context.state[agent_name][skill_name]["check_list"] = {}
47+
tool_context.state[agent_name][skill_name]["check_list"][check_item] = state
48+
logger.info(f"Updated agent[{agent_name}] state: {tool_context.state[agent_name]}")
49+
50+
51+
def create_init_skill_check_list_callback(
52+
skills_with_checklist: Dict[str, Skill],
53+
) -> Callable[[BaseTool, Dict[str, Any], ToolContext], Optional[Dict]]:
54+
"""
55+
Create a callback function to initialize checklist when a skill is invoked.
56+
57+
Args:
58+
skills_with_checklist: Dictionary mapping skill names to Skill objects
59+
60+
Returns:
61+
A callback function for before_tool_callback
62+
"""
63+
64+
def init_skill_check_list(
65+
tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext
66+
) -> Optional[Dict]:
67+
"""Callback to initialize checklist when a skill is invoked."""
68+
if tool.name == "skills_tool":
69+
skill_name = args.get("command")
70+
agent_name = tool_context.agent_name
71+
if skill_name in skills_with_checklist:
72+
skill = skills_with_checklist[skill_name]
73+
check_list_items = skill.get_checklist_items()
74+
check_list_state = {item: False for item in check_list_items}
75+
if agent_name not in tool_context.state:
76+
tool_context.state[agent_name] = {}
77+
tool_context.state[agent_name][skill_name] = {
78+
"check_list": check_list_state
79+
}
80+
logger.info(
81+
f"Initialized agent[{agent_name}] skill[{skill_name}] check_list: {check_list_state}"
82+
)
83+
return None
84+
85+
return init_skill_check_list
86+
87+
2788
def load_skill_from_directory(skill_directory: Path) -> Skill:
2889
logger.info(f"Load skill from {skill_directory}")
2990
skill_readme = skill_directory / "SKILL.md"
@@ -35,6 +96,7 @@ def load_skill_from_directory(skill_directory: Path) -> Skill:
3596

3697
skill_name = skill.get("name", "")
3798
skill_description = skill.get("description", "")
99+
checklist = skill.get("checklist", [])
38100

39101
if not skill_name or not skill_description:
40102
logger.error(
@@ -47,10 +109,14 @@ def load_skill_from_directory(skill_directory: Path) -> Skill:
47109
logger.info(
48110
f"Successfully loaded skill {skill_name} locally from {skill_readme}, name={skill_name}, description={skill_description}"
49111
)
112+
if checklist:
113+
logger.info(f"Skill {skill_name} checklist: {checklist}")
114+
50115
return Skill(
51116
name=skill_name, # type: ignore
52117
description=skill_description, # type: ignore
53118
path=str(skill_directory),
119+
checklist=checklist,
54120
)
55121

56122

veadk/toolkits/apps/reverse_mcp/server_with_reverse_mcp.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
import asyncio
1616
import json
1717
import uuid
18-
from typing import TYPE_CHECKING, Any, Optional
18+
from typing import TYPE_CHECKING, Any, Callable, Optional
1919

2020
from fastapi import FastAPI, HTTPException, Request, Response, WebSocket
2121
from fastapi.responses import StreamingResponse
22-
from google.adk.agents.run_config import StreamingMode
22+
from google.adk.agents.run_config import RunConfig, StreamingMode
2323
from google.adk.artifacts import InMemoryArtifactService
2424
from google.adk.cli.adk_web_server import RunAgentRequest
25-
from google.adk.runners import Runner as GoogleRunner, RunConfig
25+
from google.adk.runners import Runner as GoogleRunner
2626
from google.adk.sessions import InMemorySessionService, Session
2727
from google.adk.tools.mcp_tool.mcp_session_manager import (
2828
StreamableHTTPConnectionParams,
@@ -42,6 +42,12 @@
4242
REVERSE_MCP_HEADER_KEY = "X-Reverse-MCP-ID"
4343

4444

45+
class ExtraRoute(BaseModel):
46+
path: str
47+
endpoint: Callable
48+
methods: list[str]
49+
50+
4551
class WebsocketSessionManager:
4652
def __init__(self):
4753
# ws id -> ws instance
@@ -93,13 +99,21 @@ def __init__(
9399
agent: "Agent",
94100
host: str = "0.0.0.0",
95101
port: int = 8000,
102+
extra_routes: list[ExtraRoute] | None = None,
96103
):
97104
self.agent = agent
98105

99106
self.host = host
100107
self.port = port
101108

102-
self.app = FastAPI()
109+
self.extra_routes = extra_routes
110+
111+
self.app = FastAPI(
112+
openapi_url=None,
113+
docs_url=None,
114+
redoc_url=None,
115+
swagger_ui_oauth2_redirect_url=None,
116+
)
103117

104118
self.artifact_service = InMemoryArtifactService()
105119

@@ -215,7 +229,8 @@ def _get_session_service(websocket_id: str) -> InMemorySessionService:
215229
"""Get session service for the websocket client."""
216230
if websocket_id not in self.ws_session_service_mgr:
217231
raise HTTPException(
218-
status_code=404, detail=f"WebSocket client {websocket_id} not found"
232+
status_code=404,
233+
detail=f"WebSocket client {websocket_id} not found",
219234
)
220235
return self.ws_session_service_mgr[websocket_id]
221236

@@ -276,7 +291,9 @@ async def create_session_with_id(
276291
return session
277292

278293
@self.app.post("/run_sse")
279-
async def run_agent_sse(req: RunAgentRequestWithWsId) -> StreamingResponse:
294+
async def run_agent_sse(
295+
req: RunAgentRequestWithWsId,
296+
) -> StreamingResponse:
280297
"""Run agent with SSE streaming."""
281298
session_service = _get_session_service(req.websocket_id)
282299

@@ -337,7 +354,10 @@ async def event_generator():
337354
content_event.actions.artifact_delta = {}
338355
artifact_event = event.model_copy(deep=True)
339356
artifact_event.content = None
340-
events_to_stream = [content_event, artifact_event]
357+
events_to_stream = [
358+
content_event,
359+
artifact_event,
360+
]
341361

342362
for event_to_stream in events_to_stream:
343363
sse_event = event_to_stream.model_dump_json(
@@ -354,6 +374,14 @@ async def event_generator():
354374
media_type="text/event-stream",
355375
)
356376

377+
if self.extra_routes:
378+
for route in self.extra_routes:
379+
self.app.add_api_route(
380+
path=route.path,
381+
endpoint=route.endpoint,
382+
methods=route.methods,
383+
)
384+
357385
# build the fake MPC server,
358386
# and intercept all requests to the client websocket client.
359387
# NOTE: This catch-all route must be defined LAST

veadk/tools/skills_tools/skills_toolset.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
bash_tool,
3535
register_skills_tool,
3636
)
37+
from veadk.skills.utils import update_check_list
3738
from veadk.utils.logger import get_logger
3839

3940
logger = get_logger(__name__)
@@ -73,6 +74,7 @@ def __init__(self, skills: Dict[str, Skill], skills_mode: str) -> None:
7374
"edit_file": FunctionTool(edit_file_tool),
7475
"bash": FunctionTool(bash_tool),
7576
"register_skills": FunctionTool(register_skills_tool),
77+
"update_check_list": FunctionTool(update_check_list),
7678
}
7779

7880
@override

0 commit comments

Comments
 (0)