logdetective 2.9.0__tar.gz → 2.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {logdetective-2.9.0 → logdetective-2.11.0}/PKG-INFO +3 -3
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/emoji.py +46 -48
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/models.py +8 -41
- {logdetective-2.9.0 → logdetective-2.11.0}/pyproject.toml +3 -3
- {logdetective-2.9.0 → logdetective-2.11.0}/LICENSE +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/README.md +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/__init__.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/constants.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/drain3.ini +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/extractors.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/logdetective.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/models.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/prompts-summary-first.yml +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/prompts-summary-only.yml +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/prompts.yml +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/remote_log.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/__init__.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/compressors.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/config.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/__init__.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/base.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/__init__.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/exceptions.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/koji.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/merge_request_jobs.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/metrics.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/exceptions.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/gitlab.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/koji.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/llm.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/metric.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/plot.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/server.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/base_response.html.j2 +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/gitlab_full_comment.md.j2 +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/gitlab_short_comment.md.j2 +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/utils.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/skip_snippets.yml +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective/utils.py +0 -0
- {logdetective-2.9.0 → logdetective-2.11.0}/logdetective.1.asciidoc +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logdetective
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.0
|
|
4
4
|
Summary: Log using LLM AI to search for build/test failures and provide ideas for fixing these.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -28,12 +28,12 @@ Requires-Dist: aiolimiter (>=1.0.0,<2.0.0) ; extra == "server"
|
|
|
28
28
|
Requires-Dist: aioresponses (>=0.7.8,<0.8.0) ; extra == "testing"
|
|
29
29
|
Requires-Dist: alembic (>=1.13.3,<2.0.0) ; extra == "server" or extra == "server-testing"
|
|
30
30
|
Requires-Dist: asciidoc[testing] (>=10.2.1,<11.0.0) ; extra == "testing"
|
|
31
|
-
Requires-Dist: asyncpg (>=0.30.0,<0.
|
|
31
|
+
Requires-Dist: asyncpg (>=0.30.0,<1.0.0) ; extra == "server" or extra == "server-testing"
|
|
32
32
|
Requires-Dist: backoff (==2.2.1) ; extra == "server" or extra == "server-testing"
|
|
33
33
|
Requires-Dist: drain3 (>=0.9.11,<0.10.0)
|
|
34
34
|
Requires-Dist: fastapi (>=0.111.1,<1.0.0) ; extra == "server" or extra == "server-testing"
|
|
35
35
|
Requires-Dist: flexmock (>=0.12.2,<0.13.0) ; extra == "testing"
|
|
36
|
-
Requires-Dist: huggingface-hub (
|
|
36
|
+
Requires-Dist: huggingface-hub (>=0.23.0,<1.4.0)
|
|
37
37
|
Requires-Dist: koji (>=1.35.0,<2.0.0) ; extra == "server" or extra == "server-testing"
|
|
38
38
|
Requires-Dist: llama-cpp-python (>0.2.56,!=0.2.86,<1.0.0)
|
|
39
39
|
Requires-Dist: matplotlib (>=3.8.4,<4.0.0) ; extra == "server" or extra == "server-testing"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List
|
|
4
4
|
from collections import Counter
|
|
5
5
|
|
|
6
6
|
import gitlab
|
|
@@ -49,25 +49,6 @@ async def collect_emojis_for_mr(
|
|
|
49
49
|
await collect_emojis_in_comments(comments, gitlab_conn)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def _handle_gitlab_operation(func: Callable, *args):
|
|
53
|
-
"""
|
|
54
|
-
It handles errors for the specified GitLab operation.
|
|
55
|
-
After executing it in a separate thread.
|
|
56
|
-
"""
|
|
57
|
-
try:
|
|
58
|
-
return await asyncio.to_thread(func, *args)
|
|
59
|
-
except (gitlab.GitlabError, gitlab.GitlabGetError) as e:
|
|
60
|
-
log_msg = f"Error during GitLab operation {func}{args}: {e}"
|
|
61
|
-
if "Not Found" in str(e):
|
|
62
|
-
LOG.error(log_msg)
|
|
63
|
-
else:
|
|
64
|
-
LOG.exception(log_msg)
|
|
65
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
|
66
|
-
LOG.exception(
|
|
67
|
-
"Unexpected error during GitLab operation %s(%s): %s", func, args, e
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
52
|
async def collect_emojis_in_comments( # pylint: disable=too-many-locals
|
|
72
53
|
comments: List[Comments], gitlab_conn: gitlab.Gitlab
|
|
73
54
|
):
|
|
@@ -80,37 +61,54 @@ async def collect_emojis_in_comments( # pylint: disable=too-many-locals
|
|
|
80
61
|
mr_job_db = await GitlabMergeRequestJobs.get_by_id(comment.merge_request_job_id)
|
|
81
62
|
if not mr_job_db:
|
|
82
63
|
continue
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
64
|
+
try:
|
|
65
|
+
if mr_job_db.id not in projects:
|
|
66
|
+
project = await asyncio.to_thread(
|
|
67
|
+
gitlab_conn.projects.get, mr_job_db.project_id
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
projects[mr_job_db.id] = project
|
|
71
|
+
else:
|
|
72
|
+
project = projects[mr_job_db.id]
|
|
73
|
+
merge_request_iid = mr_job_db.mr_iid
|
|
74
|
+
if merge_request_iid not in merge_requests:
|
|
75
|
+
merge_request = await asyncio.to_thread(
|
|
76
|
+
project.mergerequests.get, merge_request_iid
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
merge_requests[merge_request_iid] = merge_request
|
|
80
|
+
else:
|
|
81
|
+
merge_request = merge_requests[merge_request_iid]
|
|
82
|
+
|
|
83
|
+
discussion = await asyncio.to_thread(
|
|
84
|
+
merge_request.discussions.get, comment.comment_id
|
|
96
85
|
)
|
|
97
|
-
if not merge_request:
|
|
98
|
-
continue
|
|
99
|
-
merge_requests[merge_request_iid] = merge_request
|
|
100
|
-
else:
|
|
101
|
-
merge_request = merge_requests[merge_request_iid]
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
# Get the ID of the first note
|
|
88
|
+
if "notes" not in discussion.attributes or len(discussion.attributes["notes"]) == 0:
|
|
89
|
+
LOG.warning(
|
|
90
|
+
"No notes were found in comment %s in merge request %d",
|
|
91
|
+
comment.comment_id,
|
|
92
|
+
merge_request_iid,
|
|
93
|
+
)
|
|
94
|
+
continue
|
|
108
95
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
note_id = discussion.attributes["notes"][0]["id"]
|
|
97
|
+
note = await asyncio.to_thread(merge_request.notes.get, note_id)
|
|
98
|
+
|
|
99
|
+
# Log warning with full stack trace, in case we can't find the right
|
|
100
|
+
# discussion, merge request or project.
|
|
101
|
+
# All of these objects can be lost, and we shouldn't treat as an error.
|
|
102
|
+
# Other exceptions are raised.
|
|
103
|
+
except gitlab.GitlabError as e:
|
|
104
|
+
if e.response_code == 404:
|
|
105
|
+
LOG.warning(
|
|
106
|
+
"Couldn't retrieve emoji counts for comment %s due to GitlabError",
|
|
107
|
+
comment.comment_id, exc_info=True)
|
|
108
|
+
continue
|
|
109
|
+
LOG.error("Error encountered while processing emoji counts for GitLab comment %s",
|
|
110
|
+
comment.comment_id, exc_info=True)
|
|
111
|
+
raise
|
|
114
112
|
|
|
115
113
|
emoji_counts = Counter(emoji.name for emoji in note.awardemojis.list())
|
|
116
114
|
|
|
@@ -10,6 +10,7 @@ from pydantic import (
|
|
|
10
10
|
field_validator,
|
|
11
11
|
NonNegativeFloat,
|
|
12
12
|
HttpUrl,
|
|
13
|
+
PrivateAttr,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
import aiohttp
|
|
@@ -183,8 +184,8 @@ class InferenceConfig(BaseModel): # pylint: disable=too-many-instance-attribute
|
|
|
183
184
|
user_role: str = USER_ROLE_DEFAULT
|
|
184
185
|
system_role: str = SYSTEM_ROLE_DEFAULT
|
|
185
186
|
llm_api_timeout: float = 15.0
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
_limiter: AsyncLimiter = PrivateAttr(
|
|
188
|
+
default_factory=lambda: AsyncLimiter(LLM_DEFAULT_REQUESTS_PER_MINUTE))
|
|
188
189
|
|
|
189
190
|
def __init__(self, data: Optional[dict] = None):
|
|
190
191
|
super().__init__()
|
|
@@ -207,40 +208,6 @@ class InferenceConfig(BaseModel): # pylint: disable=too-many-instance-attribute
|
|
|
207
208
|
self.llm_api_timeout = data.get("llm_api_timeout", 15.0)
|
|
208
209
|
self._limiter = AsyncLimiter(self._requests_per_minute)
|
|
209
210
|
|
|
210
|
-
def __del__(self):
|
|
211
|
-
# Close connection when this object is destroyed
|
|
212
|
-
if self._http_session:
|
|
213
|
-
try:
|
|
214
|
-
loop = asyncio.get_running_loop()
|
|
215
|
-
loop.create_task(self._http_session.close())
|
|
216
|
-
except RuntimeError:
|
|
217
|
-
# No loop running, so create one to close the session
|
|
218
|
-
loop = asyncio.new_event_loop()
|
|
219
|
-
loop.run_until_complete(self._http_session.close())
|
|
220
|
-
loop.close()
|
|
221
|
-
except Exception: # pylint: disable=broad-exception-caught
|
|
222
|
-
# We should only get here if we're shutting down, so we don't
|
|
223
|
-
# really care if the close() completes cleanly.
|
|
224
|
-
pass
|
|
225
|
-
|
|
226
|
-
def get_http_session(self):
|
|
227
|
-
"""Return the internal HTTP session so it can be used to contect the
|
|
228
|
-
LLM server. May be used as a context manager."""
|
|
229
|
-
|
|
230
|
-
# Create the session on the first attempt. We need to do this "lazily"
|
|
231
|
-
# because it needs to happen once the event loop is running, even
|
|
232
|
-
# though the initialization itself is synchronous.
|
|
233
|
-
if not self._http_session:
|
|
234
|
-
self._http_session = aiohttp.ClientSession(
|
|
235
|
-
base_url=self.url,
|
|
236
|
-
timeout=aiohttp.ClientTimeout(
|
|
237
|
-
total=self.http_timeout,
|
|
238
|
-
connect=3.07,
|
|
239
|
-
),
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
return self._http_session
|
|
243
|
-
|
|
244
211
|
def get_limiter(self):
|
|
245
212
|
"""Return the limiter object so it can be used as a context manager"""
|
|
246
213
|
return self._limiter
|
|
@@ -254,7 +221,7 @@ class ExtractorConfig(BaseModel):
|
|
|
254
221
|
max_snippet_len: int = 2000
|
|
255
222
|
csgrep: bool = False
|
|
256
223
|
|
|
257
|
-
_extractors: List[Extractor] =
|
|
224
|
+
_extractors: List[Extractor] = PrivateAttr(default_factory=list)
|
|
258
225
|
|
|
259
226
|
def _setup_extractors(self):
|
|
260
227
|
"""Initialize extractors with common settings."""
|
|
@@ -322,8 +289,8 @@ class GitLabInstanceConfig(BaseModel): # pylint: disable=too-many-instance-attr
|
|
|
322
289
|
webhook_secrets: Optional[List[str]] = None
|
|
323
290
|
|
|
324
291
|
timeout: float = 5.0
|
|
325
|
-
_conn: Gitlab = None
|
|
326
|
-
_http_session: aiohttp.ClientSession = None
|
|
292
|
+
_conn: Gitlab | None = PrivateAttr(default=None)
|
|
293
|
+
_http_session: aiohttp.ClientSession | None = PrivateAttr(default=None)
|
|
327
294
|
|
|
328
295
|
# Maximum size of artifacts.zip in MiB. (default: 300 MiB)
|
|
329
296
|
max_artifact_size: int = 300 * 1024 * 1024
|
|
@@ -409,8 +376,8 @@ class KojiInstanceConfig(BaseModel):
|
|
|
409
376
|
xmlrpc_url: str = ""
|
|
410
377
|
tokens: List[str] = []
|
|
411
378
|
|
|
412
|
-
_conn: Optional[koji.ClientSession] = None
|
|
413
|
-
_callbacks: defaultdict[int, set[str]] = defaultdict(set)
|
|
379
|
+
_conn: Optional[koji.ClientSession] = PrivateAttr(default=None)
|
|
380
|
+
_callbacks: defaultdict[int, set[str]] = PrivateAttr(default_factory=lambda: defaultdict(set))
|
|
414
381
|
|
|
415
382
|
def __init__(self, name: str, data: Optional[dict] = None):
|
|
416
383
|
super().__init__()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "logdetective"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.11.0"
|
|
4
4
|
description = "Log using LLM AI to search for build/test failures and provide ideas for fixing these."
|
|
5
5
|
authors = ["Jiri Podivin <jpodivin@gmail.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -34,7 +34,7 @@ issues = "https://github.com/fedora-copr/logdetective/issues"
|
|
|
34
34
|
python = "^3.11"
|
|
35
35
|
llama-cpp-python = ">0.2.56,!=0.2.86,<1.0.0"
|
|
36
36
|
drain3 = ">=0.9.11,<0.10.0"
|
|
37
|
-
huggingface-hub = "
|
|
37
|
+
huggingface-hub = ">=0.23.0,<1.4.0"
|
|
38
38
|
# rawhide has numpy 2, F40 and F41 are still on 1.26
|
|
39
39
|
# we need to support both versions
|
|
40
40
|
numpy = ">=1.26.0"
|
|
@@ -59,7 +59,7 @@ pytest-mock = {version = ">=3.14.1,<4.0.0", optional = true}
|
|
|
59
59
|
flexmock = {version = ">=0.12.2,<0.13.0", optional = true}
|
|
60
60
|
pytest = {version = ">=8.4.1,<9.0.0", optional = true}
|
|
61
61
|
pytest-cov = {version = "^7.0.0", extras = ["testing"], optional = true}
|
|
62
|
-
asyncpg = {version = "
|
|
62
|
+
asyncpg = {version = ">=0.30.0,<1.0.0", optional = true}
|
|
63
63
|
asciidoc = {version = "^10.2.1", optional = true, extras = ["testing"]}
|
|
64
64
|
|
|
65
65
|
[tool.poetry.extras]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/database/models/merge_request_jobs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/base_response.html.j2
RENAMED
|
File without changes
|
{logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/gitlab_full_comment.md.j2
RENAMED
|
File without changes
|
{logdetective-2.9.0 → logdetective-2.11.0}/logdetective/server/templates/gitlab_short_comment.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|