Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "surfkit"
version = "0.1.293"
version = "0.1.294"
description = "A toolkit for building AI agents that use devices"
authors = ["Patrick Barker <patrickbarkerco@gmail.com>", "Jeffrey Huckabay <jfhucka@gmail.com>"]
license = "MIT"
Expand All @@ -20,7 +20,7 @@ litellm = "^1.35.8"
rich = "^13.7.1"
tqdm = "^4.66.4"
agentdesk = "^0.2.107"
taskara = "^0.1.187"
taskara = "^0.1.189"


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


[tool.pyright]
reportUnknownParameterType = false
reportMissingTypeArgument = false
reportUnknownMemberType = false
reportUnknownVariableType = false
reportUnknownArgumentType = false


[tool.isort]
line_length = 88
profile = "black"
13 changes: 13 additions & 0 deletions surfkit/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from pydantic import BaseModel
from taskara import Task

from .skill import Skill

C = TypeVar("C", bound="BaseModel")
T = TypeVar("T", bound="TaskAgent")

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

def learn_skill(
self,
skill: Skill,
):
"""Learn a skill

Args:
skill (Skill): The skill
"""
raise NotImplementedError("Subclasses must implement this method")

@abstractmethod
def solve_task(
self,
Expand Down
19 changes: 19 additions & 0 deletions surfkit/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@
Base = declarative_base()


class SkillRecord(Base):
__tablename__ = "skills"

id = Column(String, primary_key=True)
owner_id = Column(String, nullable=False)
name = Column(String, nullable=False)
status = Column(String, nullable=False)
description = Column(String, nullable=False)
requirements = Column(String, nullable=True)
agent_type = Column(String, nullable=False)
threads = Column(String, nullable=True)
generating_tasks = Column(Boolean, nullable=False)
tasks = Column(String, nullable=True)
min_demos = Column(Integer, nullable=False)
demos_outstanding = Column(Integer, nullable=False)
created = Column(Float, default=time.time)
updated = Column(Float, default=time.time)


class AgentTypeRecord(Base):
__tablename__ = "agent_types"

Expand Down
21 changes: 20 additions & 1 deletion surfkit/runtime/agent/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
V1AgentInstance,
V1AgentType,
V1RuntimeConnect,
V1Skill,
V1SolveTask,
)
from surfkit.types import AgentType
Expand Down Expand Up @@ -334,7 +335,6 @@ def refresh(self) -> None:


class AgentRuntime(Generic[R, C], ABC):

@classmethod
def name(cls) -> str:
return cls.__name__
Expand Down Expand Up @@ -404,6 +404,25 @@ def run(
"""
pass

@abstractmethod
def learn_skill(
self,
name: str,
skill: V1Skill,
follow_logs: bool = False,
attach: bool = False,
) -> None:
"""Learn a skill

Args:
name (str): Name of the agent
skill (V1Skill): The skill
follow_logs (bool, optional): Whether to follow the logs. Defaults to False.
attach (bool, optional): Whether to attach the current process to the agent
If this process dies the agent will also die. Defaults to False.
"""
pass

@abstractmethod
def solve_task(
self,
Expand Down
10 changes: 10 additions & 0 deletions surfkit/runtime/agent/kube.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
V1AgentType,
V1ResourceLimits,
V1ResourceRequests,
V1Skill,
V1SolveTask,
)
from surfkit.types import AgentType
Expand Down Expand Up @@ -841,6 +842,15 @@ def run(
# }
# return headers

def learn_skill(
self,
name: str,
skill: V1Skill,
follow_logs: bool = False,
attach: bool = False,
) -> None:
pass

def solve_task(
self,
name: str,
Expand Down
50 changes: 49 additions & 1 deletion surfkit/server/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Any, Dict, List, Optional

from pydantic import BaseModel
from pydantic import BaseModel, Field
from taskara import V1Task
from threadmem import V1RoleThread


class V1Action(BaseModel):
Expand Down Expand Up @@ -175,3 +176,50 @@ class V1Meta(BaseModel):
owner_id: Optional[str] = None
created: float
updated: float


class UserTasks(BaseModel):
"""A list of tasks for a user story"""

tasks: List[str] = Field(description="A list of tasks for a user story")


class UserTask(BaseModel):
"""A task for a user story"""

task: str = Field(description="A task for a user story")


class V1Skill(BaseModel):
id: str
name: str
description: str
requirements: List[str]
tasks: List[V1Task]
threads: List[V1RoleThread] = []
status: Optional[str] = None
min_demos: Optional[int] = None
demos_outstanding: Optional[int] = None
owner_id: Optional[str] = None
generating_tasks: Optional[bool] = None
agent_type: str
remote: Optional[str] = None
created: int
updated: int


class V1UpdateSkill(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
requirements: Optional[List[str]] = None
tasks: Optional[List[str]] = None
threads: Optional[List[str]] = None
status: Optional[str] = None
min_demos: Optional[int] = None
demos_outstanding: Optional[int] = None


class V1LearnSkill(BaseModel):
skill_id: str
remote: Optional[str] = None
agent: Optional[V1Agent] = None
40 changes: 36 additions & 4 deletions surfkit/server/routes.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import logging
import os
import time
from typing import Annotated, Type
from typing import Annotated, Optional, Type

from agentdesk import ConnectConfig
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from fastapi import APIRouter, BackgroundTasks, Depends
from taskara import Task, TaskStatus
from taskara.server.models import V1Task, V1Tasks, V1TaskUpdate
from tenacity import retry, stop_after_attempt, wait_fixed

from surfkit.agent import TaskAgent
from surfkit.auth.transport import get_user_dependency
from surfkit.env import AGENTESEA_HUB_API_KEY_ENV
from surfkit.server.models import V1SolveTask, V1UserProfile
from surfkit.server.models import V1Agent, V1LearnSkill, V1SolveTask, V1UserProfile
from surfkit.skill import Skill

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

@api_router.post("/v1/learn")
async def learn_skill(
current_user: Annotated[V1UserProfile, Depends(get_user_dependency())],
background_tasks: BackgroundTasks,
skill_model: V1LearnSkill,
):
logger.info(
f"learning skill: {skill_model.model_dump()} with user {current_user.email}"
)

found = Skill.find(remote=skill_model.remote, id=skill_model.skill_id)
if not found:
raise Exception(f"Skill {skill_model.skill_id} not found")

skill = found[0]

background_tasks.add_task(_learn_skill, skill, current_user, skill_model.agent)

def _learn_skill(
skill: Skill, current_user: V1UserProfile, v1_agent: Optional[V1Agent] = None
):
if v1_agent:
config = Agent.config_type().model_validate(v1_agent.config)
agent = Agent.from_config(config=config)
else:
agent = Agent.default()

try:
agent.learn_skill(skill)
except Exception as e:
logger.error(f"error learning skill: {e}")

@api_router.post("/v1/tasks")
async def solve_task(
current_user: Annotated[V1UserProfile, Depends(get_user_dependency())],
Expand Down
Loading
Loading