Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion ex_app/lib/logger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
import asyncio
import inspect
import logging
import traceback

from nc_py_api import NextcloudException

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[logging.StreamHandler()])
logger = logging.getLogger('context_agent')
Expand All @@ -14,4 +18,46 @@ async def log(nc, level, content):
except asyncio.CancelledError:
raise
except Exception:
pass
pass

MAX_RESPONSE_TEXT_LENGTH = 2000


async def _ocs_response_text(e: NextcloudException) -> str | None:
response = getattr(e, 'response', None)
if response is None:
return None
try:
text = response.text
# niquests' AsyncResponse.text is a coroutine, the sync Response's is a str
if inspect.isawaitable(text):
text = await text
except asyncio.CancelledError:
raise
except Exception:
return None
if not isinstance(text, str):
return None
text = text.strip()
if not text:
return None
if len(text) > MAX_RESPONSE_TEXT_LENGTH:
text = text[:MAX_RESPONSE_TEXT_LENGTH] + '... (truncated)'
return text


async def format_exception(e: BaseException) -> str:
"""Format an exception with its traceback, like traceback.format_exception,
and append the body of any OCS error response in its chain, since
str(NextcloudException) only carries the status code and reason."""
formatted = ''.join(traceback.format_exception(e))
seen = set()
current: BaseException | None = e
while current is not None and id(current) not in seen:
seen.add(id(current))
if isinstance(current, NextcloudException):
text = await _ocs_response_text(current)
if text is not None:
formatted += f'OCS response for [{current.status_code}]: {text}\n'
current = current.__cause__ or current.__context__
return formatted
13 changes: 6 additions & 7 deletions ex_app/lib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
import concurrent.futures
import os
import traceback
from contextlib import asynccontextmanager
from json import JSONDecodeError
from threading import Event
Expand All @@ -23,7 +22,7 @@

from ex_app.lib.agent import react
from ex_app.lib.errors import UserFacingError
from ex_app.lib.logger import log
from ex_app.lib.logger import format_exception, log
from ex_app.lib.mcp_server import UserAuthMiddleware, ToolListMiddleware
from ex_app.lib.provider import provider, multimodal_provider
from ex_app.lib.tools import get_categories
Expand Down Expand Up @@ -207,7 +206,7 @@ async def background_thread_task():
await asyncio.sleep(2)
continue
except (NextcloudException, RequestException, JSONDecodeError) as e:
tb_str = ''.join(traceback.format_exception(e))
tb_str = await format_exception(e)
await log(nc, LogLvl.WARNING, "Error fetching the next task " + tb_str)
await wait_for_task(5)
continue
Expand Down Expand Up @@ -262,17 +261,17 @@ async def stream_output(intermediate_output):
)
except (NextcloudException, RequestException) as stream_err:
stream_update_failed = True
tb_str = ''.join(traceback.format_exception(stream_err))
tb_str = await format_exception(stream_err)
await log(nc, LogLvl.WARNING, "Error streaming intermediate task result: " + tb_str)

output = await react(task, nextcloud, stream_output=stream_output if stream_updates_enabled else None)
except Exception as e: # noqa
try:
tb_str = ''.join(traceback.format_exception(e))
tb_str = await format_exception(e)
await log(nc, LogLvl.ERROR, "Error: " + tb_str)
await report_error(nc, task["id"], e)
except (NextcloudException, RequestException) as net_err:
tb_str = ''.join(traceback.format_exception(net_err))
tb_str = await format_exception(net_err)
await log(nc, LogLvl.WARNING, "Network error in reporting the error: " + tb_str)
finally:
async with NUM_RUNNING_TASKS_LOCK:
Expand All @@ -285,7 +284,7 @@ async def stream_output(intermediate_output):
)
except Exception as e:
try:
tb_str = ''.join(traceback.format_exception(e))
tb_str = await format_exception(e)
await log(nc, LogLvl.ERROR, "Error trying to report the task result: " + tb_str)
except Exception:
pass
Expand Down