Skip to content

Commit 6a1ad5c

Browse files
authored
Merge pull request #124 from agentsea/skills
Add skills
2 parents fbb1b89 + 38d7eb1 commit 6a1ad5c

File tree

9 files changed

+593
-16
lines changed

9 files changed

+593
-16
lines changed

poetry.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "surfkit"
3-
version = "0.1.293"
3+
version = "0.1.294"
44
description = "A toolkit for building AI agents that use devices"
55
authors = ["Patrick Barker <[email protected]>", "Jeffrey Huckabay <[email protected]>"]
66
license = "MIT"
@@ -20,7 +20,7 @@ litellm = "^1.35.8"
2020
rich = "^13.7.1"
2121
tqdm = "^4.66.4"
2222
agentdesk = "^0.2.107"
23-
taskara = "^0.1.187"
23+
taskara = "^0.1.189"
2424

2525

2626
[tool.poetry.group.dev.dependencies]
@@ -40,6 +40,14 @@ surfkit = "surfkit.cli.main:app"
4040
lint = "scripts.lint:main"
4141

4242

43+
[tool.pyright]
44+
reportUnknownParameterType = false
45+
reportMissingTypeArgument = false
46+
reportUnknownMemberType = false
47+
reportUnknownVariableType = false
48+
reportUnknownArgumentType = false
49+
50+
4351
[tool.isort]
4452
line_length = 88
4553
profile = "black"

surfkit/agent.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from pydantic import BaseModel
66
from taskara import Task
77

8+
from .skill import Skill
9+
810
C = TypeVar("C", bound="BaseModel")
911
T = TypeVar("T", bound="TaskAgent")
1012

@@ -16,6 +18,17 @@ class TaskAgent(Generic[C, T], ABC):
1618
def name(cls) -> str:
1719
return cls.__name__
1820

21+
def learn_skill(
22+
self,
23+
skill: Skill,
24+
):
25+
"""Learn a skill
26+
27+
Args:
28+
skill (Skill): The skill
29+
"""
30+
raise NotImplementedError("Subclasses must implement this method")
31+
1932
@abstractmethod
2033
def solve_task(
2134
self,

surfkit/db/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,25 @@
77
Base = declarative_base()
88

99

10+
class SkillRecord(Base):
11+
__tablename__ = "skills"
12+
13+
id = Column(String, primary_key=True)
14+
owner_id = Column(String, nullable=False)
15+
name = Column(String, nullable=False)
16+
status = Column(String, nullable=False)
17+
description = Column(String, nullable=False)
18+
requirements = Column(String, nullable=True)
19+
agent_type = Column(String, nullable=False)
20+
threads = Column(String, nullable=True)
21+
generating_tasks = Column(Boolean, nullable=False)
22+
tasks = Column(String, nullable=True)
23+
min_demos = Column(Integer, nullable=False)
24+
demos_outstanding = Column(Integer, nullable=False)
25+
created = Column(Float, default=time.time)
26+
updated = Column(Float, default=time.time)
27+
28+
1029
class AgentTypeRecord(Base):
1130
__tablename__ = "agent_types"
1231

surfkit/runtime/agent/base.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
V1AgentInstance,
1515
V1AgentType,
1616
V1RuntimeConnect,
17+
V1Skill,
1718
V1SolveTask,
1819
)
1920
from surfkit.types import AgentType
@@ -334,7 +335,6 @@ def refresh(self) -> None:
334335

335336

