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
46 changes: 24 additions & 22 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,28 @@ jobs:
- name: Cache virtual environment
uses: actions/cache@v4
with:
path: .venv
key: ${{ runner.os }}-venv-${{ hashFiles('**/requirements.txt') }}
path: backend/.venv
key: ${{ runner.os }}-venv-${{ hashFiles('backend/requirements.txt') }}
restore-keys: |
${{ runner.os }}-venv-

- name: Install dependencies
working-directory: backend
run: |
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r backend/requirements.txt
pip install -r requirements.txt
pip install pytest pytest-asyncio pytest-cov httpx

- name: Start backend server
working-directory: backend
run: |
source .venv/bin/activate
export PYTHONPATH="$(pwd):$PYTHONPATH"
export TEST_BASE_URL="http://localhost:8013"
export TEST_COMPANY_ID="${{ secrets.TEST_COMPANY_ID }}"
export TEST_API_KEY="${{ secrets.TEST_API_KEY }}"
export SUPABASE_URL="${{ secrets.SUPABASE_URL }}"
export SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"
nohup .venv/bin/python -m uvicorn backend.main:app --host 0.0.0.0 --port 8013 &
export GEMINI_API_KEY="${{ secrets.GEMINI_API_KEY }}"
nohup .venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8013 &
sleep 10

- name: Wait for backend to be ready
Expand All @@ -69,20 +68,21 @@ jobs:
exit 1

- name: Run tests
working-directory: backend
run: |
source .venv/bin/activate
export TEST_BASE_URL="http://localhost:8013"
export TEST_COMPANY_ID="${{ secrets.TEST_COMPANY_ID }}"
export TEST_API_KEY="${{ secrets.TEST_API_KEY }}"
export SUPABASE_URL="${{ secrets.SUPABASE_URL }}"
export SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"
pytest backend/tests/ -v --cov=backend --cov-report=xml --cov-report=html --cov-report=term
export GEMINI_API_KEY="${{ secrets.GEMINI_API_KEY }}"
export TEST_COMPANY_ID="${{ secrets.TEST_COMPANY_ID }}"
export TEST_API_KEY="${{ secrets.TEST_API_KEY }}"
pytest tests/ -v --cov=. --cov-report=xml --cov-report=html --cov-report=term

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: success()
with:
files: ./coverage.xml
files: ./backend/coverage.xml
flags: unittests
name: AI-Agent-Marketplace
fail_ci_if_error: false
Expand All @@ -92,7 +92,7 @@ jobs:
if: always()
with:
name: coverage-report
path: htmlcov/
path: backend/htmlcov/
retention-days: 14

# ─────────────────────────────────────────────
Expand Down Expand Up @@ -147,28 +147,30 @@ jobs:
python-version: '3.12'

- name: Install dependencies
working-directory: backend
run: |
python -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
pip install -r requirements.txt
pip install pytest pytest-asyncio httpx

- name: Start backend
working-directory: backend
run: |
source .venv/bin/activate
export PYTHONPATH="$(pwd):$PYTHONPATH"
export TEST_BASE_URL="http://localhost:8013"
export SUPABASE_URL="${{ secrets.SUPABASE_URL }}"
export SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"
nohup .venv/bin/python -m uvicorn backend.main:app --host 0.0.0.0 --port 8013 &
export GEMINI_API_KEY="${{ secrets.GEMINI_API_KEY }}"
nohup .venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8013 &
sleep 10

- name: Run ${{ matrix.feature }} tests
working-directory: backend
run: |
source .venv/bin/activate
export TEST_BASE_URL="http://localhost:8013"
export TEST_COMPANY_ID="${{ secrets.TEST_COMPANY_ID }}"
export TEST_API_KEY="${{ secrets.TEST_API_KEY }}"
export SUPABASE_URL="${{ secrets.SUPABASE_URL }}"
export SUPABASE_ANON_KEY="${{ secrets.SUPABASE_ANON_KEY }}"
pytest backend/tests/ -v -m ${{ matrix.feature }}
export GEMINI_API_KEY="${{ secrets.GEMINI_API_KEY }}"
export TEST_COMPANY_ID="${{ secrets.TEST_COMPANY_ID }}"
export TEST_API_KEY="${{ secrets.TEST_API_KEY }}"
pytest tests/ -v -m ${{ matrix.feature }}
107 changes: 107 additions & 0 deletions backend/api/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,3 +616,110 @@ async def get_api_key_status(company_id: str):
"api_key_expiry_date": data.get("api_key_expiry_date"),
"has_active_key": data.get("api_key_status") == "active",
}


