logdetective 1.3.0__tar.gz → 1.4.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.
Files changed (34) hide show
  1. {logdetective-1.3.0 → logdetective-1.4.0}/PKG-INFO +1 -1
  2. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/constants.py +15 -0
  3. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/logdetective.py +5 -1
  4. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/models.py +14 -0
  5. logdetective-1.4.0/logdetective/prompts.yml +104 -0
  6. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/llm.py +17 -11
  7. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/models.py +6 -1
  8. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/server.py +15 -4
  9. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/utils.py +32 -0
  10. {logdetective-1.3.0 → logdetective-1.4.0}/pyproject.toml +1 -1
  11. logdetective-1.3.0/logdetective/prompts.yml +0 -61
  12. {logdetective-1.3.0 → logdetective-1.4.0}/LICENSE +0 -0
  13. {logdetective-1.3.0 → logdetective-1.4.0}/README.md +0 -0
  14. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/__init__.py +0 -0
  15. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/drain3.ini +0 -0
  16. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/extractors.py +0 -0
  17. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/prompts-summary-first.yml +0 -0
  18. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/prompts-summary-only.yml +0 -0
  19. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/remote_log.py +0 -0
  20. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/__init__.py +0 -0
  21. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/compressors.py +0 -0
  22. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/config.py +0 -0
  23. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/database/__init__.py +0 -0
  24. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/database/base.py +0 -0
  25. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/database/models/__init__.py +0 -0
  26. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/database/models/merge_request_jobs.py +0 -0
  27. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/database/models/metrics.py +0 -0
  28. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/emoji.py +0 -0
  29. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/gitlab.py +0 -0
  30. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/metric.py +0 -0
  31. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/plot.py +0 -0
  32. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/templates/gitlab_full_comment.md.j2 +0 -0
  33. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective/server/templates/gitlab_short_comment.md.j2 +0 -0
  34. {logdetective-1.3.0 → logdetective-1.4.0}/logdetective.1.asciidoc +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: logdetective
3
- Version: 1.3.0
3
+ Version: 1.4.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
  Author: Jiri Podivin
@@ -69,6 +69,17 @@ Analysis:
69
69
 
