Skip to content

Commit 70799d4

Browse files
authored
Merge pull request #3 from FullStackWithLawrence/alpha
Alpha - test PR
2 parents be6b74e + d37802e commit 70799d4

File tree

12 files changed

+158
-23
lines changed

12 files changed

+158
-23
lines changed

.env-example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ 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+
LLM_TOOL_CHOICE=required
56
LOGGING_LEVEL=20
67
MYSQL_HOST=your-mysql-host.com
78
MYSQL_PORT=3306
89
MYSQL_USER=your-username
910
MYSQL_PASSWORD=your-password
1011
MYSQL_DATABASE=your-database-name
1112
MYSQL_CHARSET=utf8mb4
12-
LLM_TOOL_CHOICE=required
13+
PYTHONPATH=./venv:./

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
# This runs on Debian Linux.
33
FROM python:3.13-slim-trixie AS base
44

5+
LABEL maintainer="Lawrence McDaniel <lpm0073@gmail.com>" \
6+
description="Docker image for the StackademyAssistent" \
7+
license="GNU AGPL v3" \
8+
vcs-url="https://github.com/FullStackWithLawrence/agentic-ai-workflow" \
9+
org.opencontainers.image.title="StackademyAssistent" \
10+
org.opencontainers.image.version="0.1.0" \
11+
org.opencontainers.image.authors="Lawrence McDaniel <lpm0073@gmail.com>" \
12+
org.opencontainers.image.url="https://FullStackWithLawrence.github.io/agentic-ai-workflow/" \
13+
org.opencontainers.image.source="https://github.com/FullStackWithLawrence/agentic-ai-workflow" \
14+
org.opencontainers.image.documentation="https://FullStackWithLawrence.github.io/agentic-ai-workflow/"
15+
516

617
FROM base AS requirements
718

app/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# -*- coding: utf-8 -*-
22
# DO NOT EDIT.
33
# Managed via automated CI/CD in .github/workflows/semanticVersionBump.yml.
4-
__version__ = "0.1.4"
4+
__version__ = "0.1.0"

app/database.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ def __init__(self):
4141
"MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE"
4242
)
4343

44+
@property
45+
def connection_string(self) -> str:
46+
"""Return the database connection string."""
47+
return f"{self.user}@{self.host}:{self.port}/{self.database}"
48+
4449
def get_connection(self) -> pymysql.Connection:
4550
"""
4651
Create and return a new MySQL connection.

app/logging_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def setup_logging(level: int = LOGGING_LEVEL) -> logging.Logger:
2727
handlers=[logging.StreamHandler(sys.stdout)], # This logs to console
2828
)
2929

30+
logging.getLogger("httpx").setLevel(logging.WARNING)
31+
3032
return logging.getLogger(__name__)
3133

3234

app/prompt.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from app.logging_config import get_logger, setup_logging
2424
from app.settings import LLM_ASSISTANT_NAME, LLM_TOOL_CHOICE
2525
from app.stackademy import stackademy_app
26-
from app.utils import dump_json_colored
26+
from app.utils import color_text, dump_json_colored
2727

2828

2929
setup_logging()
@@ -114,7 +114,8 @@ def process_tool_calls(message: ChatCompletionMessage) -> list[str]:
114114
role="assistant", content=assistant_content, tool_calls=tool_calls_param, name=LLM_ASSISTANT_NAME
115115
)
116116
)
117-
logger.info("Function call detected: %s with args %s", function_name, function_args)
117+
msg = f"Calling function: {function_name} with args {json.dumps(function_args)}"
118+
logger.info(color_text(msg, "green"))
118119

119120
function_result = handle_function_call(function_name, function_args)
120121

app/stackademy.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from app.database import db
1212
from app.exceptions import ConfigurationException
1313
from app.logging_config import get_logger, setup_logging
14+
from app.utils import color_text
1415

1516

1617
setup_logging()
@@ -67,7 +68,6 @@ def _log_success(self, message: str) -> None:
6768
def tool_factory_get_courses(self) -> ChatCompletionFunctionToolParam:
6869
"""LLM Factory function to create a tool for getting courses"""
6970
schema = StackademyGetCoursesParams.model_json_schema()
70-
schema["required"] = [] # Both parameters are optional
7171
return ChatCompletionFunctionToolParam(
7272
type="function",
7373
function={
@@ -80,7 +80,6 @@ def tool_factory_get_courses(self) -> ChatCompletionFunctionToolParam:
8080
def tool_factory_register(self) -> ChatCompletionFunctionToolParam:
8181
"""LLMFactory function to create a tool for registering a user"""
8282
schema = StackademyRegisterCourseParams.model_json_schema()
83-
schema["required"] = ["course_code", "email", "full_name"] # All parameters are required
8483
return ChatCompletionFunctionToolParam(
8584
type="function",
8685
function={
@@ -115,7 +114,7 @@ def get_courses(self, description: Optional[str] = None, max_cost: Optional[floa
115114
Returns:
116115
List[Dict[str, Any]]: List of courses matching the criteria
117116
"""
118-
# Base query
117+
119118
query = """
120119
SELECT
121120
c.course_code,
@@ -128,7 +127,6 @@ def get_courses(self, description: Optional[str] = None, max_cost: Optional[floa
128127
LEFT JOIN courses prerequisite ON c.prerequisite_id = prerequisite.course_id
129128
"""
130129

