Files changed (1) hide show
  1. app.py +80 -212
app.py CHANGED
@@ -1,7 +1,8 @@
1
  # --- Imports ---
2
- import gradio as gr
3
  import spaces
 
4
  from transformers import pipeline
 
5
 
6
  # --- Load Model ---
7
  pipe = pipeline(model="InstaDeepAI/ChatNT", trust_remote_code=True)
@@ -9,136 +10,83 @@ pipe = pipeline(model="InstaDeepAI/ChatNT", trust_remote_code=True)
9
  # --- Logs ---
10
  log_file = "logs.txt"
11
 
 
 
 
12
 
13
- def log_message(message: str):
14
- with open(log_file, "a") as log:
15
- log.write(f"{message}\n")
 
 
16
 
 
 
 
 
 
17
 
18
- # --- Utilities ---
19
- def read_dna_sequence(dna_text, fasta_file):
20
- """
21
- This function reads the DNA sequence, either from the text field or from the FASTA file,
22
- and it checks the format.
23
- It might return an error if the format of the FASTA file is incorrect.
24
-
25
- Returns:
26
- dna_sequence: str
27
- warning: str if any
28
- error: str if any
29
- """
30
  dna_sequence = ""
31
- warning = ""
32
- error = ""
33
-
34
- # Pasted text
35
  if dna_text and dna_text.strip():
