Skip to content

Commit 1f4f7fc

Browse files
authored
Enable cost tracking & update batch jobs (#7)
1 parent c58e207 commit 1f4f7fc

File tree

7 files changed

+386
-33
lines changed

7 files changed

+386
-33
lines changed

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ authors = [
99
dependencies = [
1010
"aiodocker==0.24.0",
1111
"fhaviary[server]==0.18.1",
12-
"fh-llm-client==0.0.11",
12+
"fhlmi==0.26.0",
1313
"ldp==0.23.0",
1414
"pandas==2.2.3",
1515
"numpy==2.2.3",
@@ -22,11 +22,12 @@ dependencies = [
2222
"google-auth==2.38.0",
2323
"google-cloud-storage==3.0.0",
2424
"google-cloud-secret-manager==2.23.0",
25-
"crow-client>=0.3.13",
25+
"crow-client>=0.3.14",
2626
"jupyter==1.1.1",
2727
"nbconvert==7.16.6",
2828
"notebook==7.3.2",
29-
"nbformat==5.10.4"
29+
"nbformat==5.10.4",
30+
"pydeseq2==0.5.0"
3031
]
3132
description = "Data analysis crow"
3233
name = "fhda"
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# syntax=docker/dockerfile:1.4
2+
FROM python:3.12-slim AS base
3+
4+
WORKDIR /app
5+
ENV PYTHONUNBUFFERED=1
6+
ENV DEBIAN_FRONTEND=noninteractive
7+
8+
RUN --mount=type=cache,target=/var/cache/apt \
9+
apt-get update -qq && \
10+
apt-get install -yq --no-install-recommends \
11+
git \
12+
openssh-client \
13+
wget \
14+
gpg \
15+
software-properties-common \
16+
build-essential && \
17+
apt-get clean && rm -rf /var/lib/apt/lists/*
18+
19+
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
20+
chmod +x ~/miniconda.sh && \
21+
bash ~/miniconda.sh -b -p /app/miniconda && \
22+
rm ~/miniconda.sh && \
23+
/app/miniconda/bin/conda init bash
24+
25+
# Set environment variables to point to conda environment
26+
ENV VIRTUAL_ENV="/app/miniconda/bin"
27+
ENV PATH="/app/miniconda/bin:$PATH"
28+
ENV PYTHONPATH="/app/miniconda/lib/python3.12/site-packages:${PYTHONPATH:-}"
29+
30+
# Install uv & mamba
31+
RUN pip3 install --no-cache-dir uv==0.5.21
32+
RUN conda install -c conda-forge mamba -y
33+
34+
35+
# Install R and kernels in the crow_env environment
36+
RUN mamba install -c conda-forge -y \
37+
r-base=4.3.3 \
38+
r-recommended=4.3 \
39+
r-irkernel=1.3.2 \
40+
r-factominer=2.11 \
41+
r-rcolorbrewer=1.1_3 \
42+
r-devtools=2.4.5 \
43+
r-broom=1.0.7 \
44+
r-data.table=1.15.4 \
45+
r-enrichr=3.2 \
46+
r-factoextra=1.0.7 \
47+
r-ggnewscale=0.5.0 \
48+
r-ggrepel=0.9.6 \
49+
r-ggpubr=0.6.0 \
50+
r-ggvenn=0.1.10 \
51+
r-janitor=2.2.1 \
52+
r-multcomp=1.4_26 \
53+
r-matrix=1.6_5 \
54+
r-pheatmap=1.0.12 \
55+
r-tidyverse=2.0.0 \
56+
r-readxl=1.4.3 \
57+
r-reshape=0.8.9 \
58+
r-rstatix=0.7.2 \
59+
r-viridis=0.6.5 \
60+
udocker=1.3.17 \
61+
imbalanced-learn=0.13.0 \
62+
ipykernel=6.29.5 \
63+
sqlite=3.47.2 \
64+
anndata=0.11.1 \
65+
biopython=1.84 \
66+
datasets \
67+
ete3=3.1.3 \
68+
keras=3.7.0 \
69+
jupyter=1.0.0 \
70+
matplotlib=3.10.0 \
71+
matplotlib-venn=1.1.1 \
72+
nbconvert=7.16.4 \
73+
numpy=2.0.2 \
74+
optuna=4.1.0 \
75+
openpyxl=3.1.5 \
76+
pandas=2.2.3 \
77+
plotly=5.24.1 \
78+
rpy2=3.5.11 \
79+
scipy=1.14.1 \
80+
scanpy=1.10.4 \
81+
seaborn=0.13.2 \
82+
scikit-learn=1.6.0 \
83+
statsmodels=0.14.4 \
84+
umap-learn=0.5.7
85+
86+
RUN python -m ipykernel install --user --name python3 --display-name "Python 3 (ipykernel)"
87+
RUN R -e 'IRkernel::installspec(name = "R", displayname = "R (4.3.3)")'
88+
89+
RUN mamba install -c conda-forge -c bioconda -y \
90+
biokit=0.5.0 \
91+
gseapy=1.1.4 \
92+
blast=2.16.0 \
93+
clipkit=2.3.0 \
94+
fastqc=0.12.1 \
95+
iqtree=2.3.6 \
96+
mafft=7.526 \
97+
metaeuk=7.bba0d80 \
98+
mygene=3.2.2 \
99+
perl=5.32.1 \
100+
phykit=2.0.1 \
101+
pydeseq2=0.4.12 \
102+
spades=4.0.0 \
103+
trim-galore=0.6.10 \
104+
bioconductor-enhancedvolcano=1.20.0 \
105+
bioconductor-deseq2=1.42.0 \
106+
bioconductor-clusterprofiler=4.10.0 \
107+
bioconductor-org.hs.eg.db=3.18.0 \
108+
bioconductor-genomicranges=1.54.1 \
109+
bioconductor-summarizedexperiment=1.32.0 \
110+
bioconductor-apeglm=1.24.0
111+
112+
ENV UV_COMPILE_BYTECODE=1
113+
ENV UV_LINK_MODE=copy
114+
115+
FROM base AS builder
116+
117+
ARG MODULE_NAME
118+
ARG USE_INTERNAL_DEPS
119+
ARG USE_GIT_CROW_CLIENT
120+
121+
122+
RUN mkdir -p ~/.ssh && \
123+
chmod 700 ~/.ssh && \
124+
ssh-keyscan github.com >> ~/.ssh/known_hosts && \
125+
printf "Host github.com\n IdentityFile /root/.ssh/pqa_id_ed25519\n IdentityFile /root/.ssh/aviary_id_ed25519\nHost gitlab.company.com\n IdentityFile /root/.ssh/pqa_id_ed25519\n" > ~/.ssh/config
126+
127+
RUN --mount=type=cache,target=/var/cache/apt \
128+
apt-get update -qq && \
129+
apt-get install -yq --no-install-recommends \
130+
build-essential && \
131+
apt-get clean && rm -rf /var/lib/apt/lists/*
132+
133+
ENV VIRTUAL_ENV="/app/miniconda/bin"
134+
ENV PATH="/app/miniconda/bin:$PATH"
135+
136+
COPY ./${MODULE_NAME} /app/${MODULE_NAME}
137+
138+
RUN mkdir -p /app/scripts
139+
COPY ./scripts/run_crow_job.py /app/scripts/
140+
141+
RUN --mount=type=cache,target=/root/.cache/uv \
142+
--mount=type=ssh \
143+
--mount=type=secret,id=ssh_key,target=/root/.ssh/aviary_id_ed25519.tmp \
144+
if [ "$USE_INTERNAL_DEPS" = "true" ]; then \
145+
cp /root/.ssh/aviary_id_ed25519.tmp /root/.ssh/aviary_id_ed25519 && \
146+
chmod 400 /root/.ssh/aviary_id_ed25519 && \
147+
git clone git@github.com:Future-House/aviary-internal.git /app/aviary_internal && \
148+
cd /app/aviary_internal/aviary_internal && \
149+
uv pip install --system -e .; \
150+
else \
151+
echo 'Skipping aviary_internal install'; \
152+
fi && \
153+
if [ "$USE_GIT_CROW_CLIENT" = "true" ]; then \
154+
git clone git@github.com:Future-House/crow-ecosystem.git /app/crow-ecosystem && \
155+
cd /app/crow-ecosystem/packages/crow-client && \
156+
uv pip install --system -e .; \
157+
else \
158+
uv pip install --system crow-client; \
159+
fi
160+
161+
WORKDIR /app/${MODULE_NAME}
162+
RUN --mount=type=ssh \
163+
--mount=type=secret,id=pqa_ssh_key,target=/root/.ssh/pqa_id_ed25519.tmp \
164+
cp /root/.ssh/pqa_id_ed25519.tmp /root/.ssh/pqa_id_ed25519 && \
165+
chmod 400 /root/.ssh/pqa_id_ed25519 && \
166+
if [ -f "pyproject.toml" ]; then \
167+
uv pip install --system -e .; \
168+
elif [ -f "requirements.txt" ]; then \
169+
uv pip install --system -r requirements.txt; \
170+
else \
171+
echo "No pyproject.toml or requirements.txt found" && exit 1; \
172+
fi
173+
174+
RUN find /app -type l -delete && \
175+
rm -rf /app/.git
176+
177+
FROM base AS runtime
178+
179+
COPY --from=builder /app/ /app/
180+
181+
ENV VIRTUAL_ENV="/app/miniconda/bin"
182+
ENV PATH="/app/miniconda/bin:$PATH"
183+
ENV PYTHONPATH="/app/miniconda/lib/python3.12/site-packages:${PYTHONPATH:-}"
184+
CMD ["python", "scripts/run_crow_job.py"]

src/fhda/data_analysis_env.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
Tool,
1212
)
1313

14+
from llmclient import GLOBAL_COST_TRACKER, enable_cost_tracking
15+
1416
from .notebook_env import NBEnvironment
1517
from .utils import NBLanguage, MultipleChoiceQuestion, nb_to_html
1618
from . import prompts
@@ -83,7 +85,7 @@ async def submit_answer(self, answer: str) -> str: # type: ignore[override]
8385
def export_frame(self) -> Frame:
8486
return Frame(
8587
state={
86-
"last_action": self.state.actions[-1],
88+
"last_action": self.state.actions[-1] if self.state.actions else None,
8789
"answer": self.state.answer,
8890
"done": self.state.done,
8991
"total_reward": self.state.total_reward,
@@ -96,6 +98,7 @@ def export_frame(self) -> Frame:
9698
"language": self.state.language,
9799
"problem": self.problem,
98100
"problem_id": self.problem_id,
101+
"cost": GLOBAL_COST_TRACKER.lifetime_cost_usd,
99102
},
100103
)
101104

@@ -117,7 +120,8 @@ def from_task(
117120
logger.info("User task: %s", task)
118121
logger.info("GCS artifact path: %s", gcs_artifact_path)
119122
logger.info("environment_config: %s", environment_config)
120-
123+
# Track cost of running the environment
124+
enable_cost_tracking()
121125
if (
122126
not gcs_artifact_path
123127
): # Platform jobs should always be associated with data from a GCS bucket
@@ -136,6 +140,7 @@ def from_task(
136140
logger.info("Filtered kwargs: %s", kwargs)
137141
task_hash = hashlib.sha256(task.encode()).hexdigest()
138142
if kwargs.get("eval", False):
143+
logger.info("Eval mode is True")
139144
# Create a temporary directory in GCP mounted storage volume
140145
trajectory_path = cfg.DATA_STORAGE_PATH / f"{task_hash}-{time.time()}"
141146
trajectory_path.mkdir(parents=True, exist_ok=True)
@@ -147,6 +152,7 @@ def from_task(
147152
item, trajectory_path / item.name, dirs_exist_ok=True
148153
)
149154
else:
155+
logger.info("Eval mode is False")
150156
# Use the GCP folder created when uploading the data via the platform
151157
trajectory_path = cfg.DATA_STORAGE_PATH / gcs_artifact_path
152158
# Augment incoming user query with CoT instructions
@@ -160,7 +166,7 @@ def from_task(
160166
)
161167
logger.info("Trajectory path: %s", trajectory_path)
162168
nb_path = trajectory_path / NBEnvironment.NOTEBOOK_NAME
163-
169+
logger.info("NB path: %s", nb_path)
164170
language = NBLanguage.PYTHON # In future, this should be a hyperparameter
165171
if language == NBLanguage.R:
166172
task += f"\n{prompts.R_OUTPUT_RECOMMENDATION_PROMPT}"

src/fhda/prompts.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@
6161
- The first cell has already been loaded with %load_ext rpy2.ipython so you can use %%R cells from the second cell onwards
6262
"""
6363

64+
# General notebook guidelines
65+
GENERAL_NOTEBOOK_GUIDELINES_PYTHON = """
66+
General Guidelines:
67+
- Write small to medium-sized cells for easier debugging.
68+
- Edit existing cells by their index number when fixing bugs, rather than creating new ones.
69+
- Check dataframe shapes before printing. Use head() for large dataframes.
70+
- Ensure each cell executes successfully before moving to the next.
71+
- Assume you already have the packages you need installed and only install new ones if you receive errors.
72+
- If you need to install packages, use pip.
73+
- All cells are by default Python cells. Use python for all analysis.
74+
"""
75+
6476
GENERAL_NOTEBOOK_GUIDELINES_R = """
6577
General Guidelines:
6678
- Write small to medium-sized cells for easier debugging.
@@ -172,10 +184,79 @@
172184
</thought_process>
173185
"""
174186

187+
CHAIN_OF_THOUGHT_AGNOSTIC_PYTHON = """
188+
Follow these steps to create your notebook, using chain-of-thought reasoning at each stage:
189+
190+
1. List Directory Contents:
191+
<analysis_planning>
192+
- Consider how to use the list_workdir tool to recursively list the directory contents.
193+
- Think about how to organize and present this information clearly in the notebook.
194+
- List potential challenges in interpreting the directory structure.
195+
- Consider how the directory structure might inform your approach to the analysis.
196+
</analysis_planning>
197+
Place the output of the list_workdir tool inside <directory_contents> tags.
198+
199+
2. Load Data and Perform Descriptive Statistics:
200+
<analysis_planning>
201+
- Identify which data files are most relevant to resolving the task. List these files.
202+
- Plan how to load these files efficiently in Python.
203+
- List the specific descriptive statistics you plan to use (e.g., summary(), str(), head()).
204+
- Consider potential issues like missing data or unexpected formats. How will you handle each?
205+
- Plan how to present this information clearly in the notebook.
206+
- Write down key statistics you expect to see and how you'll interpret them.
207+
- Consider potential data quality issues and how you'll address them.
208+
</analysis_planning>
209+
Execute your plan to load data and perform descriptive statistics.
210+
211+
3. Develop Analysis Plan:
212+
<analysis_planning>
213+
- Break down each task into testable components. List these components.
214+
- For each component, list appropriate statistical tests or visualizations.
215+
- Consider alternative approaches for each component and justify your choices.
216+
- Identify potential confounding factors and how to address them.
217+
- Plan the sequence of your analysis steps, explaining the rationale for each.
218+
- Consider how this analysis plan will be documented in the notebook.
219+
- List potential statistical assumptions for your chosen methods and how you'll test them.
220+
- Think about how your analysis plan addresses your original task.
221+
</analysis_planning>
222+
Write out your analysis plan as comments in the notebook.
223+
224+
4. Execute Analysis Plan:
225+
<analysis_planning>
226+
- For each step in your analysis plan, list the Python functions and libraries you'll use.
227+
- Think about how to structure your code for readability and efficiency.
228+
- Plan how to document your code with clear comments.
229+
- Consider how to present results clearly, using tables or visualizations where appropriate.
230+
- Ensure that all outputs are clearly labeled and explained in the context of the task.
231+
- Plan how you'll interpret each result in relation to the original task.
232+
- Consider potential unexpected results and how you'll handle them.
233+
</analysis_planning>
234+
Execute your analysis plan, creating new cells as needed.
235+
236+
5. Conclude and Submit Answer:
237+
<thought_process>
238+
- Reflect on how your results relate to the original task.
239+
- Consider any limitations or uncertainties in your analysis.
240+
- Plan a concise summary of your findings.
241+
- Think about how to phrase your conclusion as clear statements.
242+
- Ensure that the notebook contains all necessary information for another model to derive these answers.
243+
- Consider any additional insights or patterns you've noticed during the analysis.
244+
- Think about potential follow-up questions or areas for further investigation.
245+
</thought_process>
246+
"""
247+
175248
SUBMIT_ANSWER_HYPOTHESIS = """
176249
[Use the submit_answer tool to submit your final answer as a single string either "True" or "False"]
177250
Remember, the final notebook should contain all necessary artifacts (plots, tables, print outputs) to solve the task provided.
178251
"""
252+
SUBMIT_ANSWER_SINGLE = """
253+
[Use the submit_answer tool to submit your final answer as a single string]
254+
Example output:
255+
```
256+
submit_answer("CD94") or submit_answer("-1.23")
257+
```
258+
Remember, the final notebook should contain all necessary artifacts (plots, tables, print outputs) to solve the task provided.
259+
"""
179260
SUBMIT_ANSWER_OPEN = """
180261
[Use the submit_answer tool to submit your final answer as a jsondictionary with keys as the question number and values as a short answer]
181262
Example output:

0 commit comments

Comments
 (0)