Skip to content

Commit 181d7d0

Browse files
authored
feat: add -v option and improve logging (#146)
We can now manage verbosity via a `-v` flag in the cli. There's been also a minor refactor, that deletes de Settings class while keeping the same functionality, and some other minor changes.
1 parent 6553954 commit 181d7d0

File tree

24 files changed

+161
-251
lines changed

24 files changed

+161
-251
lines changed

.env.example

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
OPENAI_API_KEY=key
2-
GOOGLE_API_KEY=key
1+
# Agent keys
2+
OPENAI_API_KEY=
3+
GOOGLE_API_KEY=
34

45
# SERVICE_DESK Integration
56
SERVICE_DESK_URL=
67
SERVICE_DESK_USER=
78
SERVICE_DESK_TOKEN=
89

10+
# Sentry configuration
11+
SENTRY_DSN=
12+
913
# Your time zone. Defaults to UTC.
10-
TIME_ZONE=
14+
TIME_ZONE=
15+
16+
# Logging level
17+
LOG_LEVEL=

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Lightman AI is an intelligent cybersecurity news aggregation and risk assessment
8181
agent = "openai"
8282
score_threshold = 8
8383
prompt = "development"
84+
log_level = "INFO"
8485
8586
[prompts]
8687
development = "Analyze cybersecurity news for relevance to our organization."' > lightman.toml
@@ -129,6 +130,8 @@ Lightman AI is an intelligent cybersecurity news aggregation and risk assessment
129130
| `--start-date` | Start date to retrieve articles | False |
130131
| `--today` | Retrieve articles from today | False |
131132
| `--yesterday` | Retrieve articles from yesterday | False |
133+
| `-v` | Be more verbose on output | False |
134+
132135

133136
### Environment Variables:
134137
lightman-ai uses the following environment variables:

eval/cli.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import click
22
from dotenv import load_dotenv
33
from lightman_ai.ai.utils import AGENT_CHOICES
4-
from lightman_ai.constants import DEFAULT_CONFIG_FILE, DEFAULT_ENV_FILE
4+
from lightman_ai.constants import DEFAULT_AGENT, DEFAULT_CONFIG_FILE, DEFAULT_ENV_FILE, DEFAULT_SCORE
55
from lightman_ai.core.config import PromptConfig
66

77
from eval.classified_articles import NON_RELEVANT_ARTICLES, RELEVANT_ARTICLES
88
from eval.constants import DEFAULT_EVAL_CONFIG_SECTION
99
from eval.evaluator import eval
10-
from eval.utils import EvalConfig, EvalFileConfig, init_eval_settings
10+
from eval.utils import PARALLEL_WORKERS, EvalConfig, EvalFileConfig
1111

1212

1313
@click.command()
@@ -59,14 +59,14 @@ def run(
5959
model: str | None = None,
6060
) -> None:
6161
load_dotenv(env_file)
62-
settings = init_eval_settings(env_file)
62+
6363
config_from_file = EvalFileConfig.get_config_from_file(config_section=config, path=config_file)
6464
configured_prompts = PromptConfig.get_config_from_file(path=prompt_file)
6565
eval_config = EvalConfig.init_from_dict(
6666
{
67-
"agent": agent or config_from_file.agent or settings.AGENT,
67+
"agent": agent or config_from_file.agent or DEFAULT_AGENT,
6868
"prompt": prompt or config_from_file.prompt,
69-
"score_threshold": score or config_from_file.score_threshold or settings.SCORE,
69+
"score_threshold": score or config_from_file.score_threshold or DEFAULT_SCORE,
7070
"samples": samples or config_from_file.samples,
7171
"model": model or config_from_file.model,
7272
}
@@ -81,7 +81,7 @@ def run(
8181
agent=eval_config.agent,
8282
prompt=configured_prompts.get_prompt(eval_config.prompt),
8383
model=eval_config.model,
84-
parallel_workers=settings.PARALLEL_WORKERS,
84+
parallel_workers=PARALLEL_WORKERS,
8585
)
8686

8787

eval/evaluator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def eval(
4141
prompt=prompt,
4242
score=score_threshold,
4343
logger=logger,
44-
model=model or agent_class._default_model_name,
44+
model=model or agent_class._DEFAULT_MODEL_NAME,
4545
)
4646

4747
results_template.save()

eval/utils.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,12 @@
22
from collections.abc import Iterable
33
from dataclasses import dataclass
44
from decimal import Decimal
5-
from typing import Any
65

76
from lightman_ai.article.models import SelectedArticle
87
from lightman_ai.core.config import FileConfig, FinalConfig
9-
from lightman_ai.core.settings import Settings
108
from pydantic import PositiveInt
119

12-
13-
class EvalSettings(Settings):
14-
def __init__(self, *args: Any, **kwargs: Any) -> None:
15-
super().__init__(*args, **kwargs)
16-
17-
PARALLEL_WORKERS: int = 5
18-
19-
20-
def init_eval_settings(env_file: str | None = None) -> EvalSettings:
21-
return EvalSettings(_env_file=env_file)
10+
PARALLEL_WORKERS: int = 5
2211

2312

2413
class EvalConfig(FinalConfig):

src/lightman_ai/ai/base/agent.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from abc import ABC, abstractmethod
3-
from typing import Never
3+
from typing import ClassVar, Never, override
44

55
from lightman_ai.article.models import SelectedArticlesList
66
from pydantic_ai import Agent
@@ -9,18 +9,22 @@
99

1010

1111
class BaseAgent(ABC):
12-
_class: type[OpenAIChatModel] | type[GoogleModel]
13-
_default_model_name: str
12+
_AGENT_CLASS: type[OpenAIChatModel] | type[GoogleModel]
13+
_DEFAULT_MODEL_NAME: str
14+
_AGENT_NAME: ClassVar[str]
1415

1516
def __init__(self, system_prompt: str, model: str | None = None, logger: logging.Logger | None = None) -> None:
16-
agent_model = self._class(model or self._default_model_name)
17+
selected_model = model or self._DEFAULT_MODEL_NAME
18+
agent_model = self._AGENT_CLASS(selected_model)
1719
self.agent: Agent[Never, SelectedArticlesList] = Agent(
1820
model=agent_model, output_type=SelectedArticlesList, system_prompt=system_prompt
1921
)
2022
self.logger = logger or logging.getLogger("lightman")
23+
self.logger.info("Selected %s's %s model", self, selected_model)
2124

22-
def get_prompt_result(self, prompt: str) -> SelectedArticlesList:
23-
return self._run_prompt(prompt)
25+
@override
26+
def __str__(self) -> str:
27+
return self._AGENT_NAME
2428

2529
@abstractmethod
26-
def _run_prompt(self, prompt: str) -> SelectedArticlesList: ...
30+
def run_prompt(self, prompt: str) -> SelectedArticlesList: ...

src/lightman_ai/ai/gemini/agent.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
class GeminiAgent(BaseAgent):
1010
"""Class that provides an interface to operate with the Gemini model."""
1111

12-
_class = GoogleModel
13-
_default_model_name = "gemini-2.5-pro"
12+
_AGENT_CLASS = GoogleModel
13+
_DEFAULT_MODEL_NAME = "gemini-2.5-pro"
14+
_AGENT_NAME = "Gemini"
1415

1516
@override
16-
def _run_prompt(self, prompt: str) -> SelectedArticlesList:
17+
def run_prompt(self, prompt: str) -> SelectedArticlesList:
1718
with map_gemini_exceptions():
1819
result = self.agent.run_sync(prompt)
1920
return result.output

src/lightman_ai/ai/openai/agent.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@
1111
class OpenAIAgent(BaseAgent):
1212
"""Class that provides an interface to operate with the OpenAI model."""
1313

14-
_class = OpenAIChatModel
15-
_default_model_name = "gpt-4.1"
14+
_AGENT_CLASS = OpenAIChatModel
15+
_DEFAULT_MODEL_NAME = "gpt-4.1"
16+
_AGENT_NAME = "OpenAI"
1617

1718
def _execute_agent(self, prompt: str) -> AgentRunResult[SelectedArticlesList]:
1819
with map_openai_exceptions():
1920
return self.agent.run_sync(prompt)
2021

2122
@override
22-
def _run_prompt(self, prompt: str) -> SelectedArticlesList:
23+
def run_prompt(self, prompt: str) -> SelectedArticlesList:
2324
try:
2425
result = self._execute_agent(prompt)
2526
except LimitTokensExceededError as err:

src/lightman_ai/cli.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
11
import logging
2+
import os
23
from datetime import date
34
from importlib import metadata
45

56
import click
67
from dotenv import load_dotenv
78
from lightman_ai.ai.utils import AGENT_CHOICES
8-
from lightman_ai.constants import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_SECTION, DEFAULT_ENV_FILE
9+
from lightman_ai.constants import (
10+
DEFAULT_AGENT,
11+
DEFAULT_CONFIG_FILE,
12+
DEFAULT_CONFIG_SECTION,
13+
DEFAULT_ENV_FILE,
14+
DEFAULT_LOG_LEVEL,
15+
DEFAULT_SCORE,
16+
DEFAULT_TIME_ZONE,
17+
VERBOSE_LOG_LEVEL,
18+
)
919
from lightman_ai.core.config import FileConfig, FinalConfig, PromptConfig
1020
from lightman_ai.core.exceptions import ConfigNotFoundError, InvalidConfigError, PromptNotFoundError
1121
from lightman_ai.core.sentry import configure_sentry
12-
from lightman_ai.core.settings import Settings
1322
from lightman_ai.exceptions import MultipleDateSourcesError
1423
from lightman_ai.main import lightman
1524
from lightman_ai.utils import get_start_date
1625

1726
logger = logging.getLogger("lightman")
27+
logging.basicConfig(
28+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
29+
datefmt="%Y-%m-%d %H:%M:%S",
30+
)
1831

1932

2033
def get_version() -> str:
@@ -72,6 +85,7 @@ def entry_point() -> None:
7285
@click.option("--start-date", type=click.DateTime(formats=["%Y-%m-%d"]), help="Start date to retrieve articles")
7386
@click.option("--today", is_flag=True, help="Retrieve articles from today.")
7487
@click.option("--yesterday", is_flag=True, help="Retrieve articles from yesterday.")
88+
@click.option("-v", is_flag=True, help="Be more verbose on output.")
7589
def run(
7690
agent: str,
7791
prompt: str,
@@ -85,18 +99,30 @@ def run(
8599
start_date: date | None,
86100
today: bool,
87101
yesterday: bool,
102+
v: bool,
88103
) -> int:
89104
"""
90105
Entrypoint of the application.
91106
92107
Holds no logic. It loads the configuration, calls the main method and returns 0 when succesful .
93108
"""
94109
load_dotenv(env_file or DEFAULT_ENV_FILE)
95-
configure_sentry()
96110

97-
settings = Settings.try_load_from_file(env_file)
111+
if v:
112+
logger.setLevel(VERBOSE_LOG_LEVEL)
113+
else:
114+
try:
115+
env_log_level = os.getenv("LOG_LEVEL")
116+
log_level = env_log_level.upper() if env_log_level else DEFAULT_LOG_LEVEL
117+
logger.setLevel(log_level)
118+
except ValueError:
119+
logger.setLevel(DEFAULT_LOG_LEVEL)
120+
logger.warning("Invalid logging level. Using default value.")
121+
122+
configure_sentry(logger.level)
123+
98124
try:
99-
start_datetime = get_start_date(settings, yesterday, today, start_date)
125+
start_datetime = get_start_date(os.getenv("TIME_ZONE", DEFAULT_TIME_ZONE), yesterday, today, start_date)
100126
except MultipleDateSourcesError as e:
101127
raise click.UsageError(e.args[0]) from e
102128

@@ -105,9 +131,9 @@ def run(
105131
config_from_file = FileConfig.get_config_from_file(config_section=config, path=config_file)
106132
final_config = FinalConfig.init_from_dict(
107133
data={
108-
"agent": agent or config_from_file.agent or settings.AGENT,
134+
"agent": agent or config_from_file.agent or DEFAULT_AGENT,
109135
"prompt": prompt or config_from_file.prompt,
110-
"score_threshold": score or config_from_file.score_threshold or settings.SCORE,
136+
"score_threshold": score or config_from_file.score_threshold or DEFAULT_SCORE,
111137
"model": model or config_from_file.model,
112138
}
113139
)
@@ -126,5 +152,11 @@ def run(
126152
start_date=start_datetime,
127153
)
128154
relevant_articles_metadata = [f"{article.title} ({article.link})" for article in relevant_articles]
129-
logger.warning("Found these articles: \n- %s", "\n- ".join(relevant_articles_metadata))
155+
156+
if relevant_articles_metadata:
157+
articles = f"Found these articles:\n* {'\n* '.join(relevant_articles_metadata)} "
158+
click.echo(click.style(articles))
159+
else:
160+
click.echo(click.style("No relevant articles found."))
161+
130162
return 0

src/lightman_ai/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
DEFAULT_CONFIG_FILE = "lightman.toml"
44

55
DEFAULT_ENV_FILE = ".env"
6+
7+
DEFAULT_LOG_LEVEL = "WARNING"
8+
VERBOSE_LOG_LEVEL = "INFO"
9+
10+
DEFAULT_AGENT = "openai"
11+
DEFAULT_SCORE = 8
12+
DEFAULT_TIME_ZONE = "UTC"

0 commit comments

Comments
 (0)