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 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 = '================'
@@ -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 = 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)
@@ -9,6 +9,7 @@ from logdetective.extractors import LLMExtractor, DrainExtractor
9
9
 
10
10
  LOG = logging.getLogger("logdetective")
11
11
 
12
+
12
13
  def setup_args():
13
14
  """ Setup argument parser and return arguments. """
14
15
  parser = argparse.ArgumentParser("logdetective")
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 PROMPT_TEMPLATE, SNIPPET_PROMPT_TEMPLATE
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: list of CreateCompletionResponse
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
- f"[{log_request.status_code}] {log_request.text}")
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
- "prompt": text,
133
- "max_tokens": str(max_tokens),
134
- "logprobs": str(log_probs),
135
- "stream": stream,
136
- "model": model}
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
- f"[{response.status_code}] {response.text}")
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
- final_analysis = await submit_text(
212
- PROMPT_TEMPLATE.format([e["choices"][0]["text"] for e in analyzed_snippets]))
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
1
+ Metadata-Version: 2.3
2
2
  Name: logdetective
3
- Version: 0.2.8
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,<2.0.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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: poetry-core 2.0.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,