Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(agents-api): Fix transitioning to error #1027

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from

Conversation

Ahmad-mtos
Copy link
Contributor

@Ahmad-mtos Ahmad-mtos commented Jan 7, 2025

PR Type

Bug fix, Enhancement


Description

  • Fixed transitioning to error and added cancelled state handling.

  • Enhanced retry policy and error handling in workflows.

  • Refactored database pool initialization for testing environments.

  • Improved workflow step handling with additional cases and transitions.


Changes walkthrough 📝

Relevant files
Enhancement
excecute_api_call.py
Added `StepOutcome` import for activity execution.             

agents-api/agents_api/activities/excecute_api_call.py

  • Added import for StepOutcome.
+1/-0     
execute_integration.py
Added `StepOutcome` import for integration execution.       

agents-api/agents_api/activities/execute_integration.py

  • Added StepOutcome import for integration execution.
+1/-1     
execute_system.py
Added `StepOutcome` import for system execution.                 

agents-api/agents_api/activities/execute_system.py

  • Added StepOutcome import for system execution.
+1/-1     
app.py
Refactored database pool handling for testing.                     

agents-api/agents_api/app.py

  • Refactored database pool initialization to handle testing
    environments.
  • Updated lifespan function to close pools conditionally.
  • +13/-11 
    __init__.py
    Enhanced workflow step handling and error management.       

    agents-api/agents_api/workflows/task_execution/init.py

  • Added handling for cancelled and error states in workflows.
  • Enhanced step handling with additional cases and transitions.
  • Improved error handling with specific exception cases.
  • Refactored workflow logic for better maintainability.
  • +482/-446
    transition.py
    Improved transition handling for error and cancellation. 

    agents-api/agents_api/workflows/task_execution/transition.py

  • Added support for cancelled and error states in transitions.
  • Refactored transition logic for better error handling.
  • +20/-10 

    💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information


    Important

    Refines error handling in TaskExecutionWorkflow and updates imports and testing environment handling across several files.

    • Error Handling:
      • Refines error handling in TaskExecutionWorkflow in __init__.py by adding specific exception cases for CancelledError and ApplicationError.
      • Updates transition() in transition.py to handle error and cancelled states more explicitly.
    • Imports:
      • Adds StepOutcome import in excecute_api_call.py, execute_integration.py, and execute_system.py.
      • Adds ActivityError and CancelledError imports in __init__.py.
    • Testing Environment:
      • Modifies lifespan() in app.py to handle postgres_pool and s3_client differently when testing is true.

    This description was created by Ellipsis for 259a1c4. It will automatically update as commits are pushed.

    Copy link
    Contributor

    qodo-merge-pro-for-open-source bot commented Jan 7, 2025

    CI Failure Feedback 🧐

    (Checks updated until commit f1da62f)

    Action: Test

    Failed stage: Run tests [❌]

    Failed test name: test_agent_routes

    Failure summary:

    The action failed due to a database connection pool configuration error. Specifically:

  • The database pool's min_size (10) was set larger than the max_size (4), which is invalid
  • This caused the test client fixture to fail to initialize
  • As a result, three test cases in test_agent_routes.py failed:
    1. "route: unauthorized should fail"

    2. "route: create agent"
    3. "route: create agent with instructions"

  • Relevant error logs:
    1:  ##[group]Operating System
    2:  Ubuntu
    ...
    
    1332:  PASS  test_agent_queries:28 query: create agent sql                          1%
    1333:  PASS  test_agent_queries:44 query: create or update agent sql                2%
    1334:  PASS  test_agent_queries:63 query: update agent sql                          2%
    1335:  PASS  test_agent_queries:85 query: get agent not exists sql                  3%
    1336:  PASS  test_agent_queries:96 query: get agent exists sql                      3%
    1337:  PASS  test_agent_queries:111 query: list agents sql                          4%
    1338:  PASS  test_agent_queries:122 query: patch agent sql                          4%
    1339:  PASS  test_agent_queries:143 query: delete agent sql                         5%
    1340:  FAIL  test_agent_routes:9 route: unauthorized should fail                    6%
    1341:  FAIL  test_agent_routes:26 route: create agent                               6%
    1342:  FAIL  test_agent_routes:43 route: create agent with instructions             7%
    1343:  ─────────────────────── route: unauthorized should fail ────────────────────────
    1344:  Failed at tests/test_agent_routes.py                                          
    ...
    
    1465:  │ │              name = '_dsn'                                           │ │  
    1466:  │ │              self = TestArgumentResolver(                            │ │  
    1467:  │ │                     │   test=Test(                                   │ │  
    1468:  │ │                     │   │   fn=<function _ at 0x7fd0b58dd440>,       │ │  
    1469:  │ │                     │   │   module_name='test_agent_routes',         │ │  
    1470:  │ │                     │   │   id='07c123707efd40f99867abccce9a57fb',   │ │  
    1471:  │ │                     │   │   marker=None,                             │ │  
    1472:  │ │                     │   │   description='route: unauthorized should  │ │  
    1473:  │ │                     fail',                                           │ │  
    ...
    
    1592:  │ │ self = <anyio._backends._asyncio.BlockingPortal object at            │ │  
    1593:  │ │        0x7fd0b565acf0>                                               │ │  
    1594:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    1595:  │                                                                          │  
    1596:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    1597:  │ python3.12/concurrent/futures/_base.py:456 in result                     │  
    1598:  │                                                                          │  
    1599:  │   453 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    1600:  │   454 │   │   │   │   │   raise CancelledError()                         │  
    1601:  │   455 │   │   │   │   elif self._state == FINISHED:                      │  
    1602:  │ ❱ 456 │   │   │   │   │   return self.__get_result()                     │  
    1603:  │   457 │   │   │   │   else:                                              │  
    1604:  │   458 │   │   │   │   │   raise TimeoutError()                           │  
    ...
    
    1636:  │   221 │   │   except self._cancelled_exc_class:                          │  
    1637:  │                                                                          │  
    1638:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    1639:  │ │                args = ()                                             │ │  
    1640:  │ │                func = <bound method TestClient.wait_startup of       │ │  
    1641:  │ │                       <starlette.testclient.TestClient object at     │ │  
    1642:  │ │                       0x7fd0b566f890>>                               │ │  
    1643:  │ │              future = <Future at 0x7fd0b566d580 state=finished       │ │  
    1644:  │ │                       raised ValueError>                             │ │  
    ...
    
    1648:  │ │               scope = None                                           │ │  
    1649:  │ │                self = <anyio._backends._asyncio.BlockingPortal       │ │  
    1650:  │ │                       object at 0x7fd0b565acf0>                      │ │  
    1651:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    1652:  │                                                                          │  
    1653:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    1654:  │ ges/starlette/testclient.py:774 in wait_startup                          │  
    1655:  │                                                                          │  
    1656:  │   771 │   │   │   "lifespan.startup.failed",                             │  
    1657:  │   772 │   │   )                                                          │  
    1658:  │   773 │   │   if message["type"] == "lifespan.startup.failed":           │  
    1659:  │ ❱ 774 │   │   │   await receive()                                        │  
    1660:  │   775 │                                                                  │  
    1661:  │   776 │   async def wait_shutdown(self) -> None:                         │  
    1662:  │   777 │   │   async def receive() -> typing.Any:                         │  
    1663:  │                                                                          │  
    1664:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    1665:  │ │ message = {                                                          │ │  
    1666:  │ │           │   'type': 'lifespan.startup.failed',                     │ │  
    ...
    
    1685:  │ │ message = None                                                       │ │  
    1686:  │ │    self = <starlette.testclient.TestClient object at 0x7fd0b566f890> │ │  
    1687:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    1688:  │                                                                          │  
    1689:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    1690:  │ python3.12/concurrent/futures/_base.py:449 in result                     │  
    1691:  │                                                                          │  
    1692:  │   446 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    1693:  │   447 │   │   │   │   │   raise CancelledError()                         │  
    ...
    
    1729:  │   221 │   │   except self._cancelled_exc_class:                          │  
    1730:  │                                                                          │  
    1731:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    1732:  │ │                args = ()                                             │ │  
    1733:  │ │                func = <bound method TestClient.lifespan of           │ │  
    1734:  │ │                       <starlette.testclient.TestClient object at     │ │  
    1735:  │ │                       0x7fd0b566f890>>                               │ │  
    1736:  │ │              future = <Future at 0x7fd0b56b0e60 state=finished       │ │  
    1737:  │ │                       raised ValueError>                             │ │  
    ...
    
    1844:  │ │           waiting_senders=OrderedDict()), _closed=False),            │ │  
    1845:  │ │           receive_stream=MemoryObjectReceiveStream(_state=MemoryObj… │ │  
    1846:  │ │           buffer=deque([]), open_send_channels=1,                    │ │  
    1847:  │ │           open_receive_channels=1, waiting_receivers=OrderedDict(),  │ │  
    1848:  │ │           waiting_senders=OrderedDict()), _closed=False))>           │ │  
    1849:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    1850:  │                                                                          │  
    1851:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    1852:  │ ges/starlette/middleware/errors.py:152 in __call__                       │  
    ...
    
    1872:  │ │   scope = {                                                          │ │  
    1873:  │ │           │   'type': 'lifespan',                                    │ │  
    1874:  │ │           │   'state': {},                                           │ │  
    1875:  │ │           │   'app': <fastapi.applications.FastAPI object at         │ │  
    1876:  │ │           0x7fd0b6fd73b0>,                                           │ │  
    1877:  │ │           │   'router': <fastapi.routing.APIRouter object at         │ │  
    1878:  │ │           0x7fd0b7bfc920>                                            │ │  
    1879:  │ │           }                                                          │ │  
    1880:  │ │    self = <starlette.middleware.errors.ServerErrorMiddleware object  │ │  
    ...
    
    2102:  │ ges/starlette/routing.py:693 in lifespan                                 │  
    2103:  │                                                                          │  
    2104:  │   690 │   │   app: typing.Any = scope.get("app")                         │  
    2105:  │   691 │   │   await receive()                                            │  
    2106:  │   692 │   │   try:                                                       │  
    2107:  │ ❱ 693 │   │   │   async with self.lifespan_context(app) as maybe_state:  │  
    2108:  │   694 │   │   │   │   if maybe_state is not None:                        │  
    2109:  │   695 │   │   │   │   │   if "state" not in scope:                       │  
    2110:  │   696 │   │   │   │   │   │   raise RuntimeError('The server does not su │  
    ...
    
    2146:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2147:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2148:  │                                                                          │  
    2149:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2150:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2151:  │   209 │   │   try:                                                       │  
    2152:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2153:  │   211 │   │   except StopAsyncIteration:                                 │  
    2154:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2180:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2181:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2182:  │                                                                          │  
    2183:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2184:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2185:  │   209 │   │   try:                                                       │  
    2186:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2187:  │   211 │   │   except StopAsyncIteration:                                 │  
    2188:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2214:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2215:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2216:  │                                                                          │  
    2217:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2218:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2219:  │   209 │   │   try:                                                       │  
    2220:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2221:  │   211 │   │   except StopAsyncIteration:                                 │  
    2222:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2248:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2249:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2250:  │                                                                          │  
    2251:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2252:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2253:  │   209 │   │   try:                                                       │  
    2254:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2255:  │   211 │   │   except StopAsyncIteration:                                 │  
    2256:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2282:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2283:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2284:  │                                                                          │  
    2285:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2286:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2287:  │   209 │   │   try:                                                       │  
    2288:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2289:  │   211 │   │   except StopAsyncIteration:                                 │  
    2290:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2316:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2317:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2318:  │                                                                          │  
    2319:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2320:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2321:  │   209 │   │   try:                                                       │  
    2322:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2323:  │   211 │   │   except StopAsyncIteration:                                 │  
    2324:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2350:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2351:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2352:  │                                                                          │  
    2353:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2354:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2355:  │   209 │   │   try:                                                       │  
    2356:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2357:  │   211 │   │   except StopAsyncIteration:                                 │  
    2358:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2384:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2385:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2386:  │                                                                          │  
    2387:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2388:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2389:  │   209 │   │   try:                                                       │  
    2390:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2391:  │   211 │   │   except StopAsyncIteration:                                 │  
    2392:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2418:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2419:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2420:  │                                                                          │  
    2421:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2422:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2423:  │   209 │   │   try:                                                       │  
    2424:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2425:  │   211 │   │   except StopAsyncIteration:                                 │  
    2426:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2452:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2453:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2454:  │                                                                          │  
    2455:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2456:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2457:  │   209 │   │   try:                                                       │  
    2458:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2459:  │   211 │   │   except StopAsyncIteration:                                 │  
    2460:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2486:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    2487:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    2488:  │                                                                          │  
    2489:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    2490:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    2491:  │   209 │   │   try:                                                       │  
    2492:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    2493:  │   211 │   │   except StopAsyncIteration:                                 │  
    2494:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    2557:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    2558:  │                                                                          │  
    2559:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    2560:  │ ges/asyncpg/pool.py:361 in __init__                                      │  
    2561:  │                                                                          │  
    2562:  │    358 │   │   │   │   'min_size is expected to be greater or equal to z │  
    2563:  │    359 │   │                                                             │  
    2564:  │    360 │   │   if min_size > max_size:                                   │  
    2565:  │ ❱  361 │   │   │   raise ValueError('min_size is greater than max_size') │  
    2566:  │    362 │   │                                                             │  
    2567:  │    363 │   │   if max_queries <= 0:                                      │  
    2568:  │    364 │   │   │   raise ValueError('max_queries is expected to be great │  
    ...
    
    2582:  │ │                        max_size = 4                                  │ │  
    2583:  │ │                        min_size = 10                                 │ │  
    2584:  │ │                           reset = None                               │ │  
    2585:  │ │                            self = <asyncpg.pool.Pool object at       │ │  
    2586:  │ │                                   0x7fd0b5684280>                    │ │  
    2587:  │ │                           setup = None                               │ │  
    2588:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    2589:  ╰──────────────────────────────────────────────────────────────────────────╯  
    2590:  ValueError: min_size is greater than max_size                                 
    ...
    
    2667:  │ │         │   }                                                        │ │  
    2668:  │ │         )                                                            │ │  
    2669:  │ │  self = TestArgumentResolver(                                        │ │  
    2670:  │ │         │   test=Test(                                               │ │  
    2671:  │ │         │   │   fn=<function _ at 0x7fd0b58dd440>,                   │ │  
    2672:  │ │         │   │   module_name='test_agent_routes',                     │ │  
    2673:  │ │         │   │   id='07c123707efd40f99867abccce9a57fb',               │ │  
    2674:  │ │         │   │   marker=None,                                         │ │  
    2675:  │ │         │   │   description='route: unauthorized should fail',       │ │  
    ...
    
    2794:  │ │      resolved_args = {}                                              │ │  
    2795:  │ │               self = TestArgumentResolver(                           │ │  
    2796:  │ │                      │   test=Test(                                  │ │  
    2797:  │ │                      │   │   fn=<function _ at 0x7fd0b58dd440>,      │ │  
    2798:  │ │                      │   │   module_name='test_agent_routes',        │ │  
    2799:  │ │                      │   │   id='07c123707efd40f99867abccce9a57fb',  │ │  
    2800:  │ │                      │   │   marker=None,                            │ │  
    2801:  │ │                      │   │   description='route: unauthorized should │ │  
    2802:  │ │                      fail',                                          │ │  
    ...
    
    2827:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    2828:  │                                                                          │  
    2829:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    2830:  │ ges/ward/testing.py:637 in _resolve_single_arg                           │  
    2831:  │                                                                          │  
    2832:  │   634 │   │   │   else:                                                  │  
    2833:  │   635 │   │   │   │   fixture.resolved_val = arg(**args_to_inject)       │  
    2834:  │   636 │   │   except (Exception, SystemExit) as e:                       │  
    2835:  │ ❱ 637 │   │   │   raise FixtureError(f"Unable to resolve fixture '{fixtu │  
    ...
    
    2948:  │ │              name = '_dsn'                                           │ │  
    2949:  │ │              self = TestArgumentResolver(                            │ │  
    2950:  │ │                     │   test=Test(                                   │ │  
    2951:  │ │                     │   │   fn=<function _ at 0x7fd0b58dd440>,       │ │  
    2952:  │ │                     │   │   module_name='test_agent_routes',         │ │  
    2953:  │ │                     │   │   id='07c123707efd40f99867abccce9a57fb',   │ │  
    2954:  │ │                     │   │   marker=None,                             │ │  
    2955:  │ │                     │   │   description='route: unauthorized should  │ │  
    2956:  │ │                     fail',                                           │ │  
    ...
    
    2975:  │ │                     │   │   timer=<ward._testing._Timer object at    │ │  
    2976:  │ │                     0x7fd0b5664b90>,                                 │ │  
    2977:  │ │                     │   │   tags=[]                                  │ │  
    2978:  │ │                     │   ),                                           │ │  
    2979:  │ │                     │   iteration=0                                  │ │  
    2980:  │ │                     )                                                │ │  
    2981:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    2982:  ╰──────────────────────────────────────────────────────────────────────────╯  
    2983:  FixtureError: Unable to resolve fixture 'client'                              
    2984:  ───────────────────────────── route: create agent ──────────────────────────────
    2985:  Failed at tests/test_agent_routes.py                                          
    ...
    
    3232:  │ │ self = <anyio._backends._asyncio.BlockingPortal object at            │ │  
    3233:  │ │        0x7fd0b56b2b70>                                               │ │  
    3234:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    3235:  │                                                                          │  
    3236:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3237:  │ python3.12/concurrent/futures/_base.py:456 in result                     │  
    3238:  │                                                                          │  
    3239:  │   453 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    3240:  │   454 │   │   │   │   │   raise CancelledError()                         │  
    3241:  │   455 │   │   │   │   elif self._state == FINISHED:                      │  
    3242:  │ ❱ 456 │   │   │   │   │   return self.__get_result()                     │  
    3243:  │   457 │   │   │   │   else:                                              │  
    3244:  │   458 │   │   │   │   │   raise TimeoutError()                           │  
    ...
    
    3276:  │   221 │   │   except self._cancelled_exc_class:                          │  
    3277:  │                                                                          │  
    3278:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    3279:  │ │                args = ()                                             │ │  
    3280:  │ │                func = <bound method TestClient.wait_startup of       │ │  
    3281:  │ │                       <starlette.testclient.TestClient object at     │ │  
    3282:  │ │                       0x7fd0b56b1010>>                               │ │  
    3283:  │ │              future = <Future at 0x7fd0b56b2f30 state=finished       │ │  
    3284:  │ │                       raised ValueError>                             │ │  
    ...
    
    3288:  │ │               scope = None                                           │ │  
    3289:  │ │                self = <anyio._backends._asyncio.BlockingPortal       │ │  
    3290:  │ │                       object at 0x7fd0b56b2b70>                      │ │  
    3291:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    3292:  │                                                                          │  
    3293:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    3294:  │ ges/starlette/testclient.py:774 in wait_startup                          │  
    3295:  │                                                                          │  
    3296:  │   771 │   │   │   "lifespan.startup.failed",                             │  
    3297:  │   772 │   │   )                                                          │  
    3298:  │   773 │   │   if message["type"] == "lifespan.startup.failed":           │  
    3299:  │ ❱ 774 │   │   │   await receive()                                        │  
    3300:  │   775 │                                                                  │  
    3301:  │   776 │   async def wait_shutdown(self) -> None:                         │  
    3302:  │   777 │   │   async def receive() -> typing.Any:                         │  
    3303:  │                                                                          │  
    3304:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    3305:  │ │ message = {                                                          │ │  
    3306:  │ │           │   'type': 'lifespan.startup.failed',                     │ │  
    ...
    
    3325:  │ │ message = None                                                       │ │  
    3326:  │ │    self = <starlette.testclient.TestClient object at 0x7fd0b56b1010> │ │  
    3327:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    3328:  │                                                                          │  
    3329:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3330:  │ python3.12/concurrent/futures/_base.py:449 in result                     │  
    3331:  │                                                                          │  
    3332:  │   446 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    3333:  │   447 │   │   │   │   │   raise CancelledError()                         │  
    ...
    
    3369:  │   221 │   │   except self._cancelled_exc_class:                          │  
    3370:  │                                                                          │  
    3371:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    3372:  │ │                args = ()                                             │ │  
    3373:  │ │                func = <bound method TestClient.lifespan of           │ │  
    3374:  │ │                       <starlette.testclient.TestClient object at     │ │  
    3375:  │ │                       0x7fd0b56b1010>>                               │ │  
    3376:  │ │              future = <Future at 0x7fd0b56b2e70 state=finished       │ │  
    3377:  │ │                       raised ValueError>                             │ │  
    ...
    
    3484:  │ │           waiting_senders=OrderedDict()), _closed=False),            │ │  
    3485:  │ │           receive_stream=MemoryObjectReceiveStream(_state=MemoryObj… │ │  
    3486:  │ │           buffer=deque([]), open_send_channels=1,                    │ │  
    3487:  │ │           open_receive_channels=1, waiting_receivers=OrderedDict(),  │ │  
    3488:  │ │           waiting_senders=OrderedDict()), _closed=False))>           │ │  
    3489:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    3490:  │                                                                          │  
    3491:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    3492:  │ ges/starlette/middleware/errors.py:152 in __call__                       │  
    ...
    
    3512:  │ │   scope = {                                                          │ │  
    3513:  │ │           │   'type': 'lifespan',                                    │ │  
    3514:  │ │           │   'state': {},                                           │ │  
    3515:  │ │           │   'app': <fastapi.applications.FastAPI object at         │ │  
    3516:  │ │           0x7fd0b6fd73b0>,                                           │ │  
    3517:  │ │           │   'router': <fastapi.routing.APIRouter object at         │ │  
    3518:  │ │           0x7fd0b7bfc920>                                            │ │  
    3519:  │ │           }                                                          │ │  
    3520:  │ │    self = <starlette.middleware.errors.ServerErrorMiddleware object  │ │  
    ...
    
    3742:  │ ges/starlette/routing.py:693 in lifespan                                 │  
    3743:  │                                                                          │  
    3744:  │   690 │   │   app: typing.Any = scope.get("app")                         │  
    3745:  │   691 │   │   await receive()                                            │  
    3746:  │   692 │   │   try:                                                       │  
    3747:  │ ❱ 693 │   │   │   async with self.lifespan_context(app) as maybe_state:  │  
    3748:  │   694 │   │   │   │   if maybe_state is not None:                        │  
    3749:  │   695 │   │   │   │   │   if "state" not in scope:                       │  
    3750:  │   696 │   │   │   │   │   │   raise RuntimeError('The server does not su │  
    ...
    
    3786:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3787:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3788:  │                                                                          │  
    3789:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3790:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3791:  │   209 │   │   try:                                                       │  
    3792:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3793:  │   211 │   │   except StopAsyncIteration:                                 │  
    3794:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3820:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3821:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3822:  │                                                                          │  
    3823:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3824:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3825:  │   209 │   │   try:                                                       │  
    3826:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3827:  │   211 │   │   except StopAsyncIteration:                                 │  
    3828:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3854:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3855:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3856:  │                                                                          │  
    3857:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3858:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3859:  │   209 │   │   try:                                                       │  
    3860:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3861:  │   211 │   │   except StopAsyncIteration:                                 │  
    3862:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3888:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3889:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3890:  │                                                                          │  
    3891:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3892:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3893:  │   209 │   │   try:                                                       │  
    3894:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3895:  │   211 │   │   except StopAsyncIteration:                                 │  
    3896:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3922:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3923:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3924:  │                                                                          │  
    3925:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3926:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3927:  │   209 │   │   try:                                                       │  
    3928:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3929:  │   211 │   │   except StopAsyncIteration:                                 │  
    3930:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3956:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3957:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3958:  │                                                                          │  
    3959:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3960:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3961:  │   209 │   │   try:                                                       │  
    3962:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3963:  │   211 │   │   except StopAsyncIteration:                                 │  
    3964:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    3990:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    3991:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    3992:  │                                                                          │  
    3993:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    3994:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    3995:  │   209 │   │   try:                                                       │  
    3996:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    3997:  │   211 │   │   except StopAsyncIteration:                                 │  
    3998:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    4024:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    4025:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    4026:  │                                                                          │  
    4027:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    4028:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    4029:  │   209 │   │   try:                                                       │  
    4030:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    4031:  │   211 │   │   except StopAsyncIteration:                                 │  
    4032:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    4058:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    4059:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    4060:  │                                                                          │  
    4061:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    4062:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    4063:  │   209 │   │   try:                                                       │  
    4064:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    4065:  │   211 │   │   except StopAsyncIteration:                                 │  
    4066:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    4092:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    4093:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    4094:  │                                                                          │  
    4095:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    4096:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    4097:  │   209 │   │   try:                                                       │  
    4098:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    4099:  │   211 │   │   except StopAsyncIteration:                                 │  
    4100:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    4126:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    4127:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    4128:  │                                                                          │  
    4129:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    4130:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    4131:  │   209 │   │   try:                                                       │  
    4132:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    4133:  │   211 │   │   except StopAsyncIteration:                                 │  
    4134:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    4197:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    4198:  │                                                                          │  
    4199:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    4200:  │ ges/asyncpg/pool.py:361 in __init__                                      │  
    4201:  │                                                                          │  
    4202:  │    358 │   │   │   │   'min_size is expected to be greater or equal to z │  
    4203:  │    359 │   │                                                             │  
    4204:  │    360 │   │   if min_size > max_size:                                   │  
    4205:  │ ❱  361 │   │   │   raise ValueError('min_size is greater than max_size') │  
    4206:  │    362 │   │                                                             │  
    4207:  │    363 │   │   if max_queries <= 0:                                      │  
    4208:  │    364 │   │   │   raise ValueError('max_queries is expected to be great │  
    ...
    
    4222:  │ │                        max_size = 4                                  │ │  
    4223:  │ │                        min_size = 10                                 │ │  
    4224:  │ │                           reset = None                               │ │  
    4225:  │ │                            self = <asyncpg.pool.Pool object at       │ │  
    4226:  │ │                                   0x7fd0b5685900>                    │ │  
    4227:  │ │                           setup = None                               │ │  
    4228:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    4229:  ╰──────────────────────────────────────────────────────────────────────────╯  
    4230:  ValueError: min_size is greater than max_size                                 
    ...
    
    4608:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    4609:  │                                                                          │  
    4610:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    4611:  │ ges/ward/testing.py:637 in _resolve_single_arg                           │  
    4612:  │                                                                          │  
    4613:  │   634 │   │   │   else:                                                  │  
    4614:  │   635 │   │   │   │   fixture.resolved_val = arg(**args_to_inject)       │  
    4615:  │   636 │   │   except (Exception, SystemExit) as e:                       │  
    4616:  │ ❱ 637 │   │   │   raise FixtureError(f"Unable to resolve fixture '{fixtu │  
    ...
    
    4755:  │ │                     │   │   timer=<ward._testing._Timer object at    │ │  
    4756:  │ │                     0x7fd0b565ac60>,                                 │ │  
    4757:  │ │                     │   │   tags=[]                                  │ │  
    4758:  │ │                     │   ),                                           │ │  
    4759:  │ │                     │   iteration=0                                  │ │  
    4760:  │ │                     )                                                │ │  
    4761:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    4762:  ╰──────────────────────────────────────────────────────────────────────────╯  
    4763:  FixtureError: Unable to resolve fixture 'client'                              
    4764:  ──────────────────── route: create agent with instructions ─────────────────────
    4765:  Failed at tests/test_agent_routes.py                                          
    ...
    
    5013:  │ │ self = <anyio._backends._asyncio.BlockingPortal object at            │ │  
    5014:  │ │        0x7fd0b56b3ce0>                                               │ │  
    5015:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    5016:  │                                                                          │  
    5017:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5018:  │ python3.12/concurrent/futures/_base.py:456 in result                     │  
    5019:  │                                                                          │  
    5020:  │   453 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    5021:  │   454 │   │   │   │   │   raise CancelledError()                         │  
    5022:  │   455 │   │   │   │   elif self._state == FINISHED:                      │  
    5023:  │ ❱ 456 │   │   │   │   │   return self.__get_result()                     │  
    5024:  │   457 │   │   │   │   else:                                              │  
    5025:  │   458 │   │   │   │   │   raise TimeoutError()                           │  
    ...
    
    5057:  │   221 │   │   except self._cancelled_exc_class:                          │  
    5058:  │                                                                          │  
    5059:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    5060:  │ │                args = ()                                             │ │  
    5061:  │ │                func = <bound method TestClient.wait_startup of       │ │  
    5062:  │ │                       <starlette.testclient.TestClient object at     │ │  
    5063:  │ │                       0x7fd0b56b30e0>>                               │ │  
    5064:  │ │              future = <Future at 0x7fd0b566f6b0 state=finished       │ │  
    5065:  │ │                       raised ValueError>                             │ │  
    ...
    
    5069:  │ │               scope = None                                           │ │  
    5070:  │ │                self = <anyio._backends._asyncio.BlockingPortal       │ │  
    5071:  │ │                       object at 0x7fd0b56b3ce0>                      │ │  
    5072:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    5073:  │                                                                          │  
    5074:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    5075:  │ ges/starlette/testclient.py:774 in wait_startup                          │  
    5076:  │                                                                          │  
    5077:  │   771 │   │   │   "lifespan.startup.failed",                             │  
    5078:  │   772 │   │   )                                                          │  
    5079:  │   773 │   │   if message["type"] == "lifespan.startup.failed":           │  
    5080:  │ ❱ 774 │   │   │   await receive()                                        │  
    5081:  │   775 │                                                                  │  
    5082:  │   776 │   async def wait_shutdown(self) -> None:                         │  
    5083:  │   777 │   │   async def receive() -> typing.Any:                         │  
    5084:  │                                                                          │  
    5085:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    5086:  │ │ message = {                                                          │ │  
    5087:  │ │           │   'type': 'lifespan.startup.failed',                     │ │  
    ...
    
    5106:  │ │ message = None                                                       │ │  
    5107:  │ │    self = <starlette.testclient.TestClient object at 0x7fd0b56b30e0> │ │  
    5108:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    5109:  │                                                                          │  
    5110:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5111:  │ python3.12/concurrent/futures/_base.py:449 in result                     │  
    5112:  │                                                                          │  
    5113:  │   446 │   │   │   │   if self._state in [CANCELLED, CANCELLED_AND_NOTIFI │  
    5114:  │   447 │   │   │   │   │   raise CancelledError()                         │  
    ...
    
    5150:  │   221 │   │   except self._cancelled_exc_class:                          │  
    5151:  │                                                                          │  
    5152:  │ ╭─────────────────────────────── locals ───────────────────────────────╮ │  
    5153:  │ │                args = ()                                             │ │  
    5154:  │ │                func = <bound method TestClient.lifespan of           │ │  
    5155:  │ │                       <starlette.testclient.TestClient object at     │ │  
    5156:  │ │                       0x7fd0b56b30e0>>                               │ │  
    5157:  │ │              future = <Future at 0x7fd0b56b4050 state=finished       │ │  
    5158:  │ │                       raised ValueError>                             │ │  
    ...
    
    5265:  │ │           waiting_senders=OrderedDict()), _closed=False),            │ │  
    5266:  │ │           receive_stream=MemoryObjectReceiveStream(_state=MemoryObj… │ │  
    5267:  │ │           buffer=deque([]), open_send_channels=1,                    │ │  
    5268:  │ │           open_receive_channels=1, waiting_receivers=OrderedDict(),  │ │  
    5269:  │ │           waiting_senders=OrderedDict()), _closed=False))>           │ │  
    5270:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    5271:  │                                                                          │  
    5272:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    5273:  │ ges/starlette/middleware/errors.py:152 in __call__                       │  
    ...
    
    5293:  │ │   scope = {                                                          │ │  
    5294:  │ │           │   'type': 'lifespan',                                    │ │  
    5295:  │ │           │   'state': {},                                           │ │  
    5296:  │ │           │   'app': <fastapi.applications.FastAPI object at         │ │  
    5297:  │ │           0x7fd0b6fd73b0>,                                           │ │  
    5298:  │ │           │   'router': <fastapi.routing.APIRouter object at         │ │  
    5299:  │ │           0x7fd0b7bfc920>                                            │ │  
    5300:  │ │           }                                                          │ │  
    5301:  │ │    self = <starlette.middleware.errors.ServerErrorMiddleware object  │ │  
    ...
    
    5523:  │ ges/starlette/routing.py:693 in lifespan                                 │  
    5524:  │                                                                          │  
    5525:  │   690 │   │   app: typing.Any = scope.get("app")                         │  
    5526:  │   691 │   │   await receive()                                            │  
    5527:  │   692 │   │   try:                                                       │  
    5528:  │ ❱ 693 │   │   │   async with self.lifespan_context(app) as maybe_state:  │  
    5529:  │   694 │   │   │   │   if maybe_state is not None:                        │  
    5530:  │   695 │   │   │   │   │   if "state" not in scope:                       │  
    5531:  │   696 │   │   │   │   │   │   raise RuntimeError('The server does not su │  
    ...
    
    5567:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5568:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5569:  │                                                                          │  
    5570:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5571:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5572:  │   209 │   │   try:                                                       │  
    5573:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5574:  │   211 │   │   except StopAsyncIteration:                                 │  
    5575:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5601:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5602:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5603:  │                                                                          │  
    5604:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5605:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5606:  │   209 │   │   try:                                                       │  
    5607:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5608:  │   211 │   │   except StopAsyncIteration:                                 │  
    5609:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5635:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5636:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5637:  │                                                                          │  
    5638:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5639:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5640:  │   209 │   │   try:                                                       │  
    5641:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5642:  │   211 │   │   except StopAsyncIteration:                                 │  
    5643:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5669:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5670:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5671:  │                                                                          │  
    5672:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5673:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5674:  │   209 │   │   try:                                                       │  
    5675:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5676:  │   211 │   │   except StopAsyncIteration:                                 │  
    5677:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5703:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5704:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5705:  │                                                                          │  
    5706:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5707:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5708:  │   209 │   │   try:                                                       │  
    5709:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5710:  │   211 │   │   except StopAsyncIteration:                                 │  
    5711:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5737:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5738:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5739:  │                                                                          │  
    5740:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5741:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5742:  │   209 │   │   try:                                                       │  
    5743:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5744:  │   211 │   │   except StopAsyncIteration:                                 │  
    5745:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5771:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5772:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5773:  │                                                                          │  
    5774:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5775:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5776:  │   209 │   │   try:                                                       │  
    5777:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5778:  │   211 │   │   except StopAsyncIteration:                                 │  
    5779:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5805:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5806:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5807:  │                                                                          │  
    5808:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5809:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5810:  │   209 │   │   try:                                                       │  
    5811:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5812:  │   211 │   │   except StopAsyncIteration:                                 │  
    5813:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5839:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5840:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5841:  │                                                                          │  
    5842:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5843:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5844:  │   209 │   │   try:                                                       │  
    5845:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5846:  │   211 │   │   except StopAsyncIteration:                                 │  
    5847:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5873:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5874:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5875:  │                                                                          │  
    5876:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5877:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5878:  │   209 │   │   try:                                                       │  
    5879:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5880:  │   211 │   │   except StopAsyncIteration:                                 │  
    5881:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5907:  │ /home/runner/.local/share/uv/python/cpython-3.12.8-linux-x86_64-gnu/lib/ │  
    5908:  │ python3.12/contextlib.py:210 in __aenter__                               │  
    5909:  │                                                                          │  
    5910:  │   207 │   │   # they are only needed for recreation, which is not possib │  
    5911:  │   208 │   │   del self.args, self.kwds, self.func                        │  
    5912:  │   209 │   │   try:                                                       │  
    5913:  │ ❱ 210 │   │   │   return await anext(self.gen)                           │  
    5914:  │   211 │   │   except StopAsyncIteration:                                 │  
    5915:  │   212 │   │   │   raise RuntimeError("generator didn't yield") from None │  
    ...
    
    5978:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    5979:  │                                                                          │  
    5980:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    5981:  │ ges/asyncpg/pool.py:361 in __init__                                      │  
    5982:  │                                                                          │  
    5983:  │    358 │   │   │   │   'min_size is expected to be greater or equal to z │  
    5984:  │    359 │   │                                                             │  
    5985:  │    360 │   │   if min_size > max_size:                                   │  
    5986:  │ ❱  361 │   │   │   raise ValueError('min_size is greater than max_size') │  
    5987:  │    362 │   │                                                             │  
    5988:  │    363 │   │   if max_queries <= 0:                                      │  
    5989:  │    364 │   │   │   raise ValueError('max_queries is expected to be great │  
    ...
    
    6003:  │ │                        max_size = 4                                  │ │  
    6004:  │ │                        min_size = 10                                 │ │  
    6005:  │ │                           reset = None                               │ │  
    6006:  │ │                            self = <asyncpg.pool.Pool object at       │ │  
    6007:  │ │                                   0x7fd0b57e6b00>                    │ │  
    6008:  │ │                           setup = None                               │ │  
    6009:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    6010:  ╰──────────────────────────────────────────────────────────────────────────╯  
    6011:  ValueError: min_size is greater than max_size                                 
    ...
    
    6391:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    6392:  │                                                                          │  
    6393:  │ /home/runner/work/julep/julep/agents-api/.venv/lib/python3.12/site-packa │  
    6394:  │ ges/ward/testing.py:637 in _resolve_single_arg                           │  
    6395:  │                                                                          │  
    6396:  │   634 │   │   │   else:                                                  │  
    6397:  │   635 │   │   │   │   fixture.resolved_val = arg(**args_to_inject)       │  
    6398:  │   636 │   │   except (Exception, SystemExit) as e:                       │  
    6399:  │ ❱ 637 │   │   │   raise FixtureError(f"Unable to resolve fixture '{fixtu │  
    ...
    
    6539:  │ │                     │   │   timer=<ward._testing._Timer object at    │ │  
    6540:  │ │                     0x7fd0b5818830>,                                 │ │  
    6541:  │ │                     │   │   tags=[]                                  │ │  
    6542:  │ │                     │   ),                                           │ │  
    6543:  │ │                     │   iteration=0                                  │ │  
    6544:  │ │                     )                                                │ │  
    6545:  │ ╰──────────────────────────────────────────────────────────────────────╯ │  
    6546:  ╰──────────────────────────────────────────────────────────────────────────╯  
    6547:  FixtureError: Unable to resolve fixture 'client'                              
    6548:  ────────────────────────────────────────────────────────────────────────────────
    6549:  ╭──────────── Results ─────────────╮
    6550:  │  12  Tests Encountered           │
    6551:  │   9  Passes             (75.0%)  │
    6552:  │   3  Failures           (25.0%)  │
    6553:  ╰──────────────────────────────────╯
    6554:  ─────────────────────────── FAILED in 51.16 seconds ────────────────────────────
    6555:  ##[error]Process completed with exit code 1.
    

    ✨ CI feedback usage guide:

    The CI feedback tool (/checks) automatically triggers when a PR has a failed check.
    The tool analyzes the failed checks and provides several feedbacks:

    • Failed stage
    • Failed test name
    • Failure summary
    • Relevant error logs

    In addition to being automatically triggered, the tool can also be invoked manually by commenting on a PR:

    /checks "https://github.com/{repo_name}/actions/runs/{run_number}/job/{job_number}"
    

    where {repo_name} is the name of the repository, {run_number} is the run number of the failed check, and {job_number} is the job number of the failed check.

    Configuration options

    • enable_auto_checks_feedback - if set to true...

    @Vedantsahai18 Vedantsahai18 linked an issue Jan 8, 2025 that may be closed by this pull request
    1 task
    @Ahmad-mtos Ahmad-mtos marked this pull request as ready for review January 8, 2025 15:13
    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Database Pool Management

    The database pool initialization and cleanup logic has been significantly changed. Need to validate that pools are properly created and closed in both testing and non-testing environments to prevent resource leaks.

    if not pool and not testing:
        pool = await create_db_pool(pg_dsn)
    local_pool = await create_db_pool(pg_dsn) if testing else None
    pools = [pool, local_pool]
    Error Handling

    Complex error handling logic has been added with multiple exception cases. Need to verify that all error scenarios are handled correctly and that transitions to error/cancelled states work as expected.

    match e:
        case CancelledError() | ActivityError(__cause__=CancelledError()):
            workflow.logger.info("Workflow cancelled")
            await transition(context, type="cancelled", output=None)
        case ApplicationError(_non_retryable=True):
            workflow.logger.error(f"Error in step {context.cursor.step}: {e!s}")
            await transition(context, type="error", output=str(e))
        case ActivityError(__cause__=ApplicationError(_non_retryable=True)):
            workflow.logger.error(f"Error in step {context.cursor.step}: {e!s}")
            await transition(context, type="error", output=str(e.__cause__))
    State Transition Logic

    The state transition logic has been modified to handle new error and cancelled states. Need to verify that all state transitions are handled correctly and that the workflow state machine behaves as expected.

    error_type = kwargs.get("type")
    
    if state.type is not None and state.type == "error":
        error_type = "error"
    elif state.type is not None and state.type == "cancelled":
        error_type = "cancelled"
    
    if error_type and error_type == "cancelled":
        state.type = "cancelled"
    elif error_type and error_type == "error":
        state.type = "error"
    else:
        match context.is_last_step, context.cursor:
            case (True, TransitionTarget(workflow="main")):
                state.type = "finish"
            case (True, _):
                state.type = "finish_branch"
            case _, _:
                state.type = "step"

    Copy link
    Contributor

    qodo-merge-pro-for-open-source bot commented Jan 8, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Score
    Possible issue
    Add error handling for database connection initialization to prevent silent failures

    The database pool initialization logic should handle potential connection failures.
    Add error handling around create_db_pool() calls to prevent silent failures and
    ensure proper error propagation.

    agents-api/agents_api/app.py [34-36]

    -if not pool and not testing:
    -    pool = await create_db_pool(pg_dsn)
    -local_pool = await create_db_pool(pg_dsn) if testing else None
    +try:
    +    if not pool and not testing:
    +        pool = await create_db_pool(pg_dsn)
    +    local_pool = await create_db_pool(pg_dsn) if testing else None
    +except Exception as e:
    +    workflow.logger.error(f"Failed to initialize database pool: {e}")
    +    raise
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: Adding proper error handling for database connection initialization is crucial for system reliability and debugging. The suggestion helps prevent silent failures and ensures proper error propagation.

    8
    Preserve error stack traces when handling and re-raising workflow exceptions

    The error handling in the workflow should preserve the original error stack trace.
    Modify the error handling to include the original exception when raising new
    ApplicationErrors.

    agents-api/agents_api/workflows/task_execution/init.py [213-215]

     case ActivityError(__cause__=ApplicationError(_non_retryable=True)):
    -    workflow.logger.error(f"Unhandled error: {e!s}")
    +    workflow.logger.error(f"Unhandled error: {e!s}", exc_info=True)
         await transition(
             context, type="error", output=str(e.__cause__), last_error=self.last_error
         )
    +    raise ApplicationError(f"Activity error: {e.__cause__}") from e
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: Preserving error stack traces is important for debugging and understanding error chains. The suggestion improves error handling by including exc_info and properly chaining exceptions.

    7

    Copy link
    Contributor

    @ellipsis-dev ellipsis-dev bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    👍 Looks good to me! Reviewed everything up to 259a1c4 in 1 minute and 34 seconds

    More details
    • Looked at 1118 lines of code in 6 files
    • Skipped 0 files when reviewing.
    • Skipped posting 5 drafted comments based on config settings.
    1. agents-api/agents_api/activities/execute_integration.py:10
    • Draft comment:
      The import statement for ActivityError and CancelledError is added, but these exceptions are not handled in the execute_integration function. Consider adding exception handling for these specific errors.
    • Reason this comment was not posted:
      Confidence changes required: 50%
      The import statement for ActivityError and CancelledError was added, but the code does not handle these exceptions in the execute_integration function. This could lead to unhandled exceptions if these errors occur.
    2. agents-api/agents_api/activities/execute_system.py:21
    • Draft comment:
      The import statement for ActivityError and CancelledError is added, but these exceptions are not handled in the execute_system function. Consider adding exception handling for these specific errors.
    • Reason this comment was not posted:
      Confidence changes required: 50%
      The import statement for ActivityError and CancelledError was added, but the code does not handle these exceptions in the execute_system function. This could lead to unhandled exceptions if these errors occur.
    3. agents-api/agents_api/app.py:41
    • Draft comment:
      Using testing as an index for pools is risky since it's a boolean. Consider using a more explicit approach to select the correct pool.
    • Reason this comment was not posted:
      Decided after close inspection that this draft comment was likely wrong and/or not actionable:
      In Python, booleans can be used as array indices where False=0 and True=1. While this works, it's not immediately obvious to readers what pools[True] or pools[False] means. However, in this case the code is quite simple and the relationship between testing=True and wanting the local_pool (second element) is clear from the surrounding context.
      The comment raises a valid point about code readability, but the code is actually quite clear in context. The boolean indexing pattern, while unusual, is concise and effective here.
      While using booleans as indices isn't a common pattern, in this specific case it's actually a clever and readable solution given the simple two-pool setup.
      Delete the comment. The code is clear enough as is, and the suggested change would likely make it more verbose without adding significant value.
    4. agents-api/agents_api/app.py:65
    • Draft comment:
      Using testing as an index for pools is risky since it's a boolean. Consider using a more explicit approach to select the correct pool.
    • Reason this comment was not posted:
      Marked as duplicate.
    5. agents-api/agents_api/workflows/task_execution/transition.py:28
    • Draft comment:
      The logic for setting state.type based on error_type can be simplified for better readability. Consider refactoring this section.
    • Reason this comment was not posted:
      Confidence changes required: 30%
      The transition function in transition.py has been modified to handle error and cancelled states more explicitly. However, the logic for setting the state type could be simplified and made more readable.

    Workflow ID: wflow_aUT9pQ0gG4kSgkAH


    You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

    @Vedantsahai18 Vedantsahai18 linked an issue Jan 8, 2025 that may be closed by this pull request
    1 task
    Copy link

    gitguardian bot commented Jan 11, 2025

    ⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

    Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

    🔎 Detected hardcoded secret in your pull request
    GitGuardian id GitGuardian status Secret Commit Filename
    14144715 Triggered Generic Password f1da62f memory-store/docker-compose.yml View secret
    🛠 Guidelines to remediate hardcoded secrets
    1. Understand the implications of revoking this secret by investigating where it is used in your code.
    2. Replace and store your secret safely. Learn here the best practices.
    3. Revoke and rotate this secret.
    4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

    To avoid such incidents in the future consider


    🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    1 participant