Skip to content

Commit 37a1fba

Browse files
committed
chore: add logging level to .env
1 parent a0204c7 commit 37a1fba

File tree

8 files changed

+85
-14
lines changed

8 files changed

+85
-14
lines changed

.env-example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ OPENAI_API_KEY=ADD-YOUR-OPENAI_API_KEY-HERE
22
ENVIRONMENT=dev
33
DOCKERHUB_USERNAME=ADD-YOUR-DOCKERHUB_USERNAME-HERE
44
DOCKERHUB_ACCESS_TOKEN=ADD-YOUR-DOCKERHUB_ACCESS_TOKEN-HERE
5-
6-
# MySQL Database Configuration
5+
LOGGING_LEVEL=20
76
MYSQL_HOST=your-mysql-host.com
87
MYSQL_PORT=3306
98
MYSQL_USER=your-username
109
MYSQL_PASSWORD=your-password
1110
MYSQL_DATABASE=your-database-name
1211
MYSQL_CHARSET=utf8mb4
12+
TOOL_CHOICE=required

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ docker-push:
8181
docker push ${DOCKERHUB_USERNAME}/${REPO_NAME}:latest
8282

8383
docker-run:
84-
docker run -it -e OPENAI_API_KEY=${OPENAI_API_KEY} -e ENVIRONMENT=prod -e MYSQL_HOST=${MYSQL_HOST} -e MYSQL_PORT=${MYSQL_PORT} -e MYSQL_USER=${MYSQL_USER} -e MYSQL_PASSWORD=${MYSQL_PASSWORD} -e MYSQL_DATABASE=${MYSQL_DATABASE} -e MYSQL_CHARSET=${MYSQL_CHARSET} ${DOCKERHUB_USERNAME}/${REPO_NAME}:latest
84+
docker run -it -e OPENAI_API_KEY=${OPENAI_API_KEY} -e ENVIRONMENT=prod -e MYSQL_HOST=${MYSQL_HOST} -e MYSQL_PORT=${MYSQL_PORT} -e MYSQL_USER=${MYSQL_USER} -e MYSQL_PASSWORD=${MYSQL_PASSWORD} -e MYSQL_DATABASE=${MYSQL_DATABASE} -e MYSQL_CHARSET=${MYSQL_CHARSET} -e LOGGING_LEVEL=${LOGGING_LEVEL} -e TOOL_CHOICE=${TOOL_CHOICE} ${DOCKERHUB_USERNAME}/${REPO_NAME}:latest
8585

8686
docker-prune:
8787
@if [ "`docker ps -aq`" ]; then \

app/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def main(prompts: Optional[Tuple[str, ...]] = None) -> None:
2323
user_prompt = prompts[i] if prompts else input("Welcome to Stackademy! How can I assist you today? ")
2424

2525
response, functions_called = completion(prompt=user_prompt)
26-
while response.choices[0].message.content != "Goodbye!":
26+
while response and response.choices[0].message.content != "Goodbye!":
2727
i += 1
2828
message = response.choices[0].message
2929
response_message = message.content or ""

app/const.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,12 @@
22
"""Constants for the Stackademy application."""
33

44
MISSING = "MISSING"
5+
6+
7+
class ToolChoice:
8+
"""Enumeration for tool choice strategies."""
9+
10+
AUTO = "auto"
11+
REQUIRED = "required"
12+
NONE = "none"
13+
GET_COURSES = {"type": "function", "function": {"name": "get_courses"}}

app/logging_config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import logging
77
import sys
88

9+
from app.settings import LOGGING_LEVEL
910