131-
# Build WHERE clause dynamically
132130
where_conditions = []
133131
params = []
134132

@@ -140,14 +138,16 @@ def get_courses(self, description: Optional[str] = None, max_cost: Optional[floa
140138
where_conditions.append("c.cost <= %s")
141139
params.append(max_cost)
142140

143-
# Add WHERE clause if we have conditions
144141
if where_conditions:
145142
query += " WHERE " + " AND ".join(where_conditions)
146143

147144
query += " ORDER BY c.prerequisite_id"
148-
logger.info("get_courses() executing db query with params: %s", params)
145+
149146
try:
150-
return self.db.execute_query(query, tuple(params))
147+
retval = self.db.execute_query(query, tuple(params))
148+
msg = f"get_courses() retrieved {len(retval)} rows from {self.db.connection_string}"
149+
logger.info(color_text(msg, "green"))
150+
return retval
151151
# pylint: disable=broad-except
152152
except Exception as e:
153153
logger.error("Failed to retrieve courses: %s", e)
@@ -190,9 +190,9 @@ def register_course(self, course_code: str, email: str, full_name: str) -> bool:
190190
if MISSING in (course_code, email, full_name):
191191
raise ConfigurationException("Missing required registration parameters.")
192192

193-
full_name = full_name.title().strip()
194-
email = email.lower().strip()
195-
course_code = course_code.upper().strip()
193+
full_name = full_name.title().strip() if isinstance(full_name, str) else full_name
194+
email = email.lower().strip() if isinstance(email, str) else email
195+
course_code = course_code.upper().strip() if isinstance(course_code, str) else course_code
196196

197197
logger.info("Registering %s (%s) for course %s...", full_name, email, course_code)
198198
if not self.verify_course(course_code):

app/utils.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,35 @@
66
import json
77

88

9+
# ANSI color codes
10+
colors = {
11+
"blue": "\033[94m", # Bright blue
12+
"green": "\033[92m", # Bright green
13+
"reset": "\033[0m", # Reset to default color
14+
}
15+
16+
17+
def color_text(text, color="blue"):
18+
"""
19+
Colors a string as blue or green.
20+
21+
Args:
22+
text (str): The string to color
23+
color (str): Color to apply - either "blue" or "green" (default: "blue")
24+
25+
Returns:
26+
str: The colored string with ANSI escape codes
27+
28+
Raises:
29+
ValueError: If color is not "blue" or "green"
30+
"""
31+
32+
if color not in ["blue", "green"]:
33+
raise ValueError("Color must be either 'blue' or 'green'")
34+
35+
return f"{colors[color]}{text}{colors['reset']}"
36+
37+
938
def dump_json_colored(data, color="reset", indent=2, sort_keys=False):
1039
"""
1140
Dumps a JSON dictionary with colored text output.
@@ -23,12 +52,6 @@ def dump_json_colored(data, color="reset", indent=2, sort_keys=False):
2352
ValueError: If color is not "blue" or "green"
2453
TypeError: If data is not JSON serializable
2554
"""
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-
}
3255

3356
if color not in ["blue", "green"]:
3457
raise ValueError("Color must be either 'blue' or 'green'")

pyproject.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
[project]
2+
name = "StackademyAI"
3+
version = "0.1.0"
4+
requires-python = ">=3.13"
5+
description = "StackademyAI: an AI-powered marketing agent"
6+
authors = [{ name = "Lawrence McDaniel", email = "lpm0073@gmail.com" }]
7+
license = { file = "LICENSE" }
8+
keywords = ["AI", "API", "Python"]
9+
readme = "README.md"
10+
classifiers = [
11+
"Programming Language :: Python :: 3",
12+
"Programming Language :: Python :: 3.12",
13+
"License :: OSI Approved :: GNU AGPL v3 or later (AGPLv3+)",
14+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
15+
"Operating System :: POSIX :: Linux",
16+
"Intended Audience :: Developers",
17+
"Framework :: Django",
18+
"Framework :: Django REST framework",
19+
"Topic :: Software Development :: Libraries :: Application Interfaces",
20+
"Topic :: Software Development :: Libraries :: API",
21+
"Natural Language :: English",
22+
"Development Status :: 4 - Beta"
23+
]
24+
125
[tool.isort]
226
profile = "black"
327
lines_after_imports = 2

release.config.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,23 @@ module.exports = {
1010
},
1111
],
1212
"@semantic-release/github",
13+
[
14+
"@semantic-release/exec",
15+
{
16+
prepareCmd: "python scripts/bump_version.py ${nextRelease.version}",
17+
},
18+
],
1319
[
1420
"@semantic-release/git",
1521
{
1622
assets: [
1723
"CHANGELOG.md",
18-
"client/package.json",
19-
"client/package-lock.json",
2024
"requirements/prod.txt",
25+
"app/__version__.py",
26+
"pyproject.toml",
27+
"Dockerfile",
28+
"package.json",
29+
"package-lock.json",
2130
],
2231
message:
2332
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",

0 commit comments

Comments
 (0)