logdetective 3.0.0__tar.gz → 3.1.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 (46) hide show
  1. {logdetective-3.0.0 → logdetective-3.1.0}/PKG-INFO +46 -17
  2. {logdetective-3.0.0 → logdetective-3.1.0}/README.md +44 -15
  3. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/constants.py +1 -1
  4. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/logdetective.py +9 -1
  5. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/models.py +1 -20
  6. logdetective-3.1.0/logdetective/prompts/message_template.j2 +2 -0
  7. logdetective-3.1.0/logdetective/prompts/snippet_message_template.j2 +2 -0
  8. logdetective-3.1.0/logdetective/prompts/snippet_system_prompt.j2 +38 -0
  9. logdetective-3.1.0/logdetective/prompts/staged_message_template.j2 +2 -0
  10. logdetective-3.1.0/logdetective/prompts/staged_system_prompt.j2 +45 -0
  11. logdetective-3.1.0/logdetective/prompts/system_prompt.j2 +57 -0
  12. logdetective-3.1.0/logdetective/prompts.py +87 -0
  13. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/prompts.yml +7 -0
  14. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/config.py +3 -2
  15. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/utils.py +35 -26
  16. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective.1.asciidoc +6 -1
  17. {logdetective-3.0.0 → logdetective-3.1.0}/pyproject.toml +8 -2
  18. {logdetective-3.0.0 → logdetective-3.1.0}/LICENSE +0 -0
  19. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/__init__.py +0 -0
  20. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/drain3.ini +0 -0
  21. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/extractors.py +0 -0
  22. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/prompts-summary-first.yml +0 -0
  23. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/prompts-summary-only.yml +0 -0
  24. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/remote_log.py +0 -0
  25. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/__init__.py +0 -0
  26. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/compressors.py +0 -0
  27. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/__init__.py +0 -0
  28. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/base.py +0 -0
  29. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/models/__init__.py +0 -0
  30. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/models/exceptions.py +0 -0
  31. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/models/koji.py +0 -0
  32. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/models/merge_request_jobs.py +0 -0
  33. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/database/models/metrics.py +0 -0
  34. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/emoji.py +0 -0
  35. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/exceptions.py +0 -0
  36. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/gitlab.py +0 -0
  37. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/koji.py +0 -0
  38. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/llm.py +0 -0
  39. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/metric.py +0 -0
  40. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/models.py +0 -0
  41. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/server.py +0 -0
  42. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/templates/base_response.html.j2 +0 -0
  43. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/templates/gitlab_full_comment.md.j2 +0 -0
  44. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/templates/gitlab_short_comment.md.j2 +0 -0
  45. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/server/utils.py +0 -0
  46. {logdetective-3.0.0 → logdetective-3.1.0}/logdetective/skip_snippets.yml +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logdetective
3
- Version: 3.0.0
4
- Summary: Log using LLM AI to search for build/test failures and provide ideas for fixing these.
3
+ Version: 3.1.0
4
+ Summary: Analyze logs with a template miner and an LLM to discover errors and suggest solutions.
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
7
7
  Author: Jiri Podivin
@@ -96,11 +96,12 @@ Usage
96
96
 
97
97
  To analyze a log file, run the script with the following command line arguments:
98
98
  - `file` (required): The path or URL of the log file to be analyzed.
99
- - `--model` (optional, default: "Mistral-7B-Instruct-v0.3-GGUF"): The path or Hugging space name of the language model for analysis. For models from Hugging Face, write them as `namespace/repo_name`. As we are using LLama.cpp we want this to be in the `gguf` format. If the model is already on your machine it will skip the download.
99
+ - `--model` (optional, default: "granite-3.2-8b-instruct-GGUF"): The path or Hugging space name of the language model for analysis. For models from Hugging Face, write them as `namespace/repo_name`. As we are using LLama.cpp we want this to be in the `gguf` format. If the model is already on your machine it will skip the download.
100
100
  - `--filename-suffix` (optional, default "Q4_K.gguf"): You can specify which suffix of the file to use. This option is applied when specifying model using the Hugging Face repository.
101
101
  - `--n-clusters` (optional, default 8): Number of clusters for Drain to organize log chunks into. This only makes sense when you are summarizing with Drain.
102
- - `--skip-snippets` Path to patterns for skipping snippets (in YAML).
103
- - `--prompts PROMPTS` Path to prompt configuration file.
102
+ - `--prompts PROMPTS` (Deprecated, replaced by `--prompts-config`) Path to prompt configuration file.
103
+ - `--prompts-config PROMPTS` Path to prompt configuration file.
104
+ - `--prompt-templates` Path to prompt template dir. Prompts must be valid Jinja templates, and system prompts must include field `system_time`.
104
105
  - `--temperature` Temperature for inference.
105
106
  - `--skip-snippets` Path to patterns for skipping snippets.
106
107
  - `--csgrep` Use csgrep to process the log.
