diff --git a/.env.template b/.env.template index 6edd2a9b..597b3372 100644 --- a/.env.template +++ b/.env.template @@ -25,4 +25,4 @@ RESEND_API_KEY= ANTHROPIC_API_KEY= POSTHOG_API_KEY= POSTHOG_HOST= -FIRECRAWL_API_KEY= \ No newline at end of file +FIRECRAWL_API_KEY= diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 982ee08c..0608d474 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -4,10 +4,36 @@ on: pull_request: types: [opened, synchronize] +permissions: + contents: write # Grants write access to push changes + jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full commit history + token: ${{ secrets.GITHUB_TOKEN }} # Allows pushing changes + + - uses: actions/setup-python@v4 + with: + python-version: "3.11" # Set a consistent Python version + + - name: Install dependencies + run: pip install --upgrade pip pre-commit # Ensure latest pre-commit version + + - name: Run pre-commit + run: pre-commit run --all-files --show-diff-on-failure || true # Run all hooks without failing + + - name: Check for changes and commit + run: | + if [[ `git status --porcelain` ]]; then + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: Auto-fix pre-commit issues" + git push origin HEAD:${{ github.head_ref }} + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Ensures authentication diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19c116d1..b27eb1d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,18 +4,12 @@ repos: hooks: - id: check-yaml - id: trailing-whitespace - - id: check-docstring-first + - id: end-of-file-fixer - id: check-merge-conflict - id: check-added-large-files args: ["--maxkb=51200"] - id: debug-statements - - repo: https://github.com/timothycrosley/isort - rev: 5.13.2 - hooks: - - id: isort - args: ["--profile", "black", "--filter-files"] - - repo: https://github.com/psf/black rev: 24.8.0 hooks: @@ -25,18 +19,9 @@ repos: rev: v0.6.2 hooks: - id: ruff - args: [ --fix ] - - id: ruff-format - - - repo: https://github.com/PyCQA/pylint - rev: v3.2.6 - hooks: - - id: pylint - entry: bash -c 'pylint "$@" || true' -- - verbose: true + args: ["--fix"] - repo: https://github.com/PyCQA/bandit rev: 1.7.9 hooks: - id: bandit - entry: bash -c 'bandit "$@" || true' -- \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d354774f..8e3bc120 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -74,4 +74,4 @@ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.ht [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq \ No newline at end of file +https://www.contributor-covenant.org/faq diff --git a/LICENSE b/LICENSE index a57d511a..533c06f9 100644 --- a/LICENSE +++ b/LICENSE @@ -189,4 +189,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index de229bcf..323b8fe0 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,7 @@ Potpie is designed to be flexible and customizable. Here are key areas to person ### **Effortless Agent Creation**: Design custom agents tailored to your specific tasks using a single prompt. Utilize the following API to create your custom agents: - + ```bash curl -X POST "http://localhost:8001/api/v1/custom-agents/agents/auto" \ -H "Content-Type: application/json" \ @@ -280,7 +280,7 @@ Design custom agents tailored to your specific tasks using a single prompt. Util ### Tool Integration Edit or add tools in the `app/modules/intelligence/tools` directory for your custom agents. -Initialise the tools in the `app/modules/intelligence/tools/tool_service.py` file and include them in your agent. +Initialise the tools in the `app/modules/intelligence/tools/tool_service.py` file and include them in your agent. ## 🤝 Contributing diff --git a/app/alembic/README b/app/alembic/README index 98e4f9c4..2500aa1b 100644 --- a/app/alembic/README +++ b/app/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/app/alembic/versions/20241020111943_262d870e9686_custom_agents.py b/app/alembic/versions/20241020111943_262d870e9686_custom_agents.py index 7fc49248..e5d4e162 100644 --- a/app/alembic/versions/20241020111943_262d870e9686_custom_agents.py +++ b/app/alembic/versions/20241020111943_262d870e9686_custom_agents.py @@ -1,49 +1,54 @@ """custom_agents Revision ID: 20241020111943_262d870e9686 -Revises: +Revises: Create Date: 2024-10-20 11:19:43.653649 """ + from typing import Sequence, Union -from alembic import op import sqlalchemy as sa +from alembic import op from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. -revision: str = '20241020111943_262d870e9686' +revision: str = "20241020111943_262d870e9686" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None # Add this line -branch_labels = ('custom_agents_microservice',) +branch_labels = ("custom_agents_microservice",) + def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('custom_agents', - sa.Column('id', sa.String(), nullable=False), - sa.Column('user_id', sa.String(), nullable=True), - sa.Column('role', sa.String(), nullable=True), - sa.Column('goal', sa.String(), nullable=True), - sa.Column('backstory', sa.String(), nullable=True), - sa.Column('system_prompt', sa.String(), nullable=True), - sa.Column('tasks', postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column('deployment_url', sa.String(), nullable=True), - sa.Column('deployment_status', sa.String(), nullable=False), - sa.Column('created_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "custom_agents", + sa.Column("id", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=True), + sa.Column("role", sa.String(), nullable=True), + sa.Column("goal", sa.String(), nullable=True), + sa.Column("backstory", sa.String(), nullable=True), + sa.Column("system_prompt", sa.String(), nullable=True), + sa.Column("tasks", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("deployment_url", sa.String(), nullable=True), + sa.Column("deployment_status", sa.String(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_custom_agents_id"), "custom_agents", ["id"], unique=False) + op.create_index( + op.f("ix_custom_agents_user_id"), "custom_agents", ["user_id"], unique=False ) - op.create_index(op.f('ix_custom_agents_id'), 'custom_agents', ['id'], unique=False) - op.create_index(op.f('ix_custom_agents_user_id'), 'custom_agents', ['user_id'], unique=False) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_custom_agents_user_id'), table_name='custom_agents') - op.drop_index(op.f('ix_custom_agents_id'), table_name='custom_agents') - op.drop_table('custom_agents') + op.drop_index(op.f("ix_custom_agents_user_id"), table_name="custom_agents") + op.drop_index(op.f("ix_custom_agents_id"), table_name="custom_agents") + op.drop_table("custom_agents") # ### end Alembic commands ### diff --git a/app/core/models.py b/app/core/models.py index 2eb89ce0..c9dd8aa7 100644 --- a/app/core/models.py +++ b/app/core/models.py @@ -2,6 +2,9 @@ Conversation, ) from app.modules.conversations.message.message_model import Message # noqa +from app.modules.intelligence.agents.custom_agents.custom_agent_model import ( # noqa + CustomAgent, +) from app.modules.intelligence.prompts.prompt_model import ( # noqa AgentPromptMapping, Prompt, @@ -11,4 +14,3 @@ from app.modules.tasks.task_model import Task # noqa from app.modules.users.user_model import User # noqa from app.modules.users.user_preferences_model import UserPreferences # noqa -from app.modules.intelligence.agents.custom_agents.custom_agent_model import CustomAgent # noqa diff --git a/app/modules/code_provider/local_repo/local_repo_service.py b/app/modules/code_provider/local_repo/local_repo_service.py index 9dd7fab1..74810daf 100644 --- a/app/modules/code_provider/local_repo/local_repo_service.py +++ b/app/modules/code_provider/local_repo/local_repo_service.py @@ -160,22 +160,22 @@ async def _fetch_repo_structure_async( # Filter out files with excluded extensions contents = [ - item - for item in contents - if item['type'] == "dir" - or not any(item['name'].endswith(ext) for ext in exclude_extensions) - ] + item + for item in contents + if item["type"] == "dir" + or not any(item["name"].endswith(ext) for ext in exclude_extensions) + ] tasks = [] for item in contents: # Only process items within the base_path if it's specified - if base_path and not item['path'].startswith(base_path): + if base_path and not item["path"].startswith(base_path): continue - if item['type'] == "dir": + if item["type"] == "dir": task = self._fetch_repo_structure_async( repo, - item['path'], + item["path"], current_depth=current_depth, base_path=base_path, ) @@ -184,8 +184,8 @@ async def _fetch_repo_structure_async( structure["children"].append( { "type": "file", - "name": item['name'], - "path": item['path'], + "name": item["name"], + "path": item["path"], } ) @@ -275,7 +275,7 @@ def _get_contents(self, path: str) -> Union[List[dict], dict]: """ If the path is a directory, it returns a list of dictionaries, each representing a file or subdirectory. If the path is a file, its content is read and returned. - + :param path: Relative or absolute path within the local repository. :return: A dict if the path is a file (with file content loaded), or a list of dicts if the path is a directory. """ @@ -284,42 +284,48 @@ def _get_contents(self, path: str) -> Union[List[dict], dict]: if path == "/": path = "" - + abs_path = os.path.abspath(path) - + if not os.path.exists(abs_path): raise FileNotFoundError(f"Path '{abs_path}' does not exist.") - + if os.path.isdir(abs_path): contents = [] for item in os.listdir(abs_path): item_path = os.path.join(abs_path, item) if os.path.isdir(item_path): - contents.append({ - "path": item_path, - "name": item, - "type": "dir", - "content": None, #path is a dir, content is not loaded - "completed": True - }) + contents.append( + { + "path": item_path, + "name": item, + "type": "dir", + "content": None, # path is a dir, content is not loaded + "completed": True, + } + ) elif os.path.isfile(item_path): - contents.append({ - "path": item_path, - "name": item, - "type": "file", - "content": None, - "completed": False - }) + contents.append( + { + "path": item_path, + "name": item, + "type": "file", + "content": None, + "completed": False, + } + ) else: - contents.append({ - "path": item_path, - "name": item, - "type": "other", - "content": None, - "completed": True - }) + contents.append( + { + "path": item_path, + "name": item, + "type": "other", + "content": None, + "completed": True, + } + ) return contents - + elif os.path.isfile(abs_path): with open(abs_path, "r", encoding="utf-8") as file: file_content = file.read() @@ -327,8 +333,6 @@ def _get_contents(self, path: str) -> Union[List[dict], dict]: "path": abs_path, "name": os.path.basename(abs_path), "type": "file", - "content": file_content, #path is a file, content is loaded - "completed": True + "content": file_content, # path is a file, content is loaded + "completed": True, } - - \ No newline at end of file diff --git a/app/modules/conversations/conversation/conversation_service.py b/app/modules/conversations/conversation/conversation_service.py index c6c68443..9867dcd5 100644 --- a/app/modules/conversations/conversation/conversation_service.py +++ b/app/modules/conversations/conversation/conversation_service.py @@ -223,8 +223,8 @@ async def agent_node(self, state: State, writer: StreamWriter): try: system_agents = [ - agent.id for agent in self.available_agents if agent.status == "SYSTEM" - ] + agent.id for agent in self.available_agents if agent.status == "SYSTEM" + ] if state["agent_id"] in system_agents: async for chunk in self.agent.run( query=state["query"], @@ -234,7 +234,7 @@ async def agent_node(self, state: State, writer: StreamWriter): node_ids=state["node_ids"], ): if isinstance(chunk, str): - writer(chunk) + writer(chunk) else: async for chunk in await self.agent.run( query=state["query"], diff --git a/app/modules/intelligence/agents/agents_service.py b/app/modules/intelligence/agents/agents_service.py index 4df1a82e..21c3fe83 100644 --- a/app/modules/intelligence/agents/agents_service.py +++ b/app/modules/intelligence/agents/agents_service.py @@ -10,6 +10,7 @@ logger = setup_logger(__name__) + class AgentsService: def __init__(self, db): self.project_path = os.getenv("PROJECT_PATH", "projects/") @@ -69,7 +70,9 @@ async def list_available_agents( current_user["user_id"] ) except Exception as e: - logger.error(f"Failed to fetch custom agents for user {current_user['user_id']}: {e}") + logger.error( + f"Failed to fetch custom agents for user {current_user['user_id']}: {e}" + ) custom_agents = [] agent_info_list = [ AgentInfo( diff --git a/app/modules/intelligence/agents/custom_agents/custom_agent_controller.py b/app/modules/intelligence/agents/custom_agents/custom_agent_controller.py index faf3ee65..bda63f53 100644 --- a/app/modules/intelligence/agents/custom_agents/custom_agent_controller.py +++ b/app/modules/intelligence/agents/custom_agents/custom_agent_controller.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any, Dict from fastapi import Depends, HTTPException, status from sqlalchemy.orm import Session @@ -91,7 +91,6 @@ async def get_agent(self, agent_id: str, user_id: str) -> Agent: detail=f"Failed to fetch custom agent: {str(e)}", ) - async def create_agent_from_prompt( self, prompt: str, diff --git a/app/modules/intelligence/prompts/classification_prompts.py b/app/modules/intelligence/prompts/classification_prompts.py index 8f0c11bc..2a2ae46f 100644 --- a/app/modules/intelligence/prompts/classification_prompts.py +++ b/app/modules/intelligence/prompts/classification_prompts.py @@ -487,7 +487,9 @@ class ClassificationPrompts: """, } - REDUNDANT_INHIBITION_TAIL: str = "\n\nReturn ONLY JSON content, and nothing else. Don't provide reason or any other text in the response." + REDUNDANT_INHIBITION_TAIL: str = ( + "\n\nReturn ONLY JSON content, and nothing else. Don't provide reason or any other text in the response." + ) @classmethod def get_classification_prompt(cls, agent_type: AgentType) -> str: diff --git a/dockerfile b/dockerfile index a0d070a5..f559c37b 100644 --- a/dockerfile +++ b/dockerfile @@ -40,4 +40,4 @@ EXPOSE 8001 ENV PYTHONUNBUFFERED=1 # Run Supervisor when the container launches -CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"] \ No newline at end of file +CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/docs/parsing.md b/docs/parsing.md index 99c241ff..f76768fe 100644 --- a/docs/parsing.md +++ b/docs/parsing.md @@ -83,4 +83,4 @@ ## Additional Notes - Ensure that the environment variable `isDevelopmentMode` is set to "enabled" to parse local repositories. -- The `user_id` must not match the `defaultUsername` environment variable when parsing remote repositories. \ No newline at end of file +- The `user_id` must not match the `defaultUsername` environment variable when parsing remote repositories. diff --git a/isort.cfg b/isort.cfg deleted file mode 100644 index cd954b6c..00000000 --- a/isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -profile = black -line_length = 88 \ No newline at end of file diff --git a/projects/blank.md b/projects/blank.md index e753c679..07aee6df 100644 --- a/projects/blank.md +++ b/projects/blank.md @@ -1 +1 @@ -# leave this file empty, this ensures project directory is present \ No newline at end of file +# leave this file empty, this ensures project directory is present diff --git a/requirements.txt b/requirements.txt index 2957773c..0d474af9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -65,4 +65,4 @@ newrelic==9.0.0 tiktoken==0.7.0 agentops==0.3.26 pydantic[email]==2.10.3 -firecrawl-py==1.11.1 \ No newline at end of file +firecrawl-py==1.11.1 diff --git a/start.sh b/start.sh index aa45f3b2..3dd8bb73 100755 --- a/start.sh +++ b/start.sh @@ -47,4 +47,4 @@ gunicorn --worker-class uvicorn.workers.UvicornWorker --workers 1 --timeout 1800 echo "Starting Celery worker" # Start Celery worker with the new setup -celery -A app.celery.celery_app worker --loglevel=debug -Q "${CELERY_QUEUE_NAME}_process_repository" -E --concurrency=1 --pool=solo & \ No newline at end of file +celery -A app.celery.celery_app worker --loglevel=debug -Q "${CELERY_QUEUE_NAME}_process_repository" -E --concurrency=1 --pool=solo &