logdetective 0.2.8__py3-none-any.whl → 0.2.10__py3-none-any.whl
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/constants.py +20 -3
- logdetective/extractors.py +1 -1
- logdetective/logdetective.py +1 -0
- logdetective/server.py +29 -17
- logdetective/utils.py +2 -0
- {logdetective-0.2.8.dist-info → logdetective-0.2.10.dist-info}/METADATA +3 -3
- logdetective-0.2.10.dist-info/RECORD +12 -0
- {logdetective-0.2.8.dist-info → logdetective-0.2.10.dist-info}/WHEEL +1 -1
- logdetective-0.2.8.dist-info/RECORD +0 -12
- {logdetective-0.2.8.dist-info → logdetective-0.2.10.dist-info}/LICENSE +0 -0
- {logdetective-0.2.8.dist-info → logdetective-0.2.10.dist-info}/entry_points.txt +0 -0
logdetective/constants.py
CHANGED
|
@@ -32,9 +32,7 @@ Answer:
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
SNIPPET_PROMPT_TEMPLATE = """
|
|
35
|
-
Analyse following RPM build log snippet.
|
|
36
|
-
Analysis of the snippets must be in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
|
|
37
|
-
Snippets themselves must not be altered in any way whatsoever.
|
|
35
|
+
Analyse following RPM build log snippet. Decribe contents accurately, without speculation or suggestions for resolution.
|
|
38
36
|
|
|
39
37
|
Snippet:
|
|
40
38
|
|
|
@@ -43,3 +41,22 @@ Snippet:
|
|
|
43
41
|
Analysis:
|
|
44
42
|
|
|
45
43
|
"""
|
|
44
|
+
|
|
45
|
+
PROMPT_TEMPLATE_STAGED = """
|
|
46
|
+
Given following log snippets, their explanation, and nothing else, explain what failure, if any, occured during build of this package.
|
|
47
|
+
|
|
48
|
+
Snippets are in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
|
|
49
|
+
|
|
50
|
+
Snippets are delimited with '================'.
|
|
51
|
+
|
|
52
|
+
Drawing on information from all snippets, provide complete explanation of the issue and recommend solution.
|
|
53
|
+
|
|
54
|
+
Snippets:
|
|
55
|
+
|
|
56
|
+
{}
|
|
57
|
+
|
|
58
|
+
Analysis:
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
SNIPPET_DELIMITER = '================'
|
logdetective/extractors.py
CHANGED
|
@@ -16,7 +16,7 @@ class LLMExtractor:
|
|
|
16
16
|
A class that extracts relevant information from logs using a language model.
|
|
17
17
|
"""
|
|
18
18
|
def __init__(self, model: Llama, n_lines: int = 2):
|
|
19
|
-
self.model =
|
|
19
|
+
self.model = model
|
|
20
20
|
self.n_lines = n_lines
|
|
21
21
|
self.grammar = LlamaGrammar.from_string(
|
|
22
22
|
"root ::= (\"Yes\" | \"No\")", verbose=False)
|
logdetective/logdetective.py
CHANGED
logdetective/server.py
CHANGED
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
-
from typing import List, Annotated
|
|
5
|
+
from typing import List, Annotated, Dict
|
|
6
6
|
|
|
7
7
|
from llama_cpp import CreateCompletionResponse
|
|
8
8
|
from fastapi import FastAPI, HTTPException, Depends, Header
|
|
@@ -10,10 +10,13 @@ from fastapi.responses import StreamingResponse
|
|
|
10
10
|
from pydantic import BaseModel
|
|
11
11
|
import requests
|
|
12
12
|
|
|
13
|
-
from logdetective.constants import
|
|
13
|
+
from logdetective.constants import (
|
|
14
|
+
PROMPT_TEMPLATE, SNIPPET_PROMPT_TEMPLATE,
|
|
15
|
+
PROMPT_TEMPLATE_STAGED, SNIPPET_DELIMITER)
|
|
14
16
|
from logdetective.extractors import DrainExtractor
|
|
15
17
|
from logdetective.utils import validate_url, compute_certainty
|
|
16
18
|
|
|
19
|
+
|
|
17
20
|
class BuildLog(BaseModel):
|
|
18
21
|
"""Model of data submitted to API.
|
|
19
22
|
"""
|
|
@@ -38,14 +41,13 @@ class StagedResponse(Response):
|
|
|
38
41
|
explanation: CreateCompletionResponse
|
|
39
42
|
https://llama-cpp-python.readthedocs.io/en/latest/api-reference/#llama_cpp.llama_types.CreateCompletionResponse
|
|
40
43
|
response_certainty: float
|
|
41
|
-
snippets:
|
|
44
|
+
snippets:
|
|
45
|
+
list of dictionaries { 'snippet' : '<original_text>, 'comment': CreateCompletionResponse }
|
|
42
46
|
"""
|
|
43
|
-
snippets: List[CreateCompletionResponse]
|
|
44
|
-
|
|
47
|
+
snippets: List[Dict[str, str | CreateCompletionResponse]]
|
|
45
48
|
|
|
46
49
|
LOG = logging.getLogger("logdetective")
|
|
47
50
|
|
|
48
|
-
|
|
49
51
|
LLM_CPP_HOST = os.environ.get("LLAMA_CPP_HOST", "localhost")
|
|
50
52
|
LLM_CPP_SERVER_ADDRESS = f"http://{LLM_CPP_HOST}"
|
|
51
53
|
LLM_CPP_SERVER_PORT = os.environ.get("LLAMA_CPP_SERVER_PORT", 8000)
|
|
@@ -53,6 +55,7 @@ LLM_CPP_SERVER_TIMEOUT = os.environ.get("LLAMA_CPP_SERVER_TIMEOUT", 600)
|
|
|
53
55
|
LOG_SOURCE_REQUEST_TIMEOUT = os.environ.get("LOG_SOURCE_REQUEST_TIMEOUT", 60)
|
|
54
56
|
API_TOKEN = os.environ.get("LOGDETECTIVE_TOKEN", None)
|
|
55
57
|
|
|
58
|
+
|
|
56
59
|
def requires_token_when_set(authentication: Annotated[str | None, Header()] = None):
|
|
57
60
|
"""
|
|
58
61
|
FastAPI Depend function that expects a header named Authentication
|
|
@@ -80,6 +83,7 @@ def requires_token_when_set(authentication: Annotated[str | None, Header()] = No
|
|
|
80
83
|
API_TOKEN, token)
|
|
81
84
|
raise HTTPException(status_code=401, detail=f"Token {token} not valid.")
|
|
82
85
|
|
|
86
|
+
|
|
83
87
|
app = FastAPI(dependencies=[Depends(requires_token_when_set)])
|
|
84
88
|
|
|
85
89
|
|
|
@@ -97,7 +101,7 @@ def process_url(url: str) -> str:
|
|
|
97
101
|
if not log_request.ok:
|
|
98
102
|
raise HTTPException(status_code=400,
|
|
99
103
|
detail="Something went wrong while getting the logs: "
|
|
100
|
-
|
|
104
|
+
f"[{log_request.status_code}] {log_request.text}")
|
|
101
105
|
else:
|
|
102
106
|
LOG.error("Invalid URL received ")
|
|
103
107
|
raise HTTPException(status_code=400,
|
|
@@ -118,9 +122,9 @@ def mine_logs(log: str) -> List[str]:
|
|
|
118
122
|
LOG.debug("Log summary: \n %s", log_summary)
|
|
119
123
|
LOG.info("Compression ratio: %s", ratio)
|
|
120
124
|
|
|
121
|
-
|
|
122
125
|
return log_summary
|
|
123
126
|
|
|
127
|
+
|
|
124
128
|
async def submit_text(text: str, max_tokens: int = 0, log_probs: int = 1, stream: bool = False,
|
|
125
129
|
model: str = "default-model"):
|
|
126
130
|
"""Submit prompt to LLM.
|
|
@@ -129,17 +133,17 @@ async def submit_text(text: str, max_tokens: int = 0, log_probs: int = 1, stream
|
|
|
129
133
|
"""
|
|
130
134
|
LOG.info("Analyzing the text")
|
|
131
135
|
data = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
"prompt": text,
|
|
137
|
+
"max_tokens": str(max_tokens),
|
|
138
|
+
"logprobs": str(log_probs),
|
|
139
|
+
"stream": stream,
|
|
140
|
+
"model": model}
|
|
137
141
|
|
|
138
142
|
try:
|
|
139
143
|
# Expects llama-cpp server to run on LLM_CPP_SERVER_ADDRESS:LLM_CPP_SERVER_PORT
|
|
140
144
|
response = requests.post(
|
|
141
145
|
f"{LLM_CPP_SERVER_ADDRESS}:{LLM_CPP_SERVER_PORT}/v1/completions",
|
|
142
|
-
headers={"Content-Type":"application/json"},
|
|
146
|
+
headers={"Content-Type": "application/json"},
|
|
143
147
|
data=json.dumps(data),
|
|
144
148
|
timeout=int(LLM_CPP_SERVER_TIMEOUT),
|
|
145
149
|
stream=stream)
|
|
@@ -152,7 +156,7 @@ async def submit_text(text: str, max_tokens: int = 0, log_probs: int = 1, stream
|
|
|
152
156
|
raise HTTPException(
|
|
153
157
|
status_code=400,
|
|
154
158
|
detail="Something went wrong while getting a response from the llama server: "
|
|
155
|
-
|
|
159
|
+
f"[{response.status_code}] {response.text}")
|
|
156
160
|
try:
|
|
157
161
|
response = json.loads(response.text)
|
|
158
162
|
except UnicodeDecodeError as ex:
|
|
@@ -208,10 +212,18 @@ async def analyze_log_staged(build_log: BuildLog):
|
|
|
208
212
|
analyzed_snippets = await asyncio.gather(
|
|
209
213
|
*[submit_text(SNIPPET_PROMPT_TEMPLATE.format(s)) for s in log_summary])
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
|
|
215
|
+
analyzed_snippets = [
|
|
216
|
+
{"snippet":e[0], "comment":e[1]} for e in zip(log_summary, analyzed_snippets)]
|
|
217
|
+
|
|
218
|
+
final_prompt = PROMPT_TEMPLATE_STAGED.format(
|
|
219
|
+
f"\n{SNIPPET_DELIMITER}\n".join([
|
|
220
|
+
f"[{e["snippet"]}] : [{e["comment"]["choices"][0]["text"]}]"
|
|
221
|
+
for e in analyzed_snippets]))
|
|
222
|
+
|
|
223
|
+
final_analysis = await submit_text(final_prompt)
|
|
213
224
|
|
|
214
225
|
certainty = 0
|
|
226
|
+
|
|
215
227
|
if "logprobs" in final_analysis["choices"][0]:
|
|
216
228
|
try:
|
|
217
229
|
certainty = compute_certainty(
|
logdetective/utils.py
CHANGED
|
@@ -80,6 +80,8 @@ def compute_certainty(probs: List[Dict[str, float] | None]) -> float:
|
|
|
80
80
|
In this case it's just a matter of applying inverse operation exp.
|
|
81
81
|
Of course that leaves you with a value in range <0, 1> so it needs to be multiplied by 100.
|
|
82
82
|
Simply put, this is the most straightforward way to get the numbers out.
|
|
83
|
+
|
|
84
|
+
This function is used in the server codebase.
|
|
83
85
|
"""
|
|
84
86
|
|
|
85
87
|
top_logprobs = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: logdetective
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
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
|
Author: Jiri Podivin
|
|
@@ -22,7 +22,7 @@ Provides-Extra: server
|
|
|
22
22
|
Requires-Dist: drain3 (>=0.9.11,<0.10.0)
|
|
23
23
|
Requires-Dist: huggingface-hub (>0.23.2)
|
|
24
24
|
Requires-Dist: llama-cpp-python (>0.2.56,!=0.2.86)
|
|
25
|
-
Requires-Dist: numpy (>=1.26.0
|
|
25
|
+
Requires-Dist: numpy (>=1.26.0)
|
|
26
26
|
Requires-Dist: requests (>0.2.31)
|
|
27
27
|
Project-URL: homepage, https://github.com/fedora-copr/logdetective
|
|
28
28
|
Project-URL: issues, https://github.com/fedora-copr/logdetective/issues
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
logdetective/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
logdetective/constants.py,sha256=6XekuU7sbkY1Pmu4NJajgFbJ0no8PQ3DxQm8NeLKtjE,1383
|
|
3
|
+
logdetective/drain3.ini,sha256=ni91eCT1TwTznZwcqWoOVMQcGEnWhEDNCoTPF7cfGfY,1360
|
|
4
|
+
logdetective/extractors.py,sha256=xfan_dbGCrLH4cguJ2F6W6UkxXMz24Vob39r5-GsNV8,3102
|
|
5
|
+
logdetective/logdetective.py,sha256=j34E_udG7dDY5usWmsJ0q767-Fay3K5Aw_lBZUORPSI,4373
|
|
6
|
+
logdetective/server.py,sha256=ryk6bQ4xV4CTlenxiI9SgOOc0bE_GD_z46Ooo7PjFCs,9596
|
|
7
|
+
logdetective/utils.py,sha256=Q51mjbeevD6fIF84QYuc1kgC768a38zCJkJwn7TuJA0,4845
|
|
8
|
+
logdetective-0.2.10.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
9
|
+
logdetective-0.2.10.dist-info/METADATA,sha256=8qmL187ApXgT57eXP_r2-4F732EyH5YTFiGYbcmVIQw,9791
|
|
10
|
+
logdetective-0.2.10.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
11
|
+
logdetective-0.2.10.dist-info/entry_points.txt,sha256=3K_vXja6PmcA8sNdUi63WdImeiNhVZcEGPTaoJmltfA,63
|
|
12
|
+
logdetective-0.2.10.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
logdetective/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
logdetective/constants.py,sha256=1Ls2VJXb7NwSgi_HmTOA1c52K16SZIeDYBXlvBJ07zU,991
|
|
3
|
-
logdetective/drain3.ini,sha256=ni91eCT1TwTznZwcqWoOVMQcGEnWhEDNCoTPF7cfGfY,1360
|
|
4
|
-
logdetective/extractors.py,sha256=eRizRiKhC3MPTHXS5nlRKcEudEaqct7G28V1bZYGkqI,3103
|
|
5
|
-
logdetective/logdetective.py,sha256=f7ASCJg_Yt6VBFieXBYgQYdenfXjC60ZdLHhzQHideI,4372
|
|
6
|
-
logdetective/server.py,sha256=UDH7LEFZ-rnIrrnZDZTcYluom1XnsvYG1b5Fc2xiivs,9226
|
|
7
|
-
logdetective/utils.py,sha256=nTbaDVEfbHVQPTZe58T04HHZ6JWUJ1PonRRnzGX8hY0,4794
|
|
8
|
-
logdetective-0.2.8.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
9
|
-
logdetective-0.2.8.dist-info/METADATA,sha256=gFcfcgLCOQXlQu4eWbHzGzaP8z7-yMwQ36-jFJsSg7c,9797
|
|
10
|
-
logdetective-0.2.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
11
|
-
logdetective-0.2.8.dist-info/entry_points.txt,sha256=3K_vXja6PmcA8sNdUi63WdImeiNhVZcEGPTaoJmltfA,63
|
|
12
|
-
logdetective-0.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|