10-
def setup_logging(level: int = logging.INFO) -> logging.Logger:
11+
12+
def setup_logging(level: int = LOGGING_LEVEL) -> logging.Logger:
1113
"""
1214
Configure logging for the application.
1315

app/prompt.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
import json
8-
from typing import Union
8+
from typing import Optional, Union
99

1010
import openai
1111
from openai.types.chat import (
@@ -19,9 +19,11 @@
1919
)
2020

2121
from app import settings
22-
from app.const import MISSING
22+
from app.const import MISSING, ToolChoice
2323
from app.logging_config import get_logger, setup_logging
24+
from app.settings import TOOL_CHOICE
2425
from app.stackademy import stackademy_app
26+
from app.utils import dump_json_colored
2527

2628

2729
setup_logging()
@@ -120,12 +122,13 @@ def process_tool_calls(message: ChatCompletionMessage) -> list[str]:
120122
messages.append(tool_message)
121123

122124
logger.debug(
123-
"Updated messages: %s", [msg.model_dump() if not isinstance(msg, dict) else msg for msg in messages]
125+
"Updated messages: %s",
126+
[dump_json_colored(msg.model_dump(), "blue") if not isinstance(msg, dict) else msg for msg in messages],
124127
)
125128
return functions_called
126129

127130

128-
def completion(prompt: str) -> tuple[ChatCompletion, list[str]]:
131+
def completion(prompt: str) -> tuple[Optional[ChatCompletion], list[str]]:
129132
"""LLM text completion"""
130133

131134
def handle_completion(tools, tool_choice) -> ChatCompletion:
@@ -134,6 +137,11 @@ def handle_completion(tools, tool_choice) -> ChatCompletion:
134137
model = settings.OPENAI_API_MODEL
135138

136139
try:
140+
logger.debug(
141+
"Sending messages to OpenAI: %s %s",
142+
dump_json_colored(messages, "blue"),
143+
dump_json_colored(tools, "blue"),
144+
)
137145
response = openai.chat.completions.create(
138146
model=model,
139147
messages=messages,
@@ -142,7 +150,7 @@ def handle_completion(tools, tool_choice) -> ChatCompletion:
142150
temperature=settings.OPENAI_API_TEMPERATURE,
143151
max_tokens=settings.OPENAI_API_MAX_TOKENS,
144152
)
145-
logger.debug("OpenAI response: %s", response.model_dump())
153+
logger.debug("OpenAI response: %s", dump_json_colored(response.model_dump(), "green"))
146154
return response
147155
except openai.RateLimitError as e:
148156
logger.error("OpenAI rate limit exceeded: %s", e)
@@ -164,15 +172,19 @@ def handle_completion(tools, tool_choice) -> ChatCompletion:
164172
logger.error("Unexpected error during OpenAI completion: %s", e)
165173
raise
166174

175+
if not prompt.strip():
176+
logger.warning("Received empty prompt.")
177+
return None, []
178+
167179
messages.append(ChatCompletionUserMessageParam(role="user", content=prompt))
168180
functions_called = []
169181

170182
response = handle_completion(
171183
# tool_choice={"type": "function", "function": {"name": "get_courses"}},
172-
tool_choice="required",
184+
tool_choice=TOOL_CHOICE,
173185
tools=[stackademy_app.tool_factory_get_courses()],
174186
)
175-
logger.debug("Initial response: %s", response.model_dump())
187+
logger.debug("Initial response: %s", dump_json_colored(response.model_dump(), "green"))
176188

177189
message = response.choices[0].message
178190
while message.tool_calls:
@@ -182,9 +194,9 @@ def handle_completion(tools, tool_choice) -> ChatCompletion:
182194

183195
response = handle_completion(
184196
tools=[stackademy_app.tool_factory_get_courses(), stackademy_app.tool_factory_register()],
185-
tool_choice="auto",
197+
tool_choice=ToolChoice.AUTO,
186198
)
187199
message = response.choices[0].message
188-
logger.debug("Updated response: %s", response.model_dump())
200+
logger.debug("Updated response: %s", dump_json_colored(response.model_dump(), "green"))
189201

190202
return response, functions_called

app/settings.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
# -*- coding: utf-8 -*-
22
"""Settings for the app."""
33

4+
import logging
45
import os
56

67
from dotenv import load_dotenv
78

9+
from app.const import ToolChoice
810
from app.exceptions import ConfigurationException
911

1012

1113
# Load environment variables from .env file if available
1214
load_dotenv()
1315

16+
# General settings
17+
LOGGING_LEVEL = int(os.getenv("LOGGING_LEVEL", str(logging.INFO)))
18+
TOOL_CHOICE = os.getenv("TOOL_CHOICE", ToolChoice.REQUIRED)
19+
20+
1421
# OpenAI API settings
1522
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "SET-ME-PLEASE")
1623
OPENAI_API_MODEL = os.getenv("OPENAI_API_MODEL", "gpt-4o-mini")

app/utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Utility functions for Stackademy.
4+
"""
5+
6+
import json
7+
8+
9+
def dump_json_colored(data, color="reset", indent=2, sort_keys=False):
10+
"""
11+
Dumps a JSON dictionary with colored text output.
12+
13+
Args:
14+
data: Dictionary or JSON-serializable object to dump
15+
color: Color for the text output ("blue" or "green")
16+
indent: Number of spaces for JSON indentation (default: 2)
17+
sort_keys: Whether to sort dictionary keys (default: True)
18+
19+
Returns:
20+
str: Colored JSON string
21+
22+
Raises:
23+
ValueError: If color is not "blue" or "green"
24+
TypeError: If data is not JSON serializable
25+
"""
26+
# ANSI color codes
27+
colors = {
28+
"blue": "\033[94m", # Bright blue
29+
"green": "\033[92m", # Bright green
30+
"reset": "\033[0m", # Reset to default color
31+
}
32+
33+
if color not in ["blue", "green"]:
34+
raise ValueError("Color must be either 'blue' or 'green'")
35+
36+
try:
37+
json_str = json.dumps(data, indent=indent, sort_keys=sort_keys, ensure_ascii=False)
38+
colored_json = f"{colors[color]}{json_str}{colors['reset']}"
39+
return colored_json
40+
except (TypeError, ValueError) as e:
41+
raise TypeError(f"Data is not JSON serializable: {e}") from e

0 commit comments

Comments
 (0)