336337
class AgentRuntime(Generic[R, C], ABC):
337-
338338
@classmethod
339339
def name(cls) -> str:
340340
return cls.__name__
@@ -404,6 +404,25 @@ def run(
404404
"""
405405
pass
406406

407+
@abstractmethod
408+
def learn_skill(
409+
self,
410+
name: str,
411+
skill: V1Skill,
412+
follow_logs: bool = False,
413+
attach: bool = False,
414+
) -> None:
415+
"""Learn a skill
416+
417+
Args:
418+
name (str): Name of the agent
419+
skill (V1Skill): The skill
420+
follow_logs (bool, optional): Whether to follow the logs. Defaults to False.
421+
attach (bool, optional): Whether to attach the current process to the agent
422+
If this process dies the agent will also die. Defaults to False.
423+
"""
424+
pass
425+
407426
@abstractmethod
408427
def solve_task(
409428
self,

surfkit/runtime/agent/kube.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
V1AgentType,
3131
V1ResourceLimits,
3232
V1ResourceRequests,
33+
V1Skill,
3334
V1SolveTask,
3435
)
3536
from surfkit.types import AgentType
@@ -841,6 +842,15 @@ def run(
841842
# }
842843
# return headers
843844

845+
def learn_skill(
846+
self,
847+
name: str,
848+
skill: V1Skill,
849+
follow_logs: bool = False,
850+
attach: bool = False,
851+
) -> None:
852+
pass
853+
844854
def solve_task(
845855
self,
846856
name: str,

surfkit/server/models.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import Any, Dict, List, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44
from taskara import V1Task
5+
from threadmem import V1RoleThread
56

67

78
class V1Action(BaseModel):
@@ -175,3 +176,50 @@ class V1Meta(BaseModel):
175176
owner_id: Optional[str] = None
176177
created: float
177178
updated: float
179+
180+
181+
class UserTasks(BaseModel):
182+
"""A list of tasks for a user story"""
183+
184+
tasks: List[str] = Field(description="A list of tasks for a user story")
185+
186+
187+
class UserTask(BaseModel):
188+
"""A task for a user story"""
189+
190+
task: str = Field(description="A task for a user story")
191+
192+
193+
class V1Skill(BaseModel):
194+
id: str
195+
name: str
196+
description: str
197+
requirements: List[str]
198+
tasks: List[V1Task]
199+
threads: List[V1RoleThread] = []
200+
status: Optional[str] = None
201+
min_demos: Optional[int] = None
202+
demos_outstanding: Optional[int] = None
203+
owner_id: Optional[str] = None
204+
generating_tasks: Optional[bool] = None
205+
agent_type: str
206+
remote: Optional[str] = None
207+
created: int
208+
updated: int
209+
210+
211+
class V1UpdateSkill(BaseModel):
212+
name: Optional[str] = None
213+
description: Optional[str] = None
214+
requirements: Optional[List[str]] = None
215+
tasks: Optional[List[str]] = None
216+
threads: Optional[List[str]] = None
217+
status: Optional[str] = None
218+
min_demos: Optional[int] = None
219+
demos_outstanding: Optional[int] = None
220+
221+
222+
class V1LearnSkill(BaseModel):
223+
skill_id: str
224+
remote: Optional[str] = None
225+
agent: Optional[V1Agent] = None

surfkit/server/routes.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import logging
22
import os
33
import time
4-
from typing import Annotated, Type
4+
from typing import Annotated, Optional, Type
55

6-
from agentdesk import ConnectConfig
7-
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
6+
from fastapi import APIRouter, BackgroundTasks, Depends
87
from taskara import Task, TaskStatus
98
from taskara.server.models import V1Task, V1Tasks, V1TaskUpdate
109
from tenacity import retry, stop_after_attempt, wait_fixed
1110

1211
from surfkit.agent import TaskAgent
1312
from surfkit.auth.transport import get_user_dependency
1413
from surfkit.env import AGENTESEA_HUB_API_KEY_ENV
15-
from surfkit.server.models import V1SolveTask, V1UserProfile
14+
from surfkit.server.models import V1Agent, V1LearnSkill, V1SolveTask, V1UserProfile
15+
from surfkit.skill import Skill
1616

1717
DEBUG_ENV_VAR = os.getenv("DEBUG", "false").lower() == "true"
1818
log_level = logging.DEBUG if DEBUG_ENV_VAR else logging.INFO
@@ -40,6 +40,38 @@ async def root():
4040
async def health():
4141
return {"status": "ok"}
4242

43+
@api_router.post("/v1/learn")
44+
async def learn_skill(
45+
current_user: Annotated[V1UserProfile, Depends(get_user_dependency())],
46+
background_tasks: BackgroundTasks,
47+
skill_model: V1LearnSkill,
48+
):
49+
logger.info(
50+
f"learning skill: {skill_model.model_dump()} with user {current_user.email}"
51+
)
52+
53+
found = Skill.find(remote=skill_model.remote, id=skill_model.skill_id)
54+
if not found:
55+
raise Exception(f"Skill {skill_model.skill_id} not found")
56+
57+
skill = found[0]
58+
59+
background_tasks.add_task(_learn_skill, skill, current_user, skill_model.agent)
60+
61+
def _learn_skill(
62+
skill: Skill, current_user: V1UserProfile, v1_agent: Optional[V1Agent] = None
63+
):
64+
if v1_agent:
65+
config = Agent.config_type().model_validate(v1_agent.config)
66+
agent = Agent.from_config(config=config)
67+
else:
68+
agent = Agent.default()
69+
70+
try:
71+
agent.learn_skill(skill)
72+
except Exception as e:
73+
logger.error(f"error learning skill: {e}")
74+
4375
@api_router.post("/v1/tasks")
4476
async def solve_task(
4577
current_user: Annotated[V1UserProfile, Depends(get_user_dependency())],

0 commit comments

Comments
 (0)