Skip to content

Commit

Permalink
Enable creating custom skills (#53)
Browse files Browse the repository at this point in the history
* Make it easier and show how to add custom skills

* Add 'managed' col in skills table

* Update skills model, add migration file, update skills service to support new cols in skills table

* Update init db to handle updating or deletion of managed skills

* Update SkillsOut and SkillOut model

* Add routes to create, update and delete skills. Update read_skills route to show managed skills aside from user owned skills

* Add skills page, update sidebar and navbar to incorporate skills page.

* Fix navigate route to not use backticks. Format code.

* - Add GraphSkill pydantic model used by GraphMember to store skill info and provide its BaseTool instance.
- Add dynamic_api_tool to create api tool from tool definitions.
- Rename all_skills to managed_skills.

* Fix query to invalidate after skill is edited

* Update DeleteAlert component to handle skill deletion

* - Set skill description and definition as required when creating or editing skill.
- Set default for skill definition when creating skill.
- Rename 'Pre-fill' button.

* Order skills, teams in descending id. Order threads in descending order of updated at.

* Set a maxWidth for name and description cols for skills and teams table

* Refactor code

* Fix lint issues

* Update readme to include info about creating skills

* Update readme to include boolean as possible param type

* Fix validation error in api tool when optional field is not included in params.

* Make mypy verbose

* Disable mypy incremental mode

* Use python 3.12 for github test action

* Checking memory usage in github actions

* Remove memory usage check in test.yml. Track memory usage in linting stage

* Try running mypy on a small portion of code

* Try using swap space

* Show traceback when running mypy

* Track if docker containers are up

* Revert linting stage to original and remove swap space from github test action.

* Fix migration script to add superuser if it doesnt exist

* Change coverage threshold to 70

* Update skills test

* Fix lint issues

* Fix skill tests

* Fix unauthorised error in skill test

* Fix read_skill route not using user authorisation

* Fix skill test

* Update dynamic_api_tool to validate tool_definition

* Add tests for dynamic_api_tool

* Fix lint errors

* Removed unused import

* Add endpoint to validate skill's tool_definition

* Validate tool_definition before adding or updating skill, if invalid restrict from submitting

* Fix update form to reset isDirty check after submitting

* validate tool_definition in create_skill and update_skill too

* Fix validate_tool_definition should return tool definition

* Update skill tests

* Fix skill tests

* fix skill test
  • Loading branch information
StreetLamb authored Jun 18, 2024
1 parent 92bc29a commit 9b1c73f
Show file tree
Hide file tree
Showing 48 changed files with 2,292 additions and 222 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/smokeshow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- run: smokeshow upload backend/htmlcov
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 70
SMOKESHOW_GITHUB_CONTEXT: coverage
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
Expand Down
20 changes: 17 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:
- synchronize

jobs:

test:
runs-on: ubuntu-latest
steps:
Expand All @@ -20,12 +19,27 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: "3.10"

- run: cp .env.example .env
- run: docker compose build
- run: docker compose down -v --remove-orphans
- run: docker compose up -d

- name: Wait for Docker Containers to be Ready
run: |
echo "Waiting for Docker containers to be ready..."
sleep 10
docker ps -a
- name: Ensure Docker Container is Running
run: |
if [ $(docker inspect -f '{{.State.Running}}' $(docker-compose ps -q backend)) != "true" ]; then
echo "Backend container is not running"
docker logs $(docker-compose ps -q backend)
exit 1
fi
- name: Lint
run: docker compose exec -T backend bash /app/scripts/lint.sh
- name: Run tests
Expand All @@ -38,7 +52,7 @@ jobs:
path: backend/htmlcov

# https://github.com/marketplace/actions/alls-green#why
alls-green: # This job does nothing and is only used for the branch protection
alls-green: # This job does nothing and is only used for the branch protection
if: always()
needs:
- test
Expand Down
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
- [Sequential vs Hierarchical workflows](#sequential-vs-hierarchical-workflows)
- [Sequential workflows](#sequential-workflows)
- [Hierarchical workflows](#hierarchical-workflows)
- [Skills](#skills)
- [Create a Skill Using Skill Definitions](#create-a-skill-using-skill-definitions)
- [Writing a Custom Skill using LangChain](#writing-a-custom-skill-using-langchain)
- [Guides](#guides)
- [Creating Your First Hierarchical Team](#creating-your-first-hierarchical-team)
- [Equipping Your Team Member with Skills](#equipping-your-team-member-with-skills)
Expand Down Expand Up @@ -107,6 +110,69 @@ Use this if:
- Task delegation and re-evaluation are crucial for your workflow.
- You want flexibility in task management and adaptability to changes.

### Skills

Skills are abilities that you can equip your agents with to interact with the world. For example, you can provide your agent with the skill to check the current weather condition or search the web for the latest news. By default, Tribe provides three skills:

- **duckduckgo-search**: Performs web searches.
- **wikipedia**: Searches Wikipedia for information.
- **yahoo-finance**: Retrieves information from Yahoo Finance News.

You will likely want to create custom skills, which can be done in two ways: by using function definitions for simple HTTP requests or by writing custom skills in the codebase.

#### Create a Skill Using Skill Definitions

If your skill involves performing an HTTP request to fetch or update data, using skill definitions is the simplest approach. In Tribe, start by navigating to the 'Skills' tab and clicking the 'Add Skill' button. You will then be prompted to provide the skill definition, which instructs your agent on how to execute the specific skill. This definition should be structured as follows:

```json
{
"url": "https://example.com",
"method": "GET",
"headers": {},
"type": "function",
"function": {
"name": "Your skill name",
"description": "Your skill description",
"parameters": {
"type": "object",
"properties": {
"param1": {
"type": "integer",
"description": "Description of the first parameter"
},
"param2": {
"type": "string",
"enum": ["option1"],
"description": "Description of the second parameter"
}
},
"required": ["param1", "param2"]
}
}
}
```

| Key | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url` | The endpoint URL for the API call. |
| `method` | The HTTP method used for the request. It can be `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`. |
| `headers` | Any HTTP headers to include in the request. |
| `function` | Contains details about the skill: |
| `function > name` | The name of the skill. Follow these rules: only letters (a-z, A-Z), numbers (0-9), underscores (_), and hyphens (-) are allowed; must be between 1 and 64 characters long. |
| `function > description` | Describes the skill to inform the agent about its usage. |
| `function > parameters` | Details about the parameters the API accepts. |
| `properties > param` | The name of the query or body parameter. For `GET` methods, this will be a query parameter. For `POST`, `PUT`, `PATCH`, and `DELETE`, it will be in the request body. |
| `param > type` | Specifies the type of the parameter, which can be `string`, `number`, `integer`, or `boolean`. |
| `param > description` | Provides context about the parameter's purpose. |
| `param > enum` | Optionally, include an array to restrict the agent to select from predefined values. |
| `parameters > required` | Lists the parameters that are required, ensuring they are always included in the API request. |

#### Writing a Custom Skill using LangChain

For more intricate tasks that extend beyond simple HTTP requests, LangChain allows you to develop more advanced tools. You can integrate these tools into Tribe by adding them to the [`managed_skills` dictionary](https://github.com/streetlamb/tribe/blob/master/backend/app/core/graph/skills/__init__.py). For a practical example, refer to the [demo calculator tool](https://github.com/streetlamb/tribe/blob/master/backend/app/core/graph/skills/calculator.py). To learn how to create a LangChain tool, please consult their [documentation](https://python.langchain.com/v0.2/docs/how_to/custom_tools/).

After creating a new tool, restart the application to ensure the tool is properly loaded into the database. Likewise, if you need to remove a tool, simply delete it from the `managed_skills` dictionary and restart the application to ensure it is removed from the database. Do note that tools created this way are available to all users in your application.

### Guides

#### Creating Your First Hierarchical Team
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Add managed col in skills table
Revision ID: a8fff9df0a02
Revises: 6fa42be09dd2
Create Date: 2024-06-13 12:17:08.622973
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'a8fff9df0a02'
down_revision = '6fa42be09dd2'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('skill', sa.Column('managed', sa.Boolean(), nullable=False, server_default=sa.sql.expression.true()))
# ### end Alembic commands ###

# Remove server default after setting the initial values
op.alter_column('skill', 'managed', server_default=None)


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('skill', 'managed')
# ### end Alembic commands ###
73 changes: 73 additions & 0 deletions backend/app/alembic/versions/c1acf65d4731_update_skills_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Update skills table
Revision ID: c1acf65d4731
Revises: a8fff9df0a02
Create Date: 2024-06-13 16:09:19.067502
"""
from alembic import op
from app.core.security import get_password_hash
import sqlalchemy as sa
from sqlalchemy.sql import table, column, select
from sqlalchemy import String, Integer, insert

# revision identifiers, used by Alembic.
revision = 'c1acf65d4731'
down_revision = 'a8fff9df0a02'
branch_labels = None
depends_on = None

def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
# Add tool_definition column
op.add_column('skill', sa.Column('tool_definition', sa.JSON(), nullable=True))

# Import the settings to get the FIRST_SUPERUSER email
from app.core.config import settings

# Use raw SQL to find the user ID for the superuser
connection = op.get_bind()
user_table = table('user',
column('id', Integer),
column('email', String),
column('hashed_password', String),
column('is_superuser', sa.Boolean),
column('is_active', sa.Boolean))
superuser_email = settings.FIRST_SUPERUSER

# Correct the query to properly select the user ID
superuser_id = connection.execute(
select(user_table.c.id).where(user_table.c.email == superuser_email)
).scalar()

if superuser_id is None:
# Insert the superuser if it does not exist
connection.execute(
insert(user_table).values(
email=superuser_email,
hashed_password=get_password_hash(settings.FIRST_SUPERUSER_PASSWORD),
is_superuser=True,
is_active=True
)
)
# Fetch the superuser ID after insertion
superuser_id = connection.execute(
select(user_table.c.id).where(user_table.c.email == superuser_email)
).scalar()

# Add owner_id column with the superuser ID as default for existing rows
op.add_column('skill', sa.Column('owner_id', sa.Integer(), nullable=False, server_default=str(superuser_id)))
op.alter_column('skill', 'description', existing_type=sa.VARCHAR(), nullable=False)
op.create_foreign_key(None, 'skill', 'user', ['owner_id'], ['id'])

# Remove the server default after setting the initial values
op.alter_column('skill', 'owner_id', server_default=None)
# ### end Alembic commands ###

def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'skill', type_='foreignkey')
op.alter_column('skill', 'description', existing_type=sa.VARCHAR(), nullable=True)
op.drop_column('skill', 'owner_id')
op.drop_column('skill', 'tool_definition')
# ### end Alembic commands ###
Loading

0 comments on commit 9b1c73f

Please sign in to comment.