Skip to content

Commit eacc29f

Browse files
committed
feat(devtools): add devenv/direnv, default linting and formatting & scripts for build/scan vuln
1 parent dc3d6b2 commit eacc29f

13 files changed

Lines changed: 275 additions & 40 deletions

File tree

.envrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
3+
eval "$(devenv direnvrc)"
4+
5+
# You can pass flags to the devenv command
6+
# For example: use devenv --impure --option services.postgres.enable:bool true
7+
use devenv

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,19 @@ charts/*/charts
2121
**/flagged/
2222
web-apps/**/overrides.yml
2323
**/.env
24+
25+
# devenv
26+
.devenv
27+
.devenv.flake.nix
28+
devenv.lock
29+
30+
# Devenv
31+
.devenv*
32+
devenv.local.nix
33+
devenv.local.yaml
34+
35+
# direnv
36+
.direnv
37+
38+
# pre-commit
39+
.pre-commit-config.yaml

devenv.nix

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{ pkgs, ... }:
2+
let
3+
allComponents = "chat image-analysis flux-image-gen";
4+
imagePrefix = "ghcr.io/stackhpc/azimuth-llm";
5+
webAppsDir = "./web-apps";
6+
7+
# Resolves "all" or empty arg to the full list, validates otherwise.
8+
resolveComponents = ''
9+
ALL_COMPONENTS="${allComponents}"
10+
11+
resolve_components() {
12+
local input="$1"
13+
if [ -z "$input" ] || [ "$input" = "all" ]; then
14+
echo "$ALL_COMPONENTS"
15+
else
16+
for c in $input; do
17+
if ! echo " $ALL_COMPONENTS " | grep -q " $c "; then
18+
echo "Unknown component: $c" >&2
19+
echo "Available: $ALL_COMPONENTS" >&2
20+
return 1
21+
fi
22+
done
23+
echo "$input"
24+
fi
25+
}
26+
27+
image_name() {
28+
echo "${imagePrefix}-''${1}-ui"
29+
}
30+
'';
31+
in
32+
{
33+
env.GREET = "azimuth-llm dev environment";
34+
35+
packages = with pkgs; [
36+
git
37+
# Container
38+
grype
39+
# Helm / K8s
40+
kubernetes-helm
41+
chart-testing
42+
kind
43+
kubectl
44+
# CI tooling
45+
jq
46+
yq-go
47+
# Python
48+
python311
49+
ruff
50+
black
51+
];
52+
53+
treefmt = {
54+
enable = true;
55+
config.programs = {
56+
nixfmt.enable = true;
57+
black.enable = true;
58+
};
59+
};
60+
61+
git-hooks.hooks = {
62+
treefmt = {
63+
enable = true;
64+
settings.fail-on-change = false;
65+
};
66+
};
67+
68+
difftastic.enable = true;
69+
70+
scripts = {
71+
build.exec = ''
72+
${resolveComponents}
73+
TAG="latest"
74+
COMPONENT=""
75+
while [ $# -gt 0 ]; do
76+
case "$1" in
77+
--tag) TAG="$2"; shift 2 ;;
78+
*) COMPONENT="$COMPONENT $1"; shift ;;
79+
esac
80+
done
81+
COMPONENT="''${COMPONENT## }"
82+
83+
TARGETS=$(resolve_components "$COMPONENT") || exit 1
84+
for c in $TARGETS; do
85+
echo "==> Building $c (tag: $TAG)"
86+
docker build \
87+
-t "$(image_name "$c"):$TAG" \
88+
-f ${webAppsDir}/"$c"/Dockerfile \
89+
${webAppsDir}/
90+
done
91+
'';
92+
93+
scan.exec = ''
94+
${resolveComponents}
95+
TAG="latest"
96+
FAIL_ON="critical"
97+
COMPONENT=""
98+
while [ $# -gt 0 ]; do
99+
case "$1" in
100+
--tag) TAG="$2"; shift 2 ;;
101+
--fail-on) FAIL_ON="$2"; shift 2 ;;
102+
*) COMPONENT="$COMPONENT $1"; shift ;;
103+
esac
104+
done
105+
COMPONENT="''${COMPONENT## }"
106+
107+
TARGETS=$(resolve_components "$COMPONENT") || exit 1
108+
EXIT=0
109+
for c in $TARGETS; do
110+
build "$c" --tag "$TAG"
111+
112+
IMAGE="$(image_name "$c"):$TAG"
113+
echo ""
114+
echo "==> Scanning $IMAGE (fail-on: $FAIL_ON)"
115+
if ! grype "$IMAGE" --fail-on "$FAIL_ON" --only-fixed; then
116+
EXIT=1
117+
fi
118+
done
119+
exit $EXIT
120+
'';
121+
};
122+
123+
enterShell = ''
124+
echo "$GREET"
125+
echo ""
126+
echo "Commands (component = chat | image-analysis | flux-image-gen | omit for all):"
127+
echo ""
128+
echo " prek -a Format/lint all files"
129+
echo " build [component] [--tag TAG] Build container image(s)"
130+
echo " scan [component] [--tag TAG] [--fail-on SEV] Build if needed + Grype scan"
131+
echo ""
132+
'';
133+
}

