diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 6ec949a..f93597e 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -28,7 +28,7 @@ jobs: matrix: php-versions: [ '8.2' ] databases: [ 'sqlite' ] - server-versions: [ 'master', 'stable32', 'stable31' ] + server-versions: [ 'master', 'stable33', 'stable32', 'stable31' ] name: Integration test on ☁️${{ matrix.server-versions }} 🐘${{ matrix.php-versions }} @@ -198,7 +198,7 @@ jobs: CREDS: "alice:alice" run: | sleep 300 - TASK=$(curl -X POST -u "$CREDS" -H "oCS-APIRequest: true" -H "Content-type: application/json" http://localhost:8080/ocs/v2.php/taskprocessing/schedule?format=json --data-raw '{"input": {"input": "Search duckduckgo for Nextcloud", "confirmation":1, "conversation_token": ""},"type":"core:contextagent:interaction", "appId": "test", "customId": ""}') + TASK=$(curl -X POST -u "$CREDS" -H "oCS-APIRequest: true" -H "Content-type: application/json" http://localhost:8080/ocs/v2.php/taskprocessing/schedule?format=json --data-raw '{"input": {"input": "Search youtube for videos about Nextcloud", "confirmation":1, "conversation_token": ""},"type":"core:contextagent:interaction", "appId": "test", "customId": ""}') echo $TASK TASK_ID=$(echo $TASK | jq '.ocs.data.task.id') NEXT_WAIT_TIME=0 @@ -215,7 +215,7 @@ jobs: [ "$TASK_STATUS" == '"STATUS_SUCCESSFUL"' ] echo $TASK | jq '.ocs.data.task.output.output' echo $TASK | jq '.ocs.data.task.output.sources' - echo $TASK | jq '.ocs.data.task.output.sources' | grep -q 'duckduckgo_results_json' + echo $TASK | jq '.ocs.data.task.output.sources' | grep -q 'youtube_search' echo $TASK | jq '.ocs.data.task.output.output' | grep -q 'Nextcloud' - name: Show nextcloud logs diff --git a/appinfo/info.xml b/appinfo/info.xml index 97a93a7..9bb160a 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -39,7 +39,7 @@ Positive: https://github.com/nextcloud/context_agent/blob/main/img/screenshot.png?raw=true https://github.com/nextcloud/context_agent - + diff --git a/ex_app/lib/all_tools/calendar.py b/ex_app/lib/all_tools/calendar.py index 6ecb1ec..301997e 100644 --- a/ex_app/lib/all_tools/calendar.py +++ b/ex_app/lib/all_tools/calendar.py @@ -4,7 +4,7 @@ from time import sleep from typing import Optional -import httpx +from niquests import ConnectionError, Timeout import pytz from ics import Calendar, Event, Attendee, Organizer, Todo from langchain_core.tools import tool @@ -91,10 +91,8 @@ def schedule_event(calendar_name: str, title: str, description: str, start_date: json = nc.ocs('GET', '/ocs/v2.php/cloud/user') break except ( - httpx.RemoteProtocolError, - httpx.ReadError, - httpx.LocalProtocolError, - httpx.PoolTimeout, + ConnectionError, + Timeout ) as e: log(nc, LogLvl.DEBUG, "Ignored error during task polling") i += 1 diff --git a/ex_app/lib/all_tools/deck.py b/ex_app/lib/all_tools/deck.py index 4c26b91..cba72bf 100644 --- a/ex_app/lib/all_tools/deck.py +++ b/ex_app/lib/all_tools/deck.py @@ -4,7 +4,6 @@ from time import sleep from typing import Optional -import httpx import pytz from langchain_core.tools import tool from nc_py_api import Nextcloud diff --git a/ex_app/lib/all_tools/here.py b/ex_app/lib/all_tools/here.py index 887ff1a..be44df8 100644 --- a/ex_app/lib/all_tools/here.py +++ b/ex_app/lib/all_tools/here.py @@ -4,7 +4,7 @@ import datetime import urllib.parse -import httpx +import niquests from langchain_core.tools import tool from nc_py_api import Nextcloud @@ -30,7 +30,7 @@ def get_public_transport_route_for_coordinates(origin_lat: str, origin_lon: str, if departure_time is None: departure_time = urllib.parse.quote_plus(datetime.datetime.now(datetime.UTC).isoformat()) api_key = nc.appconfig_ex.get_value('here_api') - res = httpx.get('https://transit.hereapi.com/v8/routes?transportMode=car&origin=' + res = niquests.get('https://transit.hereapi.com/v8/routes?transportMode=car&origin=' + origin_lat + ',' + origin_lon + '&destination=' + destination_lat + ',' + destination_lon + '&alternatives=' + str(routes-1) + '&departureTime=' + departure_time + '&apikey=' + api_key) json = res.json() diff --git a/ex_app/lib/all_tools/lib/task_processing.py b/ex_app/lib/all_tools/lib/task_processing.py index 770eef6..59e226c 100644 --- a/ex_app/lib/all_tools/lib/task_processing.py +++ b/ex_app/lib/all_tools/lib/task_processing.py @@ -3,7 +3,7 @@ import time import typing -import httpx +from niquests import ConnectionError, Timeout from nc_py_api import NextcloudException from nc_py_api.ex_app import LogLvl from pydantic import BaseModel, ValidationError @@ -20,11 +20,24 @@ class Response(BaseModel): task: Task def run_task(nc, type, task_input): - response = nc.ocs( - "POST", - "/ocs/v1.php/taskprocessing/schedule", - json={"type": type, "appId": "context_agent", "input": task_input}, - ) + i = 0 + while i < 20: + try: + response = nc.ocs( + "POST", + "/ocs/v1.php/taskprocessing/schedule", + json={"type": type, "appId": "context_agent", "input": task_input}, + ) + break + except ( + ConnectionError, + Timeout + + ) as e: + log(nc, LogLvl.DEBUG, "Ignored error during task scheduling") + i += 1 + sleep(1) + continue try: task = Response.model_validate(response).task @@ -38,10 +51,8 @@ def run_task(nc, type, task_input): try: response = nc.ocs("GET", f"/ocs/v1.php/taskprocessing/task/{task.id}") except ( - httpx.RemoteProtocolError, - httpx.ReadError, - httpx.LocalProtocolError, - httpx.PoolTimeout, + ConnectionError, + Timeout ) as e: log(nc, LogLvl.DEBUG, "Ignored error during task polling") time.sleep(5) diff --git a/ex_app/lib/all_tools/mail.py b/ex_app/lib/all_tools/mail.py index 38a99a2..c37109e 100644 --- a/ex_app/lib/all_tools/mail.py +++ b/ex_app/lib/all_tools/mail.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later from time import sleep -import httpx +from niquests import ConnectionError, Timeout from langchain_core.tools import tool from nc_py_api import Nextcloud from nc_py_api.ex_app import LogLvl @@ -35,10 +35,8 @@ def send_email(subject: str, body: str, account_id: int, from_email: str, to_ema 'to': [{'label': '', 'email': email} for email in to_emails], }) except ( - httpx.RemoteProtocolError, - httpx.ReadError, - httpx.LocalProtocolError, - httpx.PoolTimeout, + ConnectionError, + Timeout ) as e: log(nc, LogLvl.DEBUG, "Ignored error during task polling") i += 1 diff --git a/ex_app/lib/all_tools/openstreetmap.py b/ex_app/lib/all_tools/openstreetmap.py index 4c910a3..028ac24 100644 --- a/ex_app/lib/all_tools/openstreetmap.py +++ b/ex_app/lib/all_tools/openstreetmap.py @@ -6,7 +6,7 @@ import urllib.parse from time import sleep -import httpx +import niquests from langchain_core.tools import tool from nc_py_api import Nextcloud @@ -22,7 +22,7 @@ def get_coordinates_for_address(address: str) -> (str, str): :param address: the address to calculate the coordinates for :return: a tuple of latitude and longitude """ - res = httpx.get('https://nominatim.openstreetmap.org/search', params={'q': address, 'format': 'json', 'addressdetails': '1', 'extratags': '1', 'namedetails': '1', 'limit': '1'}) + res = niquests.get('https://nominatim.openstreetmap.org/search', params={'q': address, 'format': 'json', 'addressdetails': '1', 'extratags': '1', 'namedetails': '1', 'limit': '1'}) json = res.json() if 'error' in json: raise Exception(json['error']) @@ -56,7 +56,7 @@ def get_osm_route(profile: str, origin_lat: str, origin_lon: str, destination_la profile_num = "2" url = f'https://routing.openstreetmap.de/{profile}/route/v1/driving/{origin_lon},{origin_lat};{destination_lon},{destination_lat}?overview=false&steps=true' map_url = f' https://routing.openstreetmap.de/?loc={origin_lat}%2C{origin_lon}&loc={destination_lat}%2C{destination_lon}&srv={profile_num}' - res = httpx.get(url) + res = niquests.get(url) json = res.json() return {'route_json_description': json, 'map_url': map_url} @@ -70,7 +70,7 @@ def get_osm_link(location: str): :return: URL """ - res = httpx.get('https://nominatim.openstreetmap.org/search', params={'q': location, 'format': 'json','limit': '1'}) + res = niquests.get('https://nominatim.openstreetmap.org/search', params={'q': location, 'format': 'json','limit': '1'}) json = res.json() if 'error' in json: raise Exception(json['error']) diff --git a/ex_app/lib/all_tools/weather.py b/ex_app/lib/all_tools/weather.py index 7bc59fa..db420ae 100644 --- a/ex_app/lib/all_tools/weather.py +++ b/ex_app/lib/all_tools/weather.py @@ -3,7 +3,7 @@ import typing import urllib.parse -import httpx +import niquests from langchain_core.tools import tool from nc_py_api import Nextcloud @@ -20,7 +20,7 @@ def get_current_weather_for_coordinates(lat: str, lon: str) -> dict[str, typing. :param lon: Longitude :return: """ - res = httpx.get('https://api.met.no/weatherapi/locationforecast/2.0/compact', params={ + res = niquests.get('https://api.met.no/weatherapi/locationforecast/2.0/compact', params={ 'lat': lat, 'lon': lon, }, diff --git a/ex_app/lib/nc_model.py b/ex_app/lib/nc_model.py index c917654..cf2d6cb 100644 --- a/ex_app/lib/nc_model.py +++ b/ex_app/lib/nc_model.py @@ -5,7 +5,7 @@ import typing from typing import Optional, Any, Sequence, Union, Callable -import httpx +from niquests import ConnectionError, Timeout from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.language_models import LanguageModelInput from langchain_core.messages import BaseMessage, AIMessage @@ -90,11 +90,24 @@ def _generate( log(nc, LogLvl.DEBUG, task_input) - response = nc.ocs( - "POST", - "/ocs/v1.php/taskprocessing/schedule", - json={"type": "core:text2text:chatwithtools", "appId": "context_agent", "input": task_input}, - ) + i = 0 + while i < 20: + try: + response = nc.ocs( + "POST", + "/ocs/v1.php/taskprocessing/schedule", + json={"type": "core:text2text:chatwithtools", "appId": "context_agent", "input": task_input}, + ) + break + except ( + ConnectionError, + Timeout + + ) as e: + log(nc, LogLvl.DEBUG, "Ignored error during task scheduling") + i += 1 + sleep(1) + continue try: task = Response.model_validate(response).task @@ -109,10 +122,9 @@ def _generate( try: response = nc.ocs("GET", f"/ocs/v1.php/taskprocessing/task/{task.id}") except ( - httpx.RemoteProtocolError, - httpx.ReadError, - httpx.LocalProtocolError, - httpx.PoolTimeout, + ConnectionError, + Timeout + ) as e: log(nc, LogLvl.DEBUG, "Ignored error during task polling") time.sleep(5) diff --git a/poetry.lock b/poetry.lock index 261baa1..0898c53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2110,14 +2110,14 @@ docs = ["autodoc-pydantic (>=2.0.1)", "caldav (==1.3.6)", "sphinx (<8)", "sphinx [[package]] name = "niquests" -version = "3.15.2" +version = "3.17.0" description = "Niquests is a simple, yet elegant, HTTP library. It is a drop-in replacement for Requests, which is under feature freeze." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "niquests-3.15.2-py3-none-any.whl", hash = "sha256:2446e3602ba1418434822f5c1fcf8b8d1b52a3c296d2808a1ab7de4cf1312d99"}, - {file = "niquests-3.15.2.tar.gz", hash = "sha256:8076b1d2ff957022d52b2216ca7df92d92ce426d19a7ed63c7fd4fd630ab6c2b"}, + {file = "niquests-3.17.0-py3-none-any.whl", hash = "sha256:3930d94fce367385950dd545f913e7cfc6457feda76aecafeb324aae45da9fe1"}, + {file = "niquests-3.17.0.tar.gz", hash = "sha256:1f4f337a973215c76f6f6471504fedab9dc6187203284146081e1bd3d2a311fc"}, ] [package.dependencies] @@ -4257,4 +4257,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "dece81e5f1b7f2d561d0c3b2478eeb5e2032d1b6157c37b9d6aa11a0a3fc7ec3" +content-hash = "f5266cfc8797e210c471b6c96ba26325608a9e1ac1930dcbcdab8030d888f760" diff --git a/pyproject.toml b/pyproject.toml index dabc422..8e90793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ duckduckgo-search = "^8.0.1" uvicorn = "^0.34.2" langchain-mcp-adapters = "^0.1.9" fastmcp = "^2.11.2" +niquests = "^3.17.0" [build-system] requires = ["poetry-core"]