@router.get("/{company_id}/metrics", tags=["Company"])
async def get_company_metrics(
company_id: str,
range: str = "7d" # 24h, 7d, 30d
):
"""
Get company-wide metrics with time range support.
"""
from datetime import datetime, timedelta

db = get_supabase()

# Determine date cutoff
now = datetime.utcnow()
if range == "24h":
cutoff = now - timedelta(hours=24)
elif range == "30d":
cutoff = now - timedelta(days=30)
else: # 7d
cutoff = now - timedelta(days=7)

# Fetch execution history within range
hist = (
db.table("execution_history")
.select("*")
.eq("company_id", company_id)
.gte("executed_at", cutoff.isoformat())
.order("executed_at", desc=True)
.execute()
)

rows = hist.data or []

# Calculate overall metrics
total_requests = len(rows)
successful_requests = sum(1 for r in rows if r.get("success"))
failed_requests = total_requests - successful_requests

success_rate = (successful_requests / max(total_requests, 1)) * 100
avg_response_time = round(
sum(r.get("execution_time_seconds", 0) or 0 for r in rows) / max(total_requests, 1) * 1000,
2
) # Convert to ms

# Group by agent for breakdown
agent_metrics = {}
for row in rows:
agent_id = row.get("agent_id", "unknown")
if agent_id not in agent_metrics:
agent_metrics[agent_id] = {
"agent_id": agent_id,
"agent_name": row.get("agent_name", "Unknown Agent"),
"total_requests": 0,
"successful_requests": 0,
"failed_requests": 0,
"response_times": []
}

agent_metrics[agent_id]["total_requests"] += 1
if row.get("success"):
agent_metrics[agent_id]["successful_requests"] += 1
else:
agent_metrics[agent_id]["failed_requests"] += 1

if row.get("execution_time_seconds"):
agent_metrics[agent_id]["response_times"].append(row["execution_time_seconds"])

# Calculate per-agent stats
agents = []
for agent_id, metrics in agent_metrics.items():
success_rate_agent = (metrics["successful_requests"] / max(metrics["total_requests"], 1)) * 100
avg_response = (sum(metrics["response_times"]) / len(metrics["response_times"])) if metrics["response_times"] else 0

agents.append({
"agent_id": agent_id,
"agent_name": metrics["agent_name"],
"total_requests": metrics["total_requests"],
"successful_requests": metrics["successful_requests"],
"failed_requests": metrics["failed_requests"],
"success_rate": round(success_rate_agent, 1),
"avg_response_time": round(avg_response * 1000, 2) # Convert to ms
})

# Group by day for timeline
from collections import defaultdict
daily_requests = defaultdict(int)
for row in rows:
day = (row.get("executed_at") or "")[:10]
if day:
daily_requests[day] += 1

daily_requests_list = [
{"date": day, "count": count}
for day, count in sorted(daily_requests.items())
]

return {
"total_requests": total_requests,
"successful_requests": successful_requests,
"failed_requests": failed_requests,
"success_rate": round(success_rate, 1),
"avg_response_time": avg_response_time,
"agents": sorted(agents, key=lambda x: x["total_requests"], reverse=True),
"daily_requests": daily_requests_list
}
5 changes: 3 additions & 2 deletions backend/engine/intent_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,11 @@ def parse_intent(request: str) -> dict:
genai = _get_genai()
model = genai.GenerativeModel(
model_name=os.getenv("LLM_MODEL", "gemini-2.0-flash"),
system_instruction=_SYSTEM_PROMPT,
)

prompt = f"Parse this user request: {request}"
prompt = f"""{_SYSTEM_PROMPT}

Parse this user request: {request}"""
response = model.generate_content(prompt)
raw = response.text.strip()

Expand Down
3 changes: 2 additions & 1 deletion backend/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import os

import pytest

os.environ.setdefault("SUPABASE_URL", "https://iudjozrtichetbdwzgzv.supabase.co")
os.environ.setdefault(
"SUPABASE_ANON_KEY",
Expand Down
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
<title>AgentForge - AI Agent Marketplace</title>
</head>
<body>
<div id="root"></div>
Expand Down
Loading
Loading