devenv.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
inputs:
2+
git-hooks:
3+
url: github:cachix/git-hooks.nix
4+
inputs:
5+
nixpkgs:
6+
follows: nixpkgs
7+
nixpkgs:
8+
url: github:NixOS/nixpkgs/nixpkgs-unstable
9+
treefmt-nix:
10+
url: github:numtide/treefmt-nix
11+
inputs:
12+
nixpkgs:
13+
follows: nixpkgs

scripts/perf-test/stress.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
prompts = [
1717
"Hi, how are you?",
1818
"What's the weather like with you?",
19-
"Who's the best footballer of all time?"
19+
"Who's the best footballer of all time?",
2020
]
2121

2222
client_count = 3
23-
request_count = 5 # Requests per client
23+
request_count = 5 # Requests per client
24+
2425

2526
def make_requests(client_id: int):
2627
client = Client(url)
@@ -32,7 +33,12 @@ def make_requests(client_id: int):
3233
timings.append(time.time() - start_time)
3334
return timings
3435

35-
results = list(Parallel(n_jobs=client_count)(delayed(make_requests)(i) for i in range(1, client_count+1)))
36+
37+
results = list(
38+
Parallel(n_jobs=client_count)(
39+
delayed(make_requests)(i) for i in range(1, client_count + 1)
40+
)
41+
)
3642
all_timings = []
3743
for client_timings in results:
3844
all_timings += client_timings

web-apps/chat/app.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,26 @@ class PossibleSystemPromptException(Exception):
6262
streaming=True,
6363
)
6464

65+
6566
def inference(latest_message, history):
6667
# Allow mutating global variable
6768
global BACKEND_INITIALISED
6869
log.debug("Inference request received with history: %s", history)
6970

7071
try:
7172
context = []
72-
model_instruction = settings.model_instruction.replace("{date}", f"{date.today()}")
73+
model_instruction = settings.model_instruction.replace(
74+
"{date}", f"{date.today()}"
75+
)
7376
if INCLUDE_SYSTEM_PROMPT:
7477
context.append(SystemMessage(content=model_instruction))
7578
elif history and len(history) > 0:
7679
# Mimic system prompt by prepending it to first human message
77-
history[0]['content'] = f"{model_instruction}\n\n{history[0]['content']}"
80+
history[0]["content"] = f"{model_instruction}\n\n{history[0]['content']}"
7881

7982
for message in history:
80-
role = message['role']
81-
content = message['content']
83+
role = message["role"]
84+
content = message["content"]
8285
if role == "user":
8386
context.append(HumanMessage(content=content))
8487
else:
@@ -102,10 +105,10 @@ def inference(latest_message, history):
102105
# The "think" tags mark the chatbot's reasoning. Remove the content
103106
# and replace with "Thinking..." until the closing tag is found.
104107
content = chunk.content
105-
if '<think>' in content or thinking:
108+
if "<think>" in content or thinking:
106109
thinking = True
107110
response = "Thinking..."
108-
if '</think>' in content:
111+
if "</think>" in content:
109112
thinking = False
110113
response = ""
111114
else:
@@ -175,7 +178,7 @@ def inference_wrapper(*args):
175178
js=settings.custom_javascript,
176179
title=settings.page_title,
177180
) as demo:
178-
gr.Markdown('# ' + settings.page_title)
181+
gr.Markdown("# " + settings.page_title)
179182
gr.ChatInterface(
180183
inference_wrapper,
181184
type="messages",
@@ -187,10 +190,10 @@ def inference_wrapper(*args):
187190
sanitize_html=True,
188191
autoscroll=False,
189192
latex_delimiters=[
190-
{"left": "$$", "right": "$$", "display": True },
191-
{"left": "$", "right": "$", "display": False }
192-
],
193-
),
193+
{"left": "$$", "right": "$$", "display": True},
194+
{"left": "$", "right": "$", "display": False},
195+
],
196+
),
194197
)
195198

196199

web-apps/chat/gradio-client-test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
gradio_host = sys.argv[1]
77

88
retries = 60
9-
for n in range(1, retries+1):
9+
for n in range(1, retries + 1):
1010
try:
1111
client = Client(gradio_host)
1212
result = client.predict("Hi", api_name="/chat")

web-apps/chat/test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
url = os.environ.get("GRADIO_URL", "http://localhost:7860")
88
client = Client(url)
99

10+
1011
class TestSuite(unittest.TestCase):
1112

1213
def test_gradio_api(self):
@@ -19,5 +20,6 @@ def test_gradio_api(self):
1920
# # mock_response.assert_called_once_with("Hi", [])
2021
# self.assertEqual(result, "Mocked")
2122

23+
2224
if __name__ == "__main__":
2325
unittest.main()

web-apps/flux-image-gen/api_server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ class ImageGenInput(BaseModel):
3131
prompt: str
3232
add_sampling_metadata: bool
3333

34+
3435
@app.get("/")
3536
def health_check():
3637
return "Server is running"
3738

39+
3840
@app.get("/model")
3941
async def get_model():
4042
return {"model": model}
@@ -61,7 +63,9 @@ async def generate_image(input: ImageGenInput):
6163
add_sampling_metadata=input.add_sampling_metadata,
6264
)
6365
if not image:
64-
return JSONResponse({"error": {"message": msg, "seed": seed}}, status_code=400)
66+
return JSONResponse(
67+
{"error": {"message": msg, "seed": seed}}, status_code=400
68+
)
6569
# Convert image to bytes response
6670
buffer = io.BytesIO()
6771
image.save(buffer, format="jpeg")

0 commit comments

Comments
 (0)