70
70
  """
71
71
 
72
+ DEFAULT_SYSTEM_PROMPT = """
73
+ You are a highly capable large language model based expert system specialized in
74
+ packaging and delivery of software using RPM (RPM Package Manager). Your purpose is to diagnose
75
+ RPM build failures, identifying root causes and proposing solutions if possible.
76
+ You are truthful, concise, and helpful.
77
+
78
+ You never speculate about package being built or fabricate information.
79
+ If you do not know the answer, you acknowledge the fact and end your response.
80
+ Your responses must be as short as possible.
81
+ """
82
+
72
83
  SNIPPET_DELIMITER = "================"
73
84
 
74
85
  DEFAULT_TEMPERATURE = 0.8
@@ -76,3 +87,7 @@ DEFAULT_TEMPERATURE = 0.8
76
87
  # Tuning for LLM-as-a-Service
77
88
  LLM_DEFAULT_MAX_QUEUE_SIZE = 50
78
89
  LLM_DEFAULT_REQUESTS_PER_MINUTE = 60
90
+
91
+ # Roles for chat API
92
+ SYSTEM_ROLE_DEFAULT = "developer"
93
+ USER_ROLE_DEFAULT = "user"
@@ -149,6 +149,10 @@ async def run(): # pylint: disable=too-many-statements,too-many-locals
149
149
  log_summary = format_snippets(log_summary)
150
150
  LOG.info("Log summary: \n %s", log_summary)
151
151
 
152
+ prompt = (
153
+ f"{prompts_configuration.default_system_prompt}\n"
154
+ f"{prompts_configuration.prompt_template}")
155
+
152
156
  stream = True
153
157
  if args.no_stream:
154
158
  stream = False
@@ -156,7 +160,7 @@ async def run(): # pylint: disable=too-many-statements,too-many-locals
156
160
  log_summary,
157
161
  model,
158
162
  stream,
159
- prompt_template=prompts_configuration.prompt_template,
163
+ prompt_template=prompt,
160
164
  temperature=args.temperature,
161
165
  )
162
166
  probs = []
@@ -6,6 +6,7 @@ from logdetective.constants import (
6
6
  PROMPT_TEMPLATE_STAGED,
7
7
  SUMMARIZATION_PROMPT_TEMPLATE,
8
8
  SNIPPET_PROMPT_TEMPLATE,
9
+ DEFAULT_SYSTEM_PROMPT,
9
10
  )
10
11
 
11
12
 
@@ -17,6 +18,10 @@ class PromptConfig(BaseModel):
17
18
  snippet_prompt_template: str = SNIPPET_PROMPT_TEMPLATE
18
19
  prompt_template_staged: str = PROMPT_TEMPLATE_STAGED
19
20
 
21
+ default_system_prompt: str = DEFAULT_SYSTEM_PROMPT
22
+ snippet_system_prompt: str = DEFAULT_SYSTEM_PROMPT
23
+ staged_system_prompt: str = DEFAULT_SYSTEM_PROMPT
24
+
20
25
  def __init__(self, data: Optional[dict] = None):
21
26
  super().__init__()
22
27
  if data is None:
@@ -31,3 +36,12 @@ class PromptConfig(BaseModel):
31
36
  self.prompt_template_staged = data.get(
32
37
  "prompt_template_staged", PROMPT_TEMPLATE_STAGED
33
38
  )
39
+ self.default_system_prompt = data.get(
40
+ "default_system_prompt", DEFAULT_SYSTEM_PROMPT
41
+ )
42
+ self.snippet_system_prompt = data.get(
43
+ "snippet_system_prompt", DEFAULT_SYSTEM_PROMPT
44
+ )
45
+ self.staged_system_prompt = data.get(
46
+ "staged_system_prompt", DEFAULT_SYSTEM_PROMPT
47
+ )
@@ -0,0 +1,104 @@
1
+ # This file is intended for customization of prompts
2
+ # It is used only in server mode.
3
+ # On command line you have to load it using --prompts
4
+ # The defaults are stored in constants.py
5
+
6
+ prompt_template: |
7
+ Given following log snippets, and nothing else, explain what failure, if any, occurred during build of this package.
8
+
9
+ Analysis of the snippets must be in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
10
+ Snippets themselves must not be altered in any way whatsoever.
11
+
12
+ Snippets are delimited with '================'.
13
+
14
+ Finally, drawing on information from all snippets, provide complete explanation of the issue and recommend solution.
15
+
16
+ Explanation of the issue, and recommended solution, should take handful of sentences.
17
+
18
+ Snippets:
19
+
20
+ {}
21
+
22
+ Analysis:
23
+
24
+
25
+ summarization_prompt_template: |
26
+ Does following log contain error or issue?
27
+
28
+ Log:
29
+
30
+ {}
31
+
32
+ Answer:
33
+
34
+
35
+ snippet_prompt_template: |
36
+ Analyse following RPM build log snippet. Describe contents accurately, without speculation or suggestions for resolution.
37
+
38
+ Your analysis must be as concise as possible, while keeping relevant information intact.
39
+
40
+ Snippet:
41
+
42
+ {}
43
+
44
+ Analysis:
45
+
46
+ prompt_template_staged: |
47
+ Given following log snippets, their explanation, and nothing else, explain what failure, if any, occurred during build of this package.
48
+
49
+ Snippets are in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
50
+
51
+ Snippets are delimited with '================'.
52
+
53
+ Drawing on information from all snippets, provide a concise explanation of the issue and recommend a solution.
54
+
55
+ Explanation of the issue, and recommended solution, should take a handful of sentences.
56
+
57
+ Snippets:
58
+
59
+ {}
60
+
61
+ Analysis:
62
+
63
+ # System prompts
64
+ # System prompts are meant to serve as general guide for model behavior,
65
+ # describing role and purpose it is meant to serve.
66
+ # Sample system prompts in this file are intentionally the same,
67
+ # however, in some circumstances it may be beneficial have different
68
+ # system prompts for each sub case. For example when a specialized model is deployed
69
+ # to analyze snippets.
70
+
71
+ # Default prompt is used by the CLI tool and also for final analysis
72
+ # with /analyze and /analyze/stream API endpoints
73
+ default_system_prompt: |
74
+ You are a highly capable large language model based expert system specialized in
75
+ packaging and delivery of software using RPM (RPM Package Manager). Your purpose is to diagnose
76
+ RPM build failures, identifying root causes and proposing solutions if possible.
77
+ You are truthful, concise, and helpful.
78
+
79
+ You never speculate about package being built or fabricate information.
80
+ If you do not know the answer, you acknowledge the fact and end your response.
81
+ Your responses must be as short as possible.
82
+
83
+ # Snippet system prompt is used for analysis of individual snippets
84
+ snippet_system_prompt: |
85
+ You are a highly capable large language model based expert system specialized in
86
+ packaging and delivery of software using RPM (RPM Package Manager). Your purpose is to diagnose
87
+ RPM build failures, identifying root causes and proposing solutions if possible.
88
+ You are truthful, concise, and helpful.
89
+
90
+ You never speculate about package being built or fabricate information.
91
+ If you do not know the answer, you acknowledge the fact and end your response.
92
+ Your responses must be as short as possible.
93
+
94
+
95
+ # Staged system prompt is used by /analyze/staged API endpoint
96
+ staged_system_prompt: |
97
+ You are a highly capable large language model based expert system specialized in
98
+ packaging and delivery of software using RPM (RPM Package Manager). Your purpose is to diagnose
99
+ RPM build failures, identifying root causes and proposing solutions if possible.
100
+ You are truthful, concise, and helpful.
101
+
102
+ You never speculate about package being built or fabricate information.
103
+ If you do not know the answer, you acknowledge the fact and end your response.
104
+ Your responses must be as short as possible.
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import asyncio
3
3
  import random
4
- from typing import List, Tuple, Union
4
+ from typing import List, Tuple, Union, Dict
5
5
 
6
6
  import backoff
7
7
  from fastapi import HTTPException
@@ -14,6 +14,7 @@ from logdetective.constants import SNIPPET_DELIMITER
14
14
  from logdetective.extractors import DrainExtractor
15
15
  from logdetective.utils import (
16
16
  compute_certainty,
17
+ prompt_to_messages,
17
18
  )
18
19
  from logdetective.server.config import LOG, SERVER_CONFIG, PROMPT_CONFIG, CLIENT
19
20
  from logdetective.server.models import (
@@ -85,7 +86,7 @@ def we_give_up(details: backoff._typing.Details):
85
86
  on_giveup=we_give_up,
86
87
  )
87
88
  async def submit_text(
88
- text: str,
89
+ messages: List[Dict[str, str]],
89
90
  inference_cfg: InferenceConfig,
90
91
  stream: bool = False,
91
92
  ) -> Union[Explanation, AsyncStream[ChatCompletionChunk]]:
@@ -100,12 +101,7 @@ async def submit_text(
100
101
 
101
102
  async with inference_cfg.get_limiter():
102
103
  response = await CLIENT.chat.completions.create(
103
- messages=[
104
- {
105
- "role": "user",
106
- "content": text,
107
- }
108
- ],
104
+ messages=messages,
109
105
  max_tokens=inference_cfg.max_tokens,
110
106
  logprobs=inference_cfg.log_probs,
111
107
  stream=stream,
@@ -136,7 +132,12 @@ async def perform_staged_analysis(log_text: str) -> StagedResponse:
136
132
  # Process snippets asynchronously
137
133
  awaitables = [
138
134
  submit_text(
139
- PROMPT_CONFIG.snippet_prompt_template.format(s),
135
+ prompt_to_messages(
136
+ PROMPT_CONFIG.snippet_prompt_template.format(s),
137
+ PROMPT_CONFIG.snippet_system_prompt,
138
+ SERVER_CONFIG.inference.system_role,
139
+ SERVER_CONFIG.inference.user_role,
140
+ ),
140
141
  inference_cfg=SERVER_CONFIG.snippet_inference,
141
142
  )
142
143
  for s in log_summary
@@ -150,9 +151,14 @@ async def perform_staged_analysis(log_text: str) -> StagedResponse:
150
151
  final_prompt = PROMPT_CONFIG.prompt_template_staged.format(
151
152
  format_analyzed_snippets(analyzed_snippets)
152
153
  )
153
-
154
- final_analysis = await submit_text(
154
+ messages = prompt_to_messages(
155
155
  final_prompt,
156
+ PROMPT_CONFIG.staged_system_prompt,
157
+ SERVER_CONFIG.inference.system_role,
158
+ SERVER_CONFIG.inference.user_role,
159
+ )
160
+ final_analysis = await submit_text(
161
+ messages,
156
162
  inference_cfg=SERVER_CONFIG.inference,
157
163
  )
158
164
 
@@ -20,6 +20,8 @@ from logdetective.constants import (
20
20
  DEFAULT_TEMPERATURE,
21
21
  LLM_DEFAULT_MAX_QUEUE_SIZE,
22
22
  LLM_DEFAULT_REQUESTS_PER_MINUTE,
23
+ SYSTEM_ROLE_DEFAULT,
24
+ USER_ROLE_DEFAULT,
23
25
  )
24
26
 
25
27
 
@@ -143,6 +145,8 @@ class InferenceConfig(BaseModel): # pylint: disable=too-many-instance-attribute
143
145
  temperature: NonNegativeFloat = DEFAULT_TEMPERATURE
144
146
  max_queue_size: int = LLM_DEFAULT_MAX_QUEUE_SIZE
145
147
  http_timeout: float = 5.0
148
+ user_role: str = USER_ROLE_DEFAULT
149
+ system_role: str = SYSTEM_ROLE_DEFAULT
146
150
  _http_session: aiohttp.ClientSession = None
147
151
  _limiter: AsyncLimiter = AsyncLimiter(LLM_DEFAULT_REQUESTS_PER_MINUTE)
148
152
 
@@ -159,7 +163,8 @@ class InferenceConfig(BaseModel): # pylint: disable=too-many-instance-attribute
159
163
  self.model = data.get("model", "default-model")
160
164
  self.temperature = data.get("temperature", DEFAULT_TEMPERATURE)
161
165
  self.max_queue_size = data.get("max_queue_size", LLM_DEFAULT_MAX_QUEUE_SIZE)
162
-
166
+ self.user_role = data.get("user_role", USER_ROLE_DEFAULT)
167
+ self.system_role = data.get("system_role", SYSTEM_ROLE_DEFAULT)
163
168
  self._requests_per_minute = data.get(
164
169
  "requests_per_minute", LLM_DEFAULT_REQUESTS_PER_MINUTE
165
170
  )
@@ -20,6 +20,7 @@ import logdetective.server.database.base
20
20
  from logdetective.utils import (
21
21
  compute_certainty,
22
22
  format_snippets,
23
+ prompt_to_messages,
23
24
  )
24
25
 
25
26
  from logdetective.server.config import SERVER_CONFIG, PROMPT_CONFIG, LOG
@@ -135,9 +136,14 @@ async def analyze_log(
135
136
  log_text = await remote_log.process_url()
136
137
  log_summary = mine_logs(log_text)
137
138
  log_summary = format_snippets(log_summary)
138
-
139
- response = await submit_text(
139
+ messages = prompt_to_messages(
140
140
  PROMPT_CONFIG.prompt_template.format(log_summary),
141
+ PROMPT_CONFIG.default_system_prompt,
142
+ SERVER_CONFIG.inference.system_role,
143
+ SERVER_CONFIG.inference.user_role,
144
+ )
145
+ response = await submit_text(
146
+ messages,
141
147
  inference_cfg=SERVER_CONFIG.inference,
142
148
  )
143
149
  certainty = 0
@@ -204,10 +210,15 @@ async def analyze_log_stream(
204
210
  log_text = await remote_log.process_url()
205
211
  log_summary = mine_logs(log_text)
206
212
  log_summary = format_snippets(log_summary)
207
-
213
+ messages = prompt_to_messages(
214
+ PROMPT_CONFIG.prompt_template.format(log_summary),
215
+ PROMPT_CONFIG.default_system_prompt,
216
+ SERVER_CONFIG.inference.system_role,
217
+ SERVER_CONFIG.inference.user_role,
218
+ )
208
219
  try:
209
220
  stream = submit_text(
210
- PROMPT_CONFIG.prompt_template.format(log_summary),
221
+ messages,
211
222
  inference_cfg=SERVER_CONFIG.inference,
212
223
  stream=True,
213
224
  )
@@ -195,3 +195,35 @@ def load_prompts(path: str | None) -> PromptConfig:
195
195
  except FileNotFoundError:
196
196
  print("Prompt configuration file not found, reverting to defaults.")
197
197
  return PromptConfig()
198
+
199
+
200
+ def prompt_to_messages(
201
+ user_message: str, system_prompt: str | None = None,
202
+ system_role: str = "developer", user_role: str = "user") -> List[Dict[str, str]]:
203
+ """Turn prompt into list of message dictionaries.
204
+ If `system_role` and `user_role` are the same, only a single message is created,
205
+ as concatenation of `user_message` and `system_prompt`. This is useful for models which
206
+ do not have separate system role, such as mistral.
207
+ """
208
+
209
+ if system_role == user_role:
210
+ messages = [
211
+ {
212
+ "role": system_role,
213
+ "content": f"{system_prompt}\n{user_message}"
214
+ }
215
+ ]
216
+ else:
217
+
218
+ messages = [
219
+ {
220
+ "role": system_role,
221
+ "content": system_prompt
222
+ },
223
+ {
224
+ "role": user_role,
225
+ "content": user_message,
226
+ }
227
+ ]
228
+
229
+ return messages
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "logdetective"
3
- version = "1.3.0"
3
+ version = "1.4.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"
@@ -1,61 +0,0 @@
1
- # This file is intended for customization of prompts
2
- # It is used only in server mode.
3
- # On command line you have to load it using --prompts
4
- # The defaults are stored in constants.py
5
-
6
- prompt_template: |
7
- Given following log snippets, and nothing else, explain what failure, if any, occurred during build of this package.
8
-
9
- Analysis of the snippets must be in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
10
- Snippets themselves must not be altered in any way whatsoever.
11
-
12
- Snippets are delimited with '================'.
13
-
14
- Finally, drawing on information from all snippets, provide complete explanation of the issue and recommend solution.
15
-
16
- Explanation of the issue, and recommended solution, should take handful of sentences.
17
-
18
- Snippets:
19
-
20
- {}
21
-
22
- Analysis:
23
-
24
-
25
- summarization_prompt_template: |
26
- Does following log contain error or issue?
27
-
28
- Log:
29
-
30
- {}
31
-
32
- Answer:
33
-
34
-
35
- snippet_prompt_template: |
36
- Analyse following RPM build log snippet. Describe contents accurately, without speculation or suggestions for resolution.
37
-
38
- Your analysis must be as concise as possible, while keeping relevant information intact.
39
-
40
- Snippet:
41
-
42
- {}
43
-
44
- Analysis:
45
-
46
- prompt_template_staged: |
47
- Given following log snippets, their explanation, and nothing else, explain what failure, if any, occurred during build of this package.
48
-
49
- Snippets are in a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
50
-
51
- Snippets are delimited with '================'.
52
-
53
- Drawing on information from all snippets, provide a concise explanation of the issue and recommend a solution.
54
-
55
- Explanation of the issue, and recommended solution, should take a handful of sentences.
56
-
57
- Snippets:
58
-
59
- {}
60
-
61
- Analysis:
File without changes
File without changes