@@ -120,9 +121,15 @@ Examples of using different models. Note the use of `--filename-suffix` (or `-F`
120
121
 
121
122
  Example of altered prompts:
122
123
 
123
- cp ~/.local/lib/python3.13/site-packages/logdetective/prompts.yml ~/my-prompts.yml
124
- vi ~/my-prompts.yml # edit the prompts there to better fit your needs
125
- logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompts ~/my-prompts.yml
124
+ cp -r ~/.local/lib/python3.13/site-packages/logdetective/prompts ~/my-prompts
125
+ vi ~/my-prompts/system_prompt.j2 # edit the system prompt there to better fit your needs
126
+ logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompt-templates ~/my-prompts
127
+
128
+ Example of altered prompts (Deprecated):
129
+
130
+ cp ~/.local/lib/python3.13/site-packages/logdetective/prompts.yml ~/my-prompts.yml
131
+ vi ~/my-prompts.yml # edit the prompts there to better fit your needs
132
+ logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompts ~/my-prompts.yml
126
133
 
127
134
 
128
135
  Note that streaming with some models (notably Meta-Llama-3) is broken and can be worked around by `no-stream` option:
@@ -206,7 +213,8 @@ message is reported indicating that the 'check' phase of the rpm build process
206
213
  failed with a bad exit status.
207
214
  ```
208
215
 
209
- It looks like a wall of text. Similar to any log. The main difference is that here we have the most significant lines of a logfile wrapped in `[ ] : ` and followed by textual explanation of the log text done by mistral 7b.
216
+ It looks like a wall of text. Similar to any log.
217
+ The main difference is that here we have the most significant lines of a logfile wrapped in `[ ] : ` and followed by textual explanation of the log text done by local LLM.
210
218
 
211
219
 
212
220
  Contributing
@@ -374,14 +382,14 @@ Before doing `podman-compose up`, make sure to set `MODELS_PATH` environment var
374
382
  ```
375
383
  $ export MODELS_PATH=/path/to/models/
376
384
  $ ll $MODELS_PATH
377
- -rw-r--r--. 1 tt tt 3.9G apr 10 17:18 mistral-7b-instruct-v0.2.Q4_K_S.gguf
385
+ -rw-r--r--. 1 tt tt 3.9G apr 10 17:18 granite-4.0-h-tiny-Q8_0.gguf
378
386
  ```
379
387
 
380
388
  If the variable is not set, `./models` is mounted inside by default.
381
389
 
382
390
  Model can be downloaded from [our Hugging Space](https://huggingface.co/fedora-copr) by:
383
391
  ```
384
- $ curl -L -o models/mistral-7b-instruct-v0.3.Q4_K.gguf https://huggingface.co/fedora-copr/Mistral-7B-Instruct-v0.3-GGUF/resolve/main/ggml-model-Q4_K.gguf
392
+ $ curl -L -o models/granite-3.2-8b-instruct-v0.3.Q4_K.gguf https://huggingface.co/fedora-copr/granite-3.2-8b-instruct-GGUF/resolve/main/ggml-model-Q4_K.gguf
385
393
  ```
386
394
 
387
395
  Filtering snippet analysis by relevance
@@ -501,17 +509,38 @@ http GET "localhost:8080/metrics/analyze/requests?weeks=5" > /tmp/plot_weeks.svg
501
509
  System Prompts
502
510
  --------------
503
511
 
504
- Prompt templates used by Log Detective are stored in the `prompts.yml` file.
512
+ Prompts are defined as Jinja templates and placed in location specified by `--prompt-templates` option of the CLI utility, or `LOGDETECTIVE_PROMPT_TEMPLATES` environment variable of the container service. With further, optional, configuration in the `prompts.yml` configuration file.
513
+
514
+ All system prompt templates must include place for `system_time` variable.
515
+
516
+ If `references` list is defined in `prompts.yml`, templates must also include a handling for a list of references.
517
+
518
+ Example:
519
+
520
+ ```jinja
521
+ {% if references %}
522
+ ## References:
523
+
524
+ {% for reference in references %}
525
+ * {{ reference.name }} : {{ reference.link }}
526
+ {% endfor %}
527
+ {% endif %}
528
+
529
+ ```
530
+
531
+ *Deprecated:*
532
+
533
+ *Prompt templates used by Log Detective are stored in the `prompts.yml` file.
505
534
  It is possible to modify the file in place, or provide your own.
506
535
  In CLI you can override prompt templates location using `--prompts` option,
507
536
  while in the container service deployment the `LOGDETECTIVE_PROMPTS` environment variable
508
- is used instead.
537
+ is used instead.*
509
538
 
510
- Prompts need to have a form compatible with python [format string syntax](https://docs.python.org/3/library/string.html#format-string-syntax)
511
- with spaces, or replacement fields marked with curly braces, `{}` left for insertion of snippets.
539
+ *Prompts need to have a form compatible with python [format string syntax](https://docs.python.org/3/library/string.html#format-string-syntax)
540
+ with spaces, or replacement fields marked with curly braces, `{}` left for insertion of snippets.*
512
541
 
513
- Number of replacement fields in new prompts, must be the same as in originals.
514
- Although their position may be different.
542
+ *Number of replacement fields in new prompts, must be the same as in originals.
543
+ Although their position may be different.*
515
544
 
516
545
 
517
546
  Skip Snippets
@@ -42,11 +42,12 @@ Usage
42
42
 
43
43
  To analyze a log file, run the script with the following command line arguments:
44
44
  - `file` (required): The path or URL of the log file to be analyzed.
45
- - `--model` (optional, default: "Mistral-7B-Instruct-v0.3-GGUF"): The path or Hugging space name of the language model for analysis. For models from Hugging Face, write them as `namespace/repo_name`. As we are using LLama.cpp we want this to be in the `gguf` format. If the model is already on your machine it will skip the download.
45
+ - `--model` (optional, default: "granite-3.2-8b-instruct-GGUF"): The path or Hugging space name of the language model for analysis. For models from Hugging Face, write them as `namespace/repo_name`. As we are using LLama.cpp we want this to be in the `gguf` format. If the model is already on your machine it will skip the download.
46
46
  - `--filename-suffix` (optional, default "Q4_K.gguf"): You can specify which suffix of the file to use. This option is applied when specifying model using the Hugging Face repository.
47
47
  - `--n-clusters` (optional, default 8): Number of clusters for Drain to organize log chunks into. This only makes sense when you are summarizing with Drain.
48
- - `--skip-snippets` Path to patterns for skipping snippets (in YAML).
49
- - `--prompts PROMPTS` Path to prompt configuration file.
48
+ - `--prompts PROMPTS` (Deprecated, replaced by `--prompts-config`) Path to prompt configuration file.
49
+ - `--prompts-config PROMPTS` Path to prompt configuration file.
50
+ - `--prompt-templates` Path to prompt template dir. Prompts must be valid Jinja templates, and system prompts must include field `system_time`.
50
51
  - `--temperature` Temperature for inference.
51
52
  - `--skip-snippets` Path to patterns for skipping snippets.
52
53
  - `--csgrep` Use csgrep to process the log.
@@ -66,9 +67,15 @@ Examples of using different models. Note the use of `--filename-suffix` (or `-F`
66
67
 
67
68
  Example of altered prompts:
68
69
 
69
- cp ~/.local/lib/python3.13/site-packages/logdetective/prompts.yml ~/my-prompts.yml
70
- vi ~/my-prompts.yml # edit the prompts there to better fit your needs
71
- logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompts ~/my-prompts.yml
70
+ cp -r ~/.local/lib/python3.13/site-packages/logdetective/prompts ~/my-prompts
71
+ vi ~/my-prompts/system_prompt.j2 # edit the system prompt there to better fit your needs
72
+ logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompt-templates ~/my-prompts
73
+
74
+ Example of altered prompts (Deprecated):
75
+
76
+ cp ~/.local/lib/python3.13/site-packages/logdetective/prompts.yml ~/my-prompts.yml
77
+ vi ~/my-prompts.yml # edit the prompts there to better fit your needs
78
+ logdetective https://kojipkgs.fedoraproject.org//work/tasks/3367/131313367/build.log --prompts ~/my-prompts.yml
72
79
 
73
80
 
74
81
  Note that streaming with some models (notably Meta-Llama-3) is broken and can be worked around by `no-stream` option:
@@ -152,7 +159,8 @@ message is reported indicating that the 'check' phase of the rpm build process
152
159
  failed with a bad exit status.
153
160
  ```
154
161
 
155
- It looks like a wall of text. Similar to any log. The main difference is that here we have the most significant lines of a logfile wrapped in `[ ] : ` and followed by textual explanation of the log text done by mistral 7b.
162
+ It looks like a wall of text. Similar to any log.
163
+ The main difference is that here we have the most significant lines of a logfile wrapped in `[ ] : ` and followed by textual explanation of the log text done by local LLM.
156
164
 
157
165
 
158
166
  Contributing
@@ -320,14 +328,14 @@ Before doing `podman-compose up`, make sure to set `MODELS_PATH` environment var
320
328
  ```
321
329
  $ export MODELS_PATH=/path/to/models/
322
330
  $ ll $MODELS_PATH
323
- -rw-r--r--. 1 tt tt 3.9G apr 10 17:18 mistral-7b-instruct-v0.2.Q4_K_S.gguf
331
+ -rw-r--r--. 1 tt tt 3.9G apr 10 17:18 granite-4.0-h-tiny-Q8_0.gguf
324
332
  ```
325
333
 
326
334
  If the variable is not set, `./models` is mounted inside by default.
327
335
 
328
336
  Model can be downloaded from [our Hugging Space](https://huggingface.co/fedora-copr) by:
329
337
  ```
330
- $ curl -L -o models/mistral-7b-instruct-v0.3.Q4_K.gguf https://huggingface.co/fedora-copr/Mistral-7B-Instruct-v0.3-GGUF/resolve/main/ggml-model-Q4_K.gguf
338
+ $ curl -L -o models/granite-3.2-8b-instruct-v0.3.Q4_K.gguf https://huggingface.co/fedora-copr/granite-3.2-8b-instruct-GGUF/resolve/main/ggml-model-Q4_K.gguf
331
339
  ```
332
340
 
333
341
  Filtering snippet analysis by relevance
@@ -447,17 +455,38 @@ http GET "localhost:8080/metrics/analyze/requests?weeks=5" > /tmp/plot_weeks.svg
447
455
  System Prompts
448
456
  --------------
449
457
 
450
- Prompt templates used by Log Detective are stored in the `prompts.yml` file.
458
+ Prompts are defined as Jinja templates and placed in location specified by `--prompt-templates` option of the CLI utility, or `LOGDETECTIVE_PROMPT_TEMPLATES` environment variable of the container service. With further, optional, configuration in the `prompts.yml` configuration file.
459
+
460
+ All system prompt templates must include place for `system_time` variable.
461
+
462
+ If `references` list is defined in `prompts.yml`, templates must also include a handling for a list of references.
463
+
464
+ Example:
465
+
466
+ ```jinja
467
+ {% if references %}
468
+ ## References:
469
+
470
+ {% for reference in references %}
471
+ * {{ reference.name }} : {{ reference.link }}
472
+ {% endfor %}
473
+ {% endif %}
474
+
475
+ ```
476
+
477
+ *Deprecated:*
478
+
479
+ *Prompt templates used by Log Detective are stored in the `prompts.yml` file.
451
480
  It is possible to modify the file in place, or provide your own.
452
481
  In CLI you can override prompt templates location using `--prompts` option,
453
482
  while in the container service deployment the `LOGDETECTIVE_PROMPTS` environment variable
454
- is used instead.
483
+ is used instead.*
455
484
 
456
- Prompts need to have a form compatible with python [format string syntax](https://docs.python.org/3/library/string.html#format-string-syntax)
457
- with spaces, or replacement fields marked with curly braces, `{}` left for insertion of snippets.
485
+ *Prompts need to have a form compatible with python [format string syntax](https://docs.python.org/3/library/string.html#format-string-syntax)
486
+ with spaces, or replacement fields marked with curly braces, `{}` left for insertion of snippets.*
458
487
 
459
- Number of replacement fields in new prompts, must be the same as in originals.
460
- Although their position may be different.
488
+ *Number of replacement fields in new prompts, must be the same as in originals.
489
+ Although their position may be different.*
461
490
 
462
491
 
463
492
  Skip Snippets
@@ -4,7 +4,7 @@ in prompts.yaml instead.
4
4
  """
5
5
 
6
6
  # pylint: disable=line-too-long
7
- DEFAULT_ADVISOR = "fedora-copr/Mistral-7B-Instruct-v0.3-GGUF"
7
+ DEFAULT_ADVISOR = "fedora-copr/granite-3.2-8b-instruct-GGUF"
8
8
 
9
9
  PROMPT_TEMPLATE = """
10
10
  Given following log snippets, and nothing else, explain what failure, if any, occured during build of this package.
@@ -59,10 +59,18 @@ def setup_args():
59
59
  parser.add_argument("-q", "--quiet", action="store_true")
60
60
  parser.add_argument(
61
61
  "--prompts",
62
+ "--prompts-config",
62
63
  type=str,
63
64
  default=f"{os.path.dirname(__file__)}/prompts.yml",
64
65
  help="Path to prompt configuration file.",
65
66
  )
67
+ parser.add_argument(
68
+ "--prompt-templates",
69
+ type=str,
70
+ default=f"{os.path.dirname(__file__)}/prompts",
71
+ help="Path to prompt template dir. Prompts must be valid Jinja templates, \
72
+ and system prompts must include field `system_time`.",
73
+ )
66
74
  parser.add_argument(
67
75
  "--temperature",
68
76
  type=float,
@@ -97,7 +105,7 @@ async def run(): # pylint: disable=too-many-statements,too-many-locals,too-many
97
105
  log_level = 0
98
106
 
99
107
  # Get prompts configuration
100
- prompts_configuration = load_prompts(args.prompts)
108
+ prompts_configuration = load_prompts(args.prompts, args.prompt_templates)
101
109
 
102
110
  logging.basicConfig(stream=sys.stdout)
103
111
  LOG.setLevel(log_level)
@@ -21,26 +21,7 @@ class PromptConfig(BaseModel):
21
21
  snippet_system_prompt: str = DEFAULT_SYSTEM_PROMPT
22
22
  staged_system_prompt: str = DEFAULT_SYSTEM_PROMPT
23
23
 
24
- def __init__(self, data: Optional[dict] = None):
25
- super().__init__()
26
- if data is None:
27
- return
28
- self.prompt_template = data.get("prompt_template", PROMPT_TEMPLATE)
29
- self.snippet_prompt_template = data.get(
30
- "snippet_prompt_template", SNIPPET_PROMPT_TEMPLATE
31
- )
32
- self.prompt_template_staged = data.get(
33
- "prompt_template_staged", PROMPT_TEMPLATE_STAGED
34
- )
35
- self.default_system_prompt = data.get(
36
- "default_system_prompt", DEFAULT_SYSTEM_PROMPT
37
- )
38
- self.snippet_system_prompt = data.get(
39
- "snippet_system_prompt", DEFAULT_SYSTEM_PROMPT
40
- )
41
- self.staged_system_prompt = data.get(
42
- "staged_system_prompt", DEFAULT_SYSTEM_PROMPT
43
- )
24
+ references: Optional[list[dict[str, str]]] = None
44
25
 
45
26
 
46
27
  class SkipSnippets(BaseModel):
@@ -0,0 +1,2 @@
1
+ Snippets:
2
+ {}
@@ -0,0 +1,38 @@
1
+ System time: {{ system_time }}
2
+
3
+ You are a highly capable expert system specialized in packaging and delivery of software using RPM,
4
+ within the RHEL ecosystem. Your purpose is to help package maintainers diagnose and resolve package build failures.
5
+ You are truthful, concise, and helpful.
6
+
7
+ ## Input processing
8
+
9
+ You will work with snippets of logs produced during package build.
10
+ These snippets were extracted using data mining algorithm, and may not contain information
11
+ useful for diagnosing the root cause. Snippets without useful information must be disregarded.
12
+
13
+ ## Analysis procedure
14
+
15
+ 1. Provide the snippet with a short explanation.
16
+ 2. If the snippet doesn't contain useful information, indicate the fact with a short sentence.
17
+
18
+ ## Examples:
19
+
20
+ User: "Snippet: RPM build errors:"
21
+ Assistant: "Errors occurred during package build.
22
+ ---
23
+ User: "Snippet: Copr build error: Build failed"
24
+ Assistant: "The build in Copr has failed."
25
+ ---
26
+ User: "Snippet: /bin/tar: Removing leading `/' from member names"
27
+ Assistant: "This snippet is irrelevant."
28
+ ---
29
+
30
+ {% if references %}
31
+ ## References:
32
+
33
+ When necessary, suggest resources that may be helpful to user.
34
+
35
+ {% for reference in references %}
36
+ * {{ reference.name }} : {{ reference.link }}
37
+ {% endfor %}
38
+ {% endif %}
@@ -0,0 +1,45 @@
1
+ System time: {{ system_time }}
2
+
3
+ You are a highly capable expert system specialized in packaging and delivery of software using RPM,
4
+ within the RHEL ecosystem. Your purpose is to help package maintainers diagnose and resolve package build failures.
5
+ You are truthful, concise, and helpful.
6
+
7
+ ## Input processing
8
+
9
+ You will work with snippets of logs produced during package build.
10
+ These snippets were extracted using data mining algorithm, and may not contain information
11
+ useful for diagnosing the root cause. Snippets without useful information must be disregarded.
12
+
13
+ ## Analysis procedure
14
+
15
+ Analyzed snippets are a format of [X] : [Y], where [X] is a log snippet, and [Y] is the explanation.
16
+ Do not reanalyze the raw log [X].
17
+
18
+ Snippets are delimited with '================'.
19
+
20
+ 1. Analyze individual snippets, unless they already have analysis attached.
21
+ 2. Disregard snippets that do not contain useful information.
22
+ 3. Using information from all snippets provide explanation of the issue.
23
+ 4. (Optional) Recommend a solution for the package maintainer, only if the cause is clear.
24
+
25
+ ## Examples:
26
+
27
+ User: "
28
+ Snippets:
29
+ ================
30
+ Snippet No. 1 at line #452:
31
+ [error: command 'gcc' failed: No such file or directory]: [`gcc` compiler is not available in the build environment]
32
+ ================
33
+ Snippet No. 2 at line #452:
34
+ [Copr build error: Build failed]: [Package build in Copr failed]"
35
+ Assistant: "Package build in Copr failed due to missing `gcc` compiler. Ensure that all build requirements are correctly specified in the spec file."
36
+
37
+ {% if references %}
38
+ ## References:
39
+
40
+ When necessary, suggest resources that may be helpful to user.
41
+
42
+ {% for reference in references %}
43
+ * {{ reference.name }} : {{ reference.link }}
44
+ {% endfor %}
45
+ {% endif %}
@@ -0,0 +1,57 @@
1
+ System time: {{ system_time }}
2
+
3
+ You are a highly capable expert system specialized in packaging and delivery of software using RPM,
4
+ within the RHEL ecosystem. Your purpose is to help package maintainers diagnose and resolve package build failures.
5
+ You are truthful, concise, and helpful.
6
+
7
+ ## Input processing
8
+
9
+ You will work with snippets of logs produced during a failed package build.
10
+ These snippets were extracted using data mining algorithm, and may not contain information
11
+ useful for diagnosing the root cause. Snippets without useful information must be disregarded.
12
+ General error messages, such as failure of commands used during build, are expected.
13
+
14
+ ## Temporal Logic and Causality
15
+
16
+ Log snippets are typically provided in chronological order. When analyzing multiple snippets
17
+ the first significant error in the log is usually the root cause.
18
+
19
+ An error occurring at line #500 cannot be caused by an error occurring at line #1000.
20
+
21
+ Subsequent errors are often side effects of the initial failure. Focus your diagnosis on the primary trigger.
22
+
23
+ ## Analysis procedure
24
+
25
+ Snippets are provided in order of appearance in the original log, with attached line number,
26
+ and are delimited with '================'.
27
+ Avoid generic or boilerplate recommendations (e.g., "check the logs," "ensure dependencies are met").
28
+ If a specific root cause is identified, the recommendation must directly address that cause.
29
+
30
+ 1. Analyze individual snippets. Do not quote analyzed snippets.
31
+ 2. Disregard snippets that do not contain useful information.
32
+ 3. Using information from all snippets provide explanation of the issue. Be as specific as possible.
33
+ 4. (Optional) Recommend a solution for the package maintainer, only if the cause is clear.
34
+
35
+ ## Examples:
36
+
37
+ User: "
38
+ Snippets:
39
+ Snippet No. 1 at line #452:
40
+
41
+ error: command 'gcc' failed: No such file or directory
42
+ ================
43
+ Snippet No. 2 at line #560:
44
+
45
+ Copr build error: Build failed
46
+ ================"
47
+ Assistant: "Package build in Copr failed due to missing `gcc` compiler. Ensure that all build requirements are correctly specified in the spec file."
48
+
49
+ {% if references %}
50
+ ## References:
51
+
52
+ When necessary, suggest resources that may be helpful to user.
53
+
54
+ {% for reference in references %}
55
+ * {{ reference.name }} : {{ reference.link }}
56
+ {% endfor %}
57
+ {% endif %}
@@ -0,0 +1,87 @@
1
+ from datetime import datetime, timezone
2
+ from typing import Optional
3
+ from jinja2 import Environment, FileSystemLoader, Template
4
+
5
+ from logdetective.models import PromptConfig
6
+
7
+
8
+ class PromptManager: # pylint: disable=too-many-instance-attributes
9
+ """Manages prompts defined as jinja templates"""
10
+ _tmp_env: Environment
11
+
12
+ # Templates for system prompts
13
+ _default_system_prompt_template: Template
14
+ _snippet_system_prompt_template: Template
15
+ _staged_system_prompt_template: Template
16
+
17
+ # Templates for messages
18
+ default_message_template: Template
19
+ snippet_message_template: Template
20
+ staged_message_template: Template
21
+
22
+ _references: Optional[list[dict[str, str]]] = None
23
+
24
+ def __init__(
25
+ self, prompts_path: str, prompts_configuration: Optional[PromptConfig] = None
26
+ ) -> None:
27
+ self._tmp_env = Environment(loader=FileSystemLoader(prompts_path))
28
+
29
+ self._default_system_prompt_template = self._tmp_env.get_template(
30
+ "system_prompt.j2"
31
+ )
32
+ self._snippet_system_prompt_template = self._tmp_env.get_template(
33
+ "snippet_system_prompt.j2"
34
+ )
35
+ self._staged_system_prompt_template = self._tmp_env.get_template(
36
+ "staged_system_prompt.j2"
37
+ )
38
+
39
+ self.default_message_template = self._tmp_env.get_template(
40
+ "message_template.j2"
41
+ )
42
+ self.snippet_message_template = self._tmp_env.get_template(
43
+ "snippet_message_template.j2"
44
+ )
45
+ self.staged_message_template = self._tmp_env.get_template(
46
+ "staged_message_template.j2"
47
+ )
48
+
49
+ if prompts_configuration:
50
+ self._references = prompts_configuration.references
51
+
52
+ # To maintain backward compatibility with `logdetective.models.PromptConfig`
53
+ @property
54
+ def default_system_prompt(self) -> str:
55
+ """Render system prompt from a template"""
56
+ return self._default_system_prompt_template.render(
57
+ system_time=datetime.now(timezone.utc), references=self._references
58
+ )
59
+
60
+ @property
61
+ def snippet_system_prompt(self) -> str:
62
+ """Render system prompt from a template"""
63
+ return self._snippet_system_prompt_template.render(
64
+ system_time=datetime.now(timezone.utc), references=self._references
65
+ )
66
+
67
+ @property
68
+ def staged_system_prompt(self) -> str:
69
+ """Render system prompt from a template"""
70
+ return self._staged_system_prompt_template.render(
71
+ system_time=datetime.now(timezone.utc), references=self._references
72
+ )
73
+
74
+ @property
75
+ def prompt_template(self) -> str:
76
+ """Render message prompt from the template"""
77
+ return self.default_message_template.render()
78
+
79
+ @property
80
+ def snippet_prompt_template(self) -> str:
81
+ """Render message prompt from the template"""
82
+ return self.snippet_message_template.render()
83
+
84
+ @property
85
+ def prompt_template_staged(self) -> str:
86
+ """Render message prompt from the template"""
87
+ return self.staged_message_template.render()
@@ -88,3 +88,10 @@ staged_system_prompt: |
88
88
  You never speculate about package being built or fabricate information.
89
89
  If you do not know the answer, you acknowledge the fact and end your response.
90
90
  Your responses must be as short as possible.
91
+
92
+ # Optional references, to be used when constructing prompt from Jinja template
93
+ # references:
94
+ # - name: Fedora Packaging Guidelines
95
+ # link: https://docs.fedoraproject.org/en-US/packaging-guidelines/
96
+ # - name: Mock user documentation
97
+ # link: https://rpm-software-management.github.io/mock/
@@ -61,7 +61,8 @@ def get_openai_api_client(inference_config: InferenceConfig):
61
61
 
62
62
 
63
63
  SERVER_CONFIG_PATH = os.environ.get("LOGDETECTIVE_SERVER_CONF", None)
64
- SERVER_PROMPT_PATH = os.environ.get("LOGDETECTIVE_PROMPTS", None)
64
+ SERVER_PROMPT_CONF_PATH = os.environ.get("LOGDETECTIVE_PROMPTS", None)
65
+ SERVER_PROMPT_PATH = os.environ.get("LOGDETECTIVE_PROMPT_TEMPLATES", None)
65
66
  # The default location for skip patterns is in the same directory
66
67
  # as logdetective __init__.py file.
67
68
  SERVER_SKIP_PATTERNS_PATH = os.environ.get(
@@ -70,7 +71,7 @@ SERVER_SKIP_PATTERNS_PATH = os.environ.get(
70
71
  )
71
72
 
72
73
  SERVER_CONFIG = load_server_config(SERVER_CONFIG_PATH)
73
- PROMPT_CONFIG = load_prompts(SERVER_PROMPT_PATH)
74
+ PROMPT_CONFIG = load_prompts(SERVER_PROMPT_CONF_PATH, SERVER_PROMPT_PATH)
74
75
  SKIP_SNIPPETS_CONFIG = load_skip_snippet_patterns(SERVER_SKIP_PATTERNS_PATH)
75
76
 
76
77
  LOG = get_log(SERVER_CONFIG)
@@ -1,10 +1,11 @@
1
1
  import logging
2
2
  import os
3
3
  import subprocess as sp
4
- from typing import Iterator, List, Dict, Tuple, Generator
4
+ from typing import Iterator, List, Dict, Tuple, Generator, Optional
5
5
  from urllib.parse import urlparse
6
6
 
7
7
  import aiohttp
8
+ from jinja2 import exceptions
8
9
  import numpy as np
9
10
  import yaml
10
11
 
@@ -15,6 +16,7 @@ from llama_cpp import (
15
16
  )
16
17
  from logdetective.constants import SNIPPET_DELIMITER
17
18
  from logdetective.models import PromptConfig, SkipSnippets
19
+ from logdetective.prompts import PromptManager
18
20
  from logdetective.remote_log import RemoteLog
19
21
 
20
22
  LOG = logging.getLogger("logdetective")
@@ -127,7 +129,11 @@ def compute_certainty(probs: List[Dict]) -> float:
127
129
 
128
130
 
129
131
  def process_log(
130
- log: str, model: Llama, stream: bool, prompt_templates: PromptConfig, temperature: float
132
+ log: str,
133
+ model: Llama,
134
+ stream: bool,
135
+ prompt_templates: PromptConfig | PromptManager,
136
+ temperature: float,
131
137
  ) -> CreateChatCompletionResponse | Iterator[CreateChatCompletionStreamResponse]:
132
138
  """Processes a given log using the provided language model and returns its summary.
133
139
 
@@ -135,20 +141,14 @@ def process_log(
135
141
  log (str): The input log to be processed.
136
142
  model (Llama): The language model used for processing the log.
137
143
  stream (bool): Return output as Iterator.
138
- prompt_template (str): Which prompt template to use.
144
+ prompt_templates (PromptConfig | PromptManager): Prompt templates to use with LLM.
139
145
  temperature (float): Temperature parameter for model runtime.
140
146
  Returns:
141
147
  str: The summary of the given log generated by the language model.
142
148
  """
143
149
  messages = [
144
- {
145
- "role": "system",
146
- "content": prompt_templates.default_system_prompt
147
- },
148
- {
149
- "role": "user",
150
- "content": prompt_templates.prompt_template.format(log)
151
- },
150
+ {"role": "system", "content": prompt_templates.default_system_prompt},
151
+ {"role": "user", "content": prompt_templates.prompt_template.format(log)},
152
152
  ]
153
153
 
154
154
  response = model.create_chat_completion(
@@ -200,26 +200,35 @@ def format_snippets(snippets: list[str] | list[Tuple[int, str]]) -> str:
200
200
  else:
201
201
  header = f"Snippet No. {i}:"
202
202
  snippet_content = s
203
- summary += (
204
- f"{header}\n"
205
- "\n"
206
- f"{snippet_content}\n"
207
- f"{SNIPPET_DELIMITER}\n"
208
- f"\n"
209
- )
203
+ summary += f"{header}\n\n{snippet_content}\n{SNIPPET_DELIMITER}\n\n"
210
204
  return summary
211
205
 
212
206
 
213
- def load_prompts(path: str | None) -> PromptConfig:
214
- """Load prompts from given yaml file if there is one.
215
- Alternatively use defaults."""
216
- if path:
207
+ def load_prompts(
208
+ config_path: Optional[str] = None, template_path: Optional[str] = None
209
+ ) -> PromptConfig | PromptManager:
210
+ """Load prompts from yaml file, and optionally initialize `PromptManager`
211
+ if provided with path to prompt templates.
212
+ """
213
+ configuration = PromptConfig()
214
+ if config_path:
217
215
  try:
218
- with open(path, "r") as file:
219
- return PromptConfig(yaml.safe_load(file))
216
+ with open(config_path, "r") as file:
217
+ configuration = PromptConfig(**yaml.safe_load(file))
220
218
  except FileNotFoundError:
221
- print("Prompt configuration file not found, reverting to defaults.")
222
- return PromptConfig()
219
+ LOG.error(
220
+ "Prompt configuration file not found, reverting to defaults.",
221
+ exc_info=True,
222
+ )
223
+ if template_path:
224
+ try:
225
+ return PromptManager(template_path, configuration)
226
+ except exceptions.TemplateError:
227
+ LOG.error(
228
+ "Prompt templates couldn't be rendered, reverting to defaults.",
229
+ exc_info=True,
230
+ )
231
+ return configuration
223
232
 
224
233
 
225
234
  def prompt_to_messages(
@@ -26,7 +26,7 @@ logdetective - Analyze and summarize log files using LLM and Drain templates
26
26
  Show usage description and exit.
27
27
 
28
28
  *-M* *MODEL*, *--model* *MODEL*::
29
- The path to the language model for analysis (if stored locally). You can also specify the model by name based on the repo on Hugging face (see Examples). Repo id must be in the form `'namespace/repo_name'`. As we are using `LLama.cpp` we want this to be in the gguf format. If the model is already on your machine it will skip the download. (optional, default: "fedora-copr/Mistral-7B-Instruct-v0.3-GGUF")
29
+ The path to the language model for analysis (if stored locally). You can also specify the model by name based on the repo on Hugging face (see Examples). Repo id must be in the form `'namespace/repo_name'`. As we are using `LLama.cpp` we want this to be in the gguf format. If the model is already on your machine it will skip the download. (optional, default: "fedora-copr/granite-3.2-8b-instruct-GGUF")
30
30
 
31
31
  *-F* *FILENAME_SUFFIX*, *--filename-suffix* *FILENAME_SUFFIX*::
32
32
  Define the suffix of the model file name to retrieve from Hugging Face. This option only applies when the model is specified by its Hugging face repo name, and not its path. (default `Q4_K.gguf`)
@@ -44,6 +44,7 @@ logdetective - Analyze and summarize log files using LLM and Drain templates
44
44
  Suppress non-essential output.
45
45
 
46
46
  *--prompts* *PROMPTS_FILE*::
47
+ (Deprecated replaced by *--prompts-config* and *--prompt-templates*)
47
48
  Path to prompt configuration file where you can customize (override default) prompts sent to the LLM. See https://github.com/fedora-copr/logdetective/blob/main/logdetective/prompts.yml for reference.
48
49
  +
49
50
  Prompts need to have a form compatible with Python format string syntax (see https://docs.python.org/3/library/string.html#format-string-syntax) with spaces, or replacement fields marked with curly braces, `{}` left for insertion of snippets. Number of replacement fields in new prompts must be the same as in original, although their position may be different.
@@ -65,6 +66,10 @@ logdetective - Analyze and summarize log files using LLM and Drain templates
65
66
 
66
67
  child_exit_code_zero: "Child return code was: 0"
67
68
 
69
+ *--prompts-config* *PROMPTS*:: Path to prompt configuration file where you can customize (override default) prompts sent to the LLM and set optional configuration. See https://github.com/fedora-copr/logdetective/blob/main/logdetective/prompts.yml for reference.
70
+
71
+ *--prompt-templates* *TEMPLATE_DIR*:: Path to prompt template dir. Prompts must be valid Jinja templates, and system prompts must include field *system_time*.
72
+
68
73
  == EXAMPLES
69
74
 
70
75
  Example usage:
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "logdetective"
3
- version = "3.0.0"
4
- description = "Log using LLM AI to search for build/test failures and provide ideas for fixing these."
3
+ version = "3.1.0"
4
+ description = "Analyze logs with a template miner and an LLM to discover errors and suggest solutions."
5
5
  authors = ["Jiri Podivin <jpodivin@gmail.com>"]
6
6
  license = "Apache-2.0"
7
7
  readme = "README.md"
@@ -10,6 +10,12 @@ include = [
10
10
  "logdetective/server/templates/gitlab_comment.md.j2",
11
11
  "logdetective/prompts.yml",
12
12
  "logdetective.1.asciidoc",
13
+ "logdetective/prompts/system_prompt.j2",
14
+ "logdetective/prompts/staged_system_prompt.j2",
15
+ "logdetective/prompts/snippet_system_prompt.j2",
16
+ "logdetective/prompts/message_template.j2",
17
+ "logdetective/prompts/staged_message_template.j2",
18
+ "logdetective/prompts/snippet_system_prompt.j2",
13
19
  ]
14
20
  packages = [
15
21
  { include = "logdetective" }
File without changes