36
  dna_sequence = dna_text.strip().replace("\n", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- # Text overrides uploaded FASTA
39
- if fasta_file is not None:
40
- if dna_sequence:
41
- warning = "⚠️ Warning: Both pasted DNA and FASTA file provided. Using the pasted DNA only."
42
-
43
- else:
44
- try:
45
- with open(fasta_file.name, "r") as f:
46
- content = f.read()
47
-
48
- if not content.startswith(">"):
49
- error = "❌ Invalid FASTA: must start with '>' header line."
50
- return "", warning, error
51
-
52
- sequence = ""
53
- for line in content.splitlines():
54
- if not line or line.startswith(">"):
55
- continue
56
- sequence += line.strip()
57
-
58
- dna_sequence = sequence
59
-
60
- except Exception:
61
- error = "❌ Could not read the FASTA file."
62
-
63
- if dna_sequence and not dna_sequence.isupper():
64
- dna_sequence = dna_sequence.upper()
65
- warning += "\n⚠️ Note: DNA sequence was converted to uppercase (lowercase nucleotides are not supported)."
66
-
67
- if error == "":
68
- # If there was an error before, no need to check the content of the sequence. Otherwise, check that only ACGTN are used
69
- for nucleotide in dna_sequence:
70
- if nucleotide not in ["A", "C", "G", "T", "N"]:
71
- error = "❌ The nucleotides of the DNA sequence should be either 'A', 'T', 'G', 'C' or 'N'."
72
-
73
- return dna_sequence, warning.strip(), error
74
-
75
-
76
- def validate_inputs(dna_sequence: str, custom_question: str):
77
- """
78
- This function is used to
79
-
80
- Args:
81
- dna_sequence (str): DNA sequence used by the user.
82
- Note: It might be empty ("")
83
-
84
- custom_question (str): Question of the user (in english).
85
-
86
- Returns:
87
- valid: bool
88
- Whether the input is valid (correct number of <DNA> tokens, etc)
89
- error: str
90
- Error message if necessary
91
- """
92
- placeholders = custom_question.count("<DNA>")
93
-
94
- if not custom_question.strip():
95
- return False, "❌ Please provide a question."
96
-
97
- if dna_sequence and placeholders == 0:
98
- log_message("Error: DNA sequence provided but no <DNA> token.")
99
- return False, "❌ Your question must contain the <DNA> token if you provide a DNA sequence."
100
-
101
- if not dna_sequence and placeholders == 1:
102
- log_message("Error: <DNA> token but no sequence.")
103
- return False, "❌ You must provide a DNA sequence if you use the <DNA> token."
104
-
105
- if placeholders > 1:
106
- return False, "❌ Only one <DNA> token is allowed."
107
-
108
- return True, ""
109
-
110
-
111
- # --- Main Inference ---
112
- @spaces.GPU
113
- def run_chatnt(dna_text, fasta_file, custom_question):
114
- feedback_msgs = []
115
-
116
- dna_sequence, warning, fasta_error = read_dna_sequence(dna_text, fasta_file)
117
 
118
- if fasta_error:
119
- return "", fasta_error
 
120
 
121
- is_valid, validation_error = validate_inputs(dna_sequence, custom_question)
122
- if not is_valid:
123
- return "", validation_error
124
 
125
- final_prompt = custom_question
 
126
 
127
- inputs = {
128
- "english_sequence": final_prompt,
129
- "dna_sequences": [dna_sequence] if dna_sequence else []
130
- }
 
 
 
 
 
131
 
132
- output = pipe(inputs=inputs)
133
- result = output
 
 
 
 
 
 
 
 
 
134
 
135
- if warning:
136
- feedback_msgs.append(warning)
137
 
138
- if len(feedback_msgs) == 0:
139
- feedback_msgs.append(" Execution was succesful.")
140
- return result, "\n".join(feedback_msgs)
141
 
 
142
 
143
  # --- Gradio Interface ---
144
  css = """
@@ -147,130 +95,50 @@ css = """
147
  footer { display: none !important; }
148
  """
149
 
150
- example_dna = "AGGCGGTGGCAACAGCAACGATAGTTGCATCAGCGCTAACTGCTGCTGCTGCTGCGTGTTCTGCCGGAGGCTGCCGGGAGGGTACAGTAATGACTGTAGGGAGCGCACAGCCGCTGCGGCGGCAGGTTCCCTGGATCAGGAAATTAGGACAGGCACCCGGGATTGGAGGCGAGGGCGGCGCGCGAGCCAACCAGCGGTCGCCCCGGGCCCCCGTGAGCCCCAGGCGAACGCGCGGAGCAGCCGGGCCCCTAGAGCGGTCCGGGTGGCGGCGGCGGCAGCCACGACCACGACCGCGGTCAC"
151
- example_question = "Is there a promoter element in human or mouse cells present in the nucleotide sequence <DNA>?"
152
-
153
  with gr.Blocks(css=css) as demo:
154
- gr.Markdown(
155
- """
156
- # 🧬 ChatNT: A Multimodal Conversational Agent for DNA, RNA and Protein Tasks
157
- [ChatNT](https://www.nature.com/articles/s42256-025-01047-1) is the first multimodal conversational agent designed with a deep understanding of biological sequences (DNA, RNA, proteins). It enables users — even those with no coding background — to interact with biological data through natural language and it generalizes across multiple biological tasks and modalities.
158
-
159
- This Hugging Face Space is powered by a [ZeroGPU](https://huggingface.co/docs/hub/en/spaces-zerogpu), which is free but **limited to 5 minutes per day per user**.
160
- """
161
- )
162
 
163
  with gr.Row():
164
  with gr.Column(scale=1):
165
  dna_text = gr.Textbox(
166
  label="Paste your DNA sequence",
167
- placeholder="ATGCATGC...",
168
  lines=4
169
  )
170
 
171
  fasta_file = gr.File(
172
  label="Or upload your FASTA file",
173
- file_types=[".fasta", ".fa", ".txt"],
174
- height=50
175
  )
176
 
177
  custom_question = gr.Textbox(
178
  label="English Question",
179
- placeholder="Does this sequence <DNA> contain a donor splice site?"
180
  )
181
 
182
- use_example = gr.Button("Use Example")
183
  submit_btn = gr.Button("Run Query", variant="primary")
184
 
185
- with gr.Column(scale=1):
186
- output = gr.Textbox(
187
- label="Model Answer",
188
- lines=12
189
- )
190
- error_box = gr.Textbox(
191
- label="Execution Feedback",
192
- lines=4
193
- )
194
 
195
  submit_btn.click(
196
  run_chatnt,
197
  inputs=[dna_text, fasta_file, custom_question],
198
- outputs=[output, error_box],
199
- )
200
-
201
- use_example.click(
202
- lambda: (example_dna, None, example_question),
203
- inputs=[],
204
- outputs=[dna_text, fasta_file, custom_question]
205
  )
206
 
207
- with gr.Row():
208
- with gr.Column(scale=3):
209
- gr.Markdown(
210
- """
211
- You must use **exactly one `<DNA>` token** if you want the model to see your sequence.
212
- It is also possible to use the model without any DNA sequence (in this case, the `<DNA>` token must not be present in the question).
213
- You can either paste a sequence or upload a FASTA file.
214
-
215
- ---
216
-
217
- ### Limitations and Disclaimer
218
- ChatNT can only handle questions related to the 27 tasks it has been trained on, including the same format of DNA sequences. ChatNT is not a clinical or diagnostic tool. It can produce incorrect or “hallucinated” answers, particularly on out‑of‑distribution inputs, and its numeric predictions may suffer digit‑level errors. Confidence estimates require post‑hoc calibration. Users should always validate critical outputs against experiments or specialized bioinformatics pipelines.
219
-
220
- ---
221
-
222
- ### How to query the model ?
223
- ChatNT works like an advanced assistant for analyzing DNA sequences — but it has some important rules.
224
-
225
- #### ✅ What works well
226
- ChatNT is good at answering biological questions about the meaning or function of the DNA sequence.
227
- Examples of good queries:
228
- - "Does this sequence `<DNA>` contain a donor splice site?"
229
- - "Is it possible for you to identify whether there's a substantial presence of H3 histone protein occupancy in the nucleotide sequence `<DNA>` in yeast?"
230
- - "Determine the degradation rate of the mouse RNA sequence `<DNA>` within the -5 to 5 range."
231
-
232
- #### ❌ What does not work properly
233
- ChatNT does not have direct access to the raw nucleotides of the DNA. Instead, it works with an internal representation of the sequence. This means it cannot answer questions about the exact nucleotides or modify the sequence for you.
234
- Examples of bad queries:
235
- - "What is the length of this sequence `<DNA>`?"
236
- - "Replace the all 'A' nucleotides by 'G' in this sequence `<DNA>`".
237
-
238
- For more examples, you can refer to the [training dataset](https://huggingface.co/datasets/InstaDeepAI/ChatNT_training_data).
239
-
240
- ---
241
-
242
- """
243
- )
244
- with gr.Column(scale=1):
245
- gr.Image(
246
- "https://media.springernature.com/w440/springer-static/cover-hires/journal/42256/7/6",
247
- label=None,
248
- show_label=False
249
- )
250
-
251
  gr.Markdown("""
252
- ### 📚 Citation
253
-
254
- If you use **ChatNT**, please cite:
 
 
255
 
256
- ```bibtex
257
- @article{deAlmeida2025,
258
- title = {A multimodal conversational agent for DNA, RNA and protein tasks},
259
- author = {de Almeida, Bernardo P. and Richard, Guillaume and Dalla-Torre, Hugo and Blum, Christopher and Hexemer, Lorenz and Pandey, Priyanka and Laurent, Stefan and Rajesh, Chandana and Lopez, Marie and Laterre, Alexandre and Lang, Maren and Şahin, Uğur and Beguir, Karim and Pierrot, Thomas},
260
- journal = {Nature Machine Intelligence},
261
- year = {2025},
262
- volume = {7},
263
- number = {6},
264
- pages = {928--941},
265
- doi = {10.1038/s42256-025-01047-1},
266
- url = {https://doi.org/10.1038/s42256-025-01047-1},
267
- issn = {2522-5839}
268
- }
269
- ```
270
- """,
271
- show_copy_button=True
272
- )
273
 
 
274
  if __name__ == "__main__":
275
  demo.queue()
276
  demo.launch(debug=True, show_error=True)
 
1
  # --- Imports ---
 
2
  import spaces
3
+ import gradio as gr
4
  from transformers import pipeline
5
+ import os
6
 
7
  # --- Load Model ---
8
  pipe = pipeline(model="InstaDeepAI/ChatNT", trust_remote_code=True)
 
10
  # --- Logs ---
11
  log_file = "logs.txt"
12
 
13
+ class Log:
14
+ def __init__(self, log_file):
15
+ self.log_file = log_file
16
 
17
+ def __call__(self):
18
+ if not os.path.exists(self.log_file):
19
+ return ""
20
+ with open(self.log_file, "r") as f:
21
+ return f.read()
22
 
23
+ # --- Main Function ---
24
+ @spaces.GPU
25
+ def run_chatnt(dna_text, fasta_file, custom_question):
26
+ with open(log_file, "a") as log:
27
+ log.write("Request started\n\n")
28
 
29
+ # Read DNA sequence from text field or file
 
 
 
 
 
 
 
 
 
 
 
30
  dna_sequence = ""
 
 
 
 
31
  if dna_text and dna_text.strip():
32
  dna_sequence = dna_text.strip().replace("\n", "")
33
+ elif fasta_file is not None:
34
+ file_content = fasta_file.read().decode("utf-8")
35
+ lines = file_content.splitlines()
36
+ sequence = ""
37
+ for line in lines:
38
+ line = line.strip()
39
+ if not line or line.startswith(">"):
40
+ continue
41
+ sequence += line
42
+ dna_sequence = sequence
43
+
44
+ dna_sequences = []
45
+ if dna_sequence:
46
+ dna_sequences.append(dna_sequence)
47
 
48
+ with open(log_file, "a") as log:
49
+ log.write(f"DNA sequences found: {dna_sequences}\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ # Check DNA sequences count
52
+ if len(dna_sequences) > 1:
53
+ return "You must use only one DNA sequence."
54
 
55
+ if not custom_question or custom_question.strip() == "":
56
+ return "Please provide a question."
 
57
 
58
+ # Build prompt
59
+ num_placeholders = custom_question.count("<DNA>")
60
 
61
+ if len(dna_sequences) == 0:
62
+ english_sequence = custom_question
63
+ else:
64
+ if num_placeholders == 0:
65
+ return "Your question must include the <DNA> token at the position where the DNA sequence should be inserted."
66
+ elif num_placeholders == 1:
67
+ english_sequence = custom_question
68
+ else:
69
+ return "You can only provide one DNA sequence, so you must use exactly one <DNA> placeholder."
70
 
71
+ with open(log_file, "a") as log:
72
+ log.write(f"Initial user question: {custom_question}\n")
73
+ log.write(f"Full english prompt: {english_sequence}\n")
74
+ log.write("Calling model\n")
75
+
76
+ output = pipe(
77
+ inputs={
78
+ "english_sequence": english_sequence,
79
+ "dna_sequences": dna_sequences
80
+ }
81
+ )
82
 
83
+ if len(dna_sequences) == 0:
84
+ return f"{output}\n\nNote: Careful, you did not provide any DNA sequence."
85
 
86
+ with open(log_file, "a") as log:
87
+ log.write(f"Output: {output}\n")
 
88
 
89
+ return output
90
 
91
  # --- Gradio Interface ---
92
  css = """
 
95
  footer { display: none !important; }
96
  """
97
 
 
 
 
98
  with gr.Blocks(css=css) as demo:
99
+ gr.Markdown("# 🧬 ChatNT: A Multimodal Conversational Agent for DNA, RNA and Protein Tasks")
 
 
 
 
 
 
 
100
 
101
  with gr.Row():
102
  with gr.Column(scale=1):
103
  dna_text = gr.Textbox(
104
  label="Paste your DNA sequence",
105
+ placeholder="ATGCATGCATGC...",
106
  lines=4
107
  )
108
 
109
  fasta_file = gr.File(
110
  label="Or upload your FASTA file",
111
+ file_types=[".fasta", ".fa", ".txt"]
 
112
  )
113
 
114
  custom_question = gr.Textbox(
115
  label="English Question",
116
+ placeholder="e.g., Does this sequence <DNA> contain a donor splice site?"
117
  )
118
 
 
119
  submit_btn = gr.Button("Run Query", variant="primary")
120
 
121
+ with gr.Row():
122
+ output = gr.Textbox(label="Answer", lines=6)
 
 
 
 
 
 
 
123
 
124
  submit_btn.click(
125
  run_chatnt,
126
  inputs=[dna_text, fasta_file, custom_question],
127
+ outputs=output,
 
 
 
 
 
 
128
  )
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  gr.Markdown("""
131
+ **Note:**
132
+ ✅ You must use **exactly one DNA sequence** (either paste it or upload a file).
133
+ Your question must include the `<DNA>` token **exactly once** at the position where the DNA will be inserted.
134
+ Example: *"Does this sequence <DNA> contain a donor splice site?"*
135
+ """)
136
 
137
+ with gr.Accordion("Logs", open=True):
138
+ log_display = Log(log_file)
139
+ gr.Markdown(log_display)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
+ # --- Launch ---
142
  if __name__ == "__main__":
143
  demo.queue()
144
  demo.launch(debug=True, show_error=True)