From 79d0645fc9a0aa1c365762f012100e93ae53d3a1 Mon Sep 17 00:00:00 2001 From: Teo Date: Mon, 30 Dec 2024 07:12:31 +0100 Subject: [PATCH 1/6] py3.9 Signed-off-by: Teo --- .github/workflows/codecov.yml | 43 --------------- .github/workflows/python-testing.yml | 44 ---------------- .github/workflows/python-tests.yaml | 69 ++++++++++++++++++++++++ pyproject.toml | 79 ++++++++++++++++++---------- 4 files changed, 120 insertions(+), 115 deletions(-) delete mode 100644 .github/workflows/codecov.yml delete mode 100644 .github/workflows/python-testing.yml create mode 100644 .github/workflows/python-tests.yaml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index c60c9a54..00000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Codecov - -on: - push: - branches: - - main - paths: - - 'agentops/**/*.py' - pull_request: - branches: - - main - paths: - - 'agentops/**/*.py' - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.11 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - - name: Run tests with tox - run: tox - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.xml - flags: unittests - name: codecov-umbrella - fail_ci_if_error: true diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml deleted file mode 100644 index 357624a5..00000000 --- a/.github/workflows/python-testing.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Python Tests - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - 'agentops/**/*.py' - - 'agentops/**/*.ipynb' - - 'tests/**/*.py' - - 'tests/**/*.ipynb' - pull_request: - branches: - - main - paths: - - 'agentops/**/*.py' - - 'agentops/**/*.ipynb' - - 'tests/**/*.py' - - 'tests/**/*.ipynb' - -jobs: - build: - runs-on: ubuntu-latest - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - strategy: - matrix: - python-version: ["3.7","3.8","3.9","3.10","3.11","3.12"] - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - cache: 'pip' - python-version: '3.11' - - name: Install tox - run: pip install tox - - name: Run tests with tox - run: tox - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - AGENTOPS_API_KEY: ${{ secrets.AGENTOPS_API_KEY }} diff --git a/.github/workflows/python-tests.yaml b/.github/workflows/python-tests.yaml new file mode 100644 index 00000000..2f35bd9b --- /dev/null +++ b/.github/workflows/python-tests.yaml @@ -0,0 +1,69 @@ +# :: Use nektos/act to run this locally +# :: Example: +# :: `act push -j python-tests --matrix python-version:3.10 --container-architecture linux/amd64` +name: Python Tests +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'agentops/**/*.py' + - 'agentops/**/*.ipynb' + - 'tests/**/*.py' + - 'tests/**/*.ipynb' + pull_request: + branches: + - main + paths: + - 'agentops/**/*.py' + - 'agentops/**/*.ipynb' + - 'tests/**/*.py' + - 'tests/**/*.ipynb' + +jobs: + python-tests: + runs-on: ubuntu-latest + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12"] + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Setup UV + uses: astral-sh/setup-uv@v5 + continue-on-error: true + with: + python-version: ${{ matrix.python-version }} + cache-prefix: uv-${{ matrix.python-version }} + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" + + - name: Install dependencies + run: | + uv sync --group test --group dev + + - name: Run tests with coverage + timeout-minutes: 10 + run: | + uv run -m pytest tests/ -v --cov=agentops --cov-report=xml + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AGENTOPS_API_KEY: ${{ secrets.AGENTOPS_API_KEY }} + PYTHONUNBUFFERED: "1" + + # Only upload coverage report for python3.11 + - name: Upload coverage to Codecov + if: ${{matrix.python-version == '3.11'}} + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + flags: unittests + name: codecov-umbrella + fail_ci_if_error: true # Should we? diff --git a/pyproject.toml b/pyproject.toml index f8e0a770..d438e7a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,13 +9,19 @@ authors = [ { name="Alex Reibman", email="areibman@gmail.com" }, { name="Shawn Qiu", email="siyangqiu@gmail.com" }, { name="Braelyn Boynton", email="bboynton97@gmail.com" }, - { name="Howard Gil", email="howardbgil@gmail.com" } + { name="Howard Gil", email="howardbgil@gmail.com" }, + { name="Constantin Teodorescu", email="teocns@gmail.com"} ] description = "Observability and DevTool Platform for AI Agents" readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.9,<3.13" classifiers = [ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] @@ -24,52 +30,69 @@ dependencies = [ "psutil>=5.9.8,<6.1.0", "termcolor>=2.3.0,<2.5.0", "PyYAML>=5.3,<7.0", - "opentelemetry-api>=1.22.0,<2.0.0", # API for interfaces - "opentelemetry-sdk>=1.22.0,<2.0.0", # SDK for implementation - "opentelemetry-exporter-otlp-proto-http>=1.22.0,<2.0.0", # For OTLPSpanExporter + "opentelemetry-api>=1.22.0,<2.0.0", + "opentelemetry-sdk>=1.22.0,<2.0.0", + "opentelemetry-exporter-otlp-proto-http>=1.22.0,<2.0.0", + # "typing-extensions>=4.9.0; python_version >= '3.8'", + # "pydantic>=2.7.4,<3.0.0; python_version < '3.13'", + # "pydantic-core>=2.23.4; python_version >= '3.8' and python_version < '3.13'" ] [dependency-groups] -dev = [ - "pytest==7.4.0", - "pytest-depends", - "pytest-asyncio", - "pytest-vcr", - "pytest-mock", - "pyfakefs", - "requests_mock==1.11.0", - "ruff", - "vcrpy>=6.0.0; python_version >= '3.8'", - "python-dotenv" - ] -ci = [ - "tach~=0.9", +test = [ + "openai>=1.0.0,<2.0.0", + "langchain", + "pytest-cov", ] -[project.optional-dependencies] -langchain = [ - "langchain==0.2.14; python_version >= '3.8.1'" +dev = [ + # Testing essentials + "pytest>=7.4.0,<8.0.0", # Testing framework with good async support + "pytest-depends", # For testing complex agent workflows + "pytest-asyncio", # Async test support for testing concurrent agent operations + "pytest-mock", # Mocking capabilities for isolating agent components + "pyfakefs", # File system testing + "pytest-recording", # Alternative to pytest-vcr with better Python 3.x support + "vcrpy @ git+https://github.com/kevin1024/vcrpy.git@81978659f1b18bbb7040ceb324a19114e4a4f328", + # Code quality and type checking + "ruff", # Fast Python linter for maintaining code quality + "mypy", # Static type checking for better reliability + "types-requests", # Type stubs for requests library + + # HTTP mocking and environment + "requests_mock>=1.11.0", # Mock HTTP requests for testing agent external communications + "python-dotenv", # Environment management for secure testing + + # Agent integration testing ] +# CI dependencies +ci = [ + "tach~=0.9" # Task runner for CI/CD pipelines +] [project.urls] Homepage = "https://github.com/AgentOps-AI/agentops" Issues = "https://github.com/AgentOps-AI/agentops/issues" +[tool.uv] +compile-bytecode = true # Enable bytecode compilation for better performance +default-groups = ["test", "dev"] # Default groups to install for development + [tool.autopep8] max_line_length = 120 -[project.scripts] -agentops = "agentops.cli:main" - [tool.pytest.ini_options] -asyncio_mode = "strict" -asyncio_default_fixture_loop_scope = "function" +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" # WARNING: Changing this may break tests. A `module`-scoped session might be faster, but also unstable. test_paths = [ "tests", ] -addopts = "--import-mode=importlib --tb=short -p no:warnings" +addopts = "--tb=short -p no:warnings" pythonpath = ["."] +faulthandler_timeout = 30 # Reduced from 60 +timeout = 60 # Reduced from 300 +disable_socket = true # Add this to prevent hanging on socket cleanup [tool.ruff] line-length = 120 From 32b874acc9ebd960d8de5fbbac17fbd48267bdb3 Mon Sep 17 00:00:00 2001 From: Teo Date: Mon, 30 Dec 2024 07:16:21 +0100 Subject: [PATCH 2/6] add Python 3.13 support --- .github/workflows/python-tests.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-tests.yaml b/.github/workflows/python-tests.yaml index 2f35bd9b..e072b9b8 100644 --- a/.github/workflows/python-tests.yaml +++ b/.github/workflows/python-tests.yaml @@ -29,7 +29,7 @@ jobs: strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] fail-fast: false steps: diff --git a/pyproject.toml b/pyproject.toml index d438e7a3..03dc8af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ authors = [ ] description = "Observability and DevTool Platform for AI Agents" readme = "README.md" -requires-python = ">=3.9,<3.13" +requires-python = ">=3.9,<3.14" classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", From f8893a1c05ea51cbea729531e538828f60af2691 Mon Sep 17 00:00:00 2001 From: Teo Date: Mon, 30 Dec 2024 07:21:00 +0100 Subject: [PATCH 3/6] remove devin test timing out CI Signed-off-by: Teo --- .../test_openai_integration.py | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 tests/openai_handlers/test_openai_integration.py diff --git a/tests/openai_handlers/test_openai_integration.py b/tests/openai_handlers/test_openai_integration.py deleted file mode 100644 index 8b2a0fec..00000000 --- a/tests/openai_handlers/test_openai_integration.py +++ /dev/null @@ -1,69 +0,0 @@ -import pytest -import agentops -import asyncio -from openai import OpenAI, AsyncOpenAI -from dotenv import load_dotenv -import os - -load_dotenv() - - -@pytest.mark.integration -def test_openai_integration(): - """Integration test demonstrating all four OpenAI call patterns: - 1. Sync (non-streaming) - 2. Sync (streaming) - 3. Async (non-streaming) - 4. Async (streaming) - - Verifies that AgentOps correctly tracks all LLM calls via analytics. - """ - # Initialize AgentOps without auto-starting session - agentops.init(auto_start_session=False) - session = agentops.start_session() - - def sync_no_stream(): - client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) - client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "Hello from sync no stream"}], - ) - - def sync_stream(): - client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) - stream_result = client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "Hello from sync streaming"}], - stream=True, - ) - for _ in stream_result: - pass - - async def async_no_stream(): - client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) - await client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "Hello from async no stream"}], - ) - - async def async_stream(): - client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY")) - async_stream_result = await client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "Hello from async streaming"}], - stream=True, - ) - async for _ in async_stream_result: - pass - - # Call each function - sync_no_stream() - sync_stream() - asyncio.run(async_no_stream()) - asyncio.run(async_stream()) - - session.end_session("Success") - analytics = session.get_analytics() - print(analytics) - # Verify that all LLM calls were tracked - assert analytics["LLM calls"] >= 4, f"Expected at least 4 LLM calls, but got {analytics['LLM calls']}" From 371c086d4564cb7bffb3eb039930e43f88244af8 Mon Sep 17 00:00:00 2001 From: Teo Date: Mon, 30 Dec 2024 20:47:39 +0100 Subject: [PATCH 4/6] =?UTF-8?q?Revert=20"Replace=20usage=20of=20functools.?= =?UTF-8?q?cached=5Fproperty=20with=20DIY=20class=20for=20backwar=E2=80=A6?= =?UTF-8?q?=20(#613)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 82f9bb989e726c88e84265fc2399093cdcdfecf5. --- agentops/client.py | 2 +- agentops/helpers.py | 19 ------------ tests/test_helpers.py | 67 ------------------------------------------- 3 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 tests/test_helpers.py diff --git a/agentops/client.py b/agentops/client.py index 8e0b6de5..fb3e1793 100644 --- a/agentops/client.py +++ b/agentops/client.py @@ -14,6 +14,7 @@ import threading import traceback from decimal import Decimal +from functools import cached_property from typing import List, Optional, Tuple, Union from uuid import UUID, uuid4 @@ -27,7 +28,6 @@ from .meta_client import MetaClient from .session import Session, active_sessions from .singleton import conditional_singleton -from .helpers import cached_property @conditional_singleton diff --git a/agentops/helpers.py b/agentops/helpers.py index 3e8df36e..ca0c4f0e 100644 --- a/agentops/helpers.py +++ b/agentops/helpers.py @@ -174,22 +174,3 @@ def wrapper(self, *args, **kwargs): return func(self, *args, **kwargs) return wrapper - - -class cached_property: - """ - Decorator that converts a method with a single self argument into a - property cached on the instance. - See: https://github.com/AgentOps-AI/agentops/issues/612 - """ - - def __init__(self, func): - self.func = func - self.__doc__ = func.__doc__ - - def __get__(self, instance, cls=None): - if instance is None: - return self - value = self.func(instance) - setattr(instance, self.func.__name__, value) - return value diff --git a/tests/test_helpers.py b/tests/test_helpers.py deleted file mode 100644 index 401bb566..00000000 --- a/tests/test_helpers.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -from agentops.helpers import cached_property - - -def test_cached_property(): - class TestClass: - def __init__(self): - self.compute_count = 0 - - @cached_property - def expensive_computation(self): - self.compute_count += 1 - return 42 - - # Create instance - obj = TestClass() - - # First access should compute the value - assert obj.expensive_computation == 42 - assert obj.compute_count == 1 - - # Second access should use cached value - assert obj.expensive_computation == 42 - assert obj.compute_count == 1 # Count shouldn't increase - - # Third access should still use cached value - assert obj.expensive_computation == 42 - assert obj.compute_count == 1 # Count shouldn't increase - - -def test_cached_property_different_instances(): - class TestClass: - def __init__(self): - self.compute_count = 0 - - @cached_property - def expensive_computation(self): - self.compute_count += 1 - return id(self) # Return unique id for each instance - - # Create two different instances - obj1 = TestClass() - obj2 = TestClass() - - # Each instance should compute its own value - val1 = obj1.expensive_computation - val2 = obj2.expensive_computation - - assert val1 != val2 # Values should be different - assert obj1.compute_count == 1 - assert obj2.compute_count == 1 - - # Accessing again should use cached values - assert obj1.expensive_computation == val1 - assert obj2.expensive_computation == val2 - assert obj1.compute_count == 1 # Counts shouldn't increase - assert obj2.compute_count == 1 - - -def test_cached_property_class_access(): - class TestClass: - @cached_property - def expensive_computation(self): - return 42 - - # Accessing via class should return the descriptor - assert isinstance(TestClass.expensive_computation, cached_property) From 5e0a430882c85189db3f202a846fc0bd508b1c58 Mon Sep 17 00:00:00 2001 From: Teo Date: Mon, 30 Dec 2024 20:50:51 +0100 Subject: [PATCH 5/6] fix: TypeError: unsupported operand type(s) for |: 'type' and 'NoneType' Signed-off-by: Teo --- tests/test_agent.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_agent.py b/tests/test_agent.py index 95617ca6..6691b166 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -1,5 +1,6 @@ from unittest import TestCase from uuid import uuid4 +from typing import Optional from agentops import track_agent from agentops.descriptor import agentops_property @@ -99,6 +100,7 @@ def _model_validate(self): def test_track_agent_with_pydantic_model(self): """Test setting agentops_agent_name with actual Pydantic BaseModel""" try: + from typing import Optional from pydantic import BaseModel, Field, model_validator except ImportError: self.skipTest("Pydantic not installed, skipping Pydantic model test") @@ -106,8 +108,8 @@ def test_track_agent_with_pydantic_model(self): @track_agent() class TestAgentModel(BaseModel): role: str = Field(default="test_role") - agentops_agent_name: str | None = None - agentops_agent_id: str | None = None + agentops_agent_name: Optional[str] = None + agentops_agent_id: Optional[str] = None @model_validator(mode="after") def set_agent_name(self): From 9b7ef49c419a7c14875ef5da9210e10a88576969 Mon Sep 17 00:00:00 2001 From: Pratyush Shukla Date: Tue, 31 Dec 2024 01:43:44 +0530 Subject: [PATCH 6/6] add my name to pyproject.toml --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 03dc8af6..76485a68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ authors = [ { name="Shawn Qiu", email="siyangqiu@gmail.com" }, { name="Braelyn Boynton", email="bboynton97@gmail.com" }, { name="Howard Gil", email="howardbgil@gmail.com" }, - { name="Constantin Teodorescu", email="teocns@gmail.com"} + { name="Constantin Teodorescu", email="teocns@gmail.com" }, + { name="Pratyush Shukla", email="ps4534@nyu.edu" } ] description = "Observability and DevTool Platform for AI Agents" readme = "README.md"