logdetective 1.3.0__py3-none-any.whl → 1.4.0__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
@@ -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 = []
logdetective/models.py CHANGED
@@ -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
+ )
logdetective/prompts.yml CHANGED
@@ -59,3 +59,46 @@ prompt_template_staged: |
59
59
  {}
60
60
 
61
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
  )
logdetective/utils.py CHANGED
@@ -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
  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
@@ -1,12 +1,12 @@
1
1
  logdetective/__init__.py,sha256=VqRngDcuFT7JWms8Qc_MsOvajoXVOKPr-S1kqY3Pqhc,59
2
- logdetective/constants.py,sha256=UmYSutgy8yK-IDMQyXqLtQV-wDserDa4K1GmsMCGXHc,1949
2
+ logdetective/constants.py,sha256=yH2vpYENfJpoYUC6KzzTSRDFOiMmjtRK-GU9vfv8o3o,2560
3
3
  logdetective/drain3.ini,sha256=ni91eCT1TwTznZwcqWoOVMQcGEnWhEDNCoTPF7cfGfY,1360
4
4
  logdetective/extractors.py,sha256=7ahzWbTtU9MveG1Q7wU9LO8OJgs85X-cHmWltUhCe9M,3491
5
- logdetective/logdetective.py,sha256=cC2oL4yPNo94AB2nS4v1jpZi-Qo1g0_FEchL_yQL1UU,5832
6
- logdetective/models.py,sha256=nrGBmMRu8i6UhFflQKAp81Y3Sd_Aaoor0i_yqSJoLT0,1115
5
+ logdetective/logdetective.py,sha256=YwjKSgW2iW4BAxWTOgUbrVbk2Lbv76vOLFTTtuPg3hg,5928
6
+ logdetective/models.py,sha256=0DHCtGUqZzPcduSU4Z5AzuNn3g9XBh6UhLBcA9mDG6M,1653
7
7
  logdetective/prompts-summary-first.yml,sha256=3Zfp4NNOfaFYq5xBlBjeQa5PdjYfS4v17OtJqQ-DRpU,821
8
8
  logdetective/prompts-summary-only.yml,sha256=8U9AMJV8ePW-0CoXOXlQoO92DAJDeutIT8ntSkkm6W0,470
9
- logdetective/prompts.yml,sha256=urPKG068TYxi58EicFVUH6FavZq_q36oM1LvfI4ddjg,1729
9
+ logdetective/prompts.yml,sha256=2WGox--VRINjsxaJc7fql-v1sIVYWYZLEjJV_vvBm9o,3969
10
10
  logdetective/remote_log.py,sha256=1oeMIdDE_ob_2QrlXYTAA_m-36pNEicXbZwrCyzNgwo,2256
11
11
  logdetective/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  logdetective/server/compressors.py,sha256=qzrT-BPSksXY6F2L6ger04GGrgdBsGOfK2YuCFRs0Q4,5427
@@ -18,16 +18,16 @@ logdetective/server/database/models/merge_request_jobs.py,sha256=hw88wV1-3x7i53s
18
18
  logdetective/server/database/models/metrics.py,sha256=yl9fS4IPVFWDeFvPAxO6zOVu6oLF319ApvVLAgnD5yU,13928
19
19
  logdetective/server/emoji.py,sha256=W1nJLU1UnTG8FGttOs6gC7x3TcjxiBuviXklD9f2Mu8,4370
20
20
  logdetective/server/gitlab.py,sha256=wQSlvdWn6XEi1oP6HhI75bIhm6bgdpWr3zu2WXF0_oE,16473
21
- logdetective/server/llm.py,sha256=GkbOjRRWEbw7EhFRpblalwNbwNVQPTTjrbLOqJXKqy0,5388
21
+ logdetective/server/llm.py,sha256=q9LdoAmsx9MpBjnjLyJ9GBU27jKViTaWbVXyMsmsCI0,5721
22
22
  logdetective/server/metric.py,sha256=B3ew_qSmtEMj6xl-FoOtS4F_bkplp-shhtfHF1cG_Io,4010
23
- logdetective/server/models.py,sha256=Pfvyd8CKlahIWeoVAJlQEt2TiLA5ndHEcigfm6xJwBI,15471
23
+ logdetective/server/models.py,sha256=I45uLnq_zqn_r0FdOdop9zQPbsOWOY_M39NBBOXP134,15738
24
24
  logdetective/server/plot.py,sha256=eZs4r9gua-nW3yymSMIz1leL9mb4QKlh6FJZSeOfZ5M,14872
25
- logdetective/server/server.py,sha256=-JJnHj8fPzx8aCJD3q2wRwidxoHPCmwOP8FTWwc1C14,18386
25
+ logdetective/server/server.py,sha256=V-lSG2cCTxoGwvUc8mEmLQQWS4g_W_dER2o118RufAk,18792
26
26
  logdetective/server/templates/gitlab_full_comment.md.j2,sha256=DQZ2WVFedpuXI6znbHIW4wpF9BmFS8FaUkowh8AnGhE,1627
27
27
  logdetective/server/templates/gitlab_short_comment.md.j2,sha256=fzScpayv2vpRLczP_0O0YxtA8rsKvR6gSv4ntNdWb98,1443
28
- logdetective/utils.py,sha256=hdExAC8FtDIxvdgIq-Ro6LVM-JZ-k_UofaMzaDAHvzM,6088
29
- logdetective-1.3.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
30
- logdetective-1.3.0.dist-info/METADATA,sha256=9UIOXKl7Ubj5TCsM2p_enbgDDC80d7uByqwA-VpPFZQ,17709
31
- logdetective-1.3.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- logdetective-1.3.0.dist-info/entry_points.txt,sha256=3K_vXja6PmcA8sNdUi63WdImeiNhVZcEGPTaoJmltfA,63
33
- logdetective-1.3.0.dist-info/RECORD,,
28
+ logdetective/utils.py,sha256=4VDghJs6mTz8PjkYaV794LUWWyEqBRddXKkENJzR5n4,7025
29
+ logdetective-1.4.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
30
+ logdetective-1.4.0.dist-info/METADATA,sha256=UlBDdFwH4kswo5NqhvEWfz7dGcex7aXfFOUt1JI5n4Y,17709
31
+ logdetective-1.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ logdetective-1.4.0.dist-info/entry_points.txt,sha256=3K_vXja6PmcA8sNdUi63WdImeiNhVZcEGPTaoJmltfA,63
33
+ logdetective-1.4.0.dist-info/RECORD,,