danaaubakirova HF Staff commited on
Commit
3130db2
·
verified ·
1 Parent(s): 687fa23

Move the agent-tool section before Limitations; link example files instead of inlining them

Browse files
Files changed (3) hide show
  1. README.md +38 -164
  2. examples/agent_tool.py +65 -0
  3. examples/inspect_agent.py +67 -0
README.md CHANGED
@@ -26,10 +26,10 @@ An open 14B language model for DNA understanding and generation, from the **Carb
26
  2. [How to use](#how-to-use)
27
  3. [Evaluation](#evaluation)
28
  4. [Training](#training)
29
- 5. [Limitations](#limitations)
30
- 6. [License](#license)
31
- 7. [Acknowledgements](#acknowledgements)
32
- 8. [Using Carbon-X as a tool for an agent](#using-carbon-x-as-a-tool-for-an-agent)
33
 
34
  ## Model Summary
35
 
@@ -228,31 +228,10 @@ Both rounds use 16 samples per prompt, completions of up to 4,096 tokens, a lear
228
 
229
  - **Post-training:** [TRL](https://github.com/huggingface/trl) for supervised fine-tuning and asynchronous GRPO, with [vLLM](https://github.com/vllm-project/vllm) for generation.
230
 
231
- ## Limitations
232
-
233
- Carbon-X is a starting point for research on connecting DNA and language. It has **not been clinically validated**, and it should not be used to make medical decisions.
234
-
235
- - ⚠️ **Genetic data is highly sensitive.** Depending on how you use this model (local download, inference API or endpoints, third-party inference providers, Spaces demos or others), input and output data may be processed or handled differently by different providers or Space owners. Make sure you understand and agree with how your data is handled before using the model.
236
- - **Exact sequence operations.** The model can miscount bases, confuse coordinates or make incorrect edits, particularly when it locates exact positions in long sequences. For tasks that need exact operations, pair it with tools that perform them.
237
- - **Missing context and hallucinations.** The model may invent a sequence's identity or function when the prompt does not give enough information. Include known annotations and context where available, and verify its biological claims.
238
- - **Uneven task coverage.** Splice-site and histone-mark questions remain weak (29.8% and 34.6% on GenomeQA). Carbon-X is also less reliable on short DNA exercises and textbook biology than on its core variant-effect and sequence-recovery tasks.
239
- - **Long sequence generation.** Quality degrades when the model is asked to generate long DNA sequences. Carbon-X reads and explains existing sequences. It was not trained to design new ones.
240
- - **Long inputs.** Mid-training used 8,192-token sequences (up to ≈ 49 kbp of DNA), and post-training examples were shorter than 16k tokens. The model accepts up to 40,960 tokens, but we have not evaluated inputs much longer than those seen in training.
241
- - **Thinking mode.** The chat template supports `enable_thinking=True`, but Carbon-X was post-trained and evaluated with thinking disabled.
242
-
243
- ## License
244
-
245
- Apache 2.0.
246
-
247
- ## Acknowledgements
248
-
249
- Carbon is a joint collaboration between the research teams at Hugging Face, Zhongguancun Academy, and TIGEM/University of Naples “Federico II”.
250
-
251
  ## Using Carbon-X as a tool for an agent
252
 
253
- A general-purpose assistant can call Carbon-X when a question involves DNA. Serve Carbon-X with vLLM as above, wrap it in a function, and give that function to any chat model that supports tool calling.
254
-
255
- **1. Wrap Carbon-X in a function.** The function takes a question and named DNA sequences, and returns Carbon-X's answer.
256
 
257
  ```python
258
  from openai import OpenAI
@@ -273,151 +252,46 @@ def ask_carbon(question: str, sequences: dict[str, str], context: str = "") -> s
273
  return response.choices[0].message.content
274
  ```
275
 
276
- **2. Describe it to the agent as a tool.** Let the agent refer to sequences by name, and have your code look up the DNA. Language models often miscopy long sequences, so the agent should never retype them.
277
-
278
- ```python
279
- import json
280
-
281
- sequences = {"reference.fa": "ATGGTGCATCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
282
- "variant.fa": "ATGGTGCATCTGACTCCTGTGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG"}
283
-
284
- carbon_tool = {
285
- "type": "function",
286
- "function": {
287
- "name": "ask_carbon",
288
- "description": (
289
- "Ask Carbon-X, a DNA language model, a question about DNA sequences: variant effects, "
290
- "regulatory elements, organism of origin, sequence features. Refer to sequences by name; "
291
- "do not paste DNA. Its answers are model predictions, not verified facts."
292
- ),
293
- "parameters": {
294
- "type": "object",
295
- "properties": {
296
- "question": {"type": "string", "description": "One self-contained question."},
297
- "sequences": {"type": "array", "items": {"type": "string", "enum": list(sequences)}},
298
- "context": {"type": "string", "description": "Known gene, coordinates or annotations, if any."},
299
- },
300
- "required": ["question", "sequences"],
301
- },
302
- },
303
- }
304
- ```
305
-
306
- **3. Run the agent loop.** Any OpenAI-compatible chat model with tool calling works as the agent.
307
-
308
- ```python
309
- agent = OpenAI() # the agent model's provider
310
- messages = [
311
- {"role": "system", "content": "For questions about DNA, consult Carbon-X with the ask_carbon tool before answering."},
312
- {"role": "user", "content": "variant.fa differs from reference.fa by one base. Is the change likely benign or pathogenic?"},
313
- ]
314
- while True:
315
- reply = agent.chat.completions.create(model="<agent model>", messages=messages, tools=[carbon_tool])
316
- message = reply.choices[0].message
317
- messages.append(message)
318
- if not message.tool_calls:
319
- break
320
- for call in message.tool_calls:
321
- args = json.loads(call.function.arguments)
322
- answer = ask_carbon(args["question"], {name: sequences[name] for name in args["sequences"]}, args.get("context", ""))
323
- messages.append({"role": "tool", "tool_call_id": call.id, "content": answer})
324
- print(message.content)
325
- ```
326
-
327
 
328
- ### With Inspect
 
 
 
 
 
 
 
329
 
330
- In [Inspect](https://inspect.aisi.org.uk/), wrap Carbon-X in an `@tool` and give it to a `react()` agent. Each sample
331
- keeps its DNA in metadata, so the agent refers to sequences by name.
332
-
333
- ```python
334
- from inspect_ai import Task, eval, task
335
- from inspect_ai.agent import as_solver, react
336
- from inspect_ai.dataset import Sample
337
- from inspect_ai.scorer import includes
338
- from inspect_ai.solver import solver
339
- from inspect_ai.tool import ToolError, tool
340
- from inspect_ai.util import store
341
- from openai import AsyncOpenAI
342
-
343
- carbon = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
344
-
345
-
346
- @tool
347
- def ask_carbon():
348
- async def execute(question: str, sequences: list[str], context: str = "") -> str:
349
- """Ask Carbon-X, a DNA language model, about this task's DNA sequences. Its answers are predictions.
350
-
351
- Args:
352
- question: One self-contained question.
353
- sequences: Names of the sequences to send, as given in the task.
354
- context: Known facts, such as the gene, positions or alleles.
355
- """
356
- dna = store().get("sequences")
357
- if missing := [name for name in sequences if name not in dna]:
358
- raise ToolError(f"Unknown sequence names: {missing}. Available: {list(dna)}")
359
- parts = [context, question] if context else [question]
360
- parts += [f"{name}: <dna>{dna[name]}</dna>" for name in sequences]
361
- response = await carbon.chat.completions.create(
362
- model="HuggingFaceBio/Carbon-X",
363
- messages=[{"role": "user", "content": "\n\n".join(parts)}],
364
- max_tokens=4096,
365
- extra_body={"chat_template_kwargs": {"enable_thinking": False}},
366
- )
367
- return response.choices[0].message.content
368
-
369
- return execute
370
-
371
-
372
- @solver
373
- def load_sequences():
374
- async def solve(state, generate):
375
- store().set("sequences", state.metadata["sequences"]) # the tool reads DNA from here
376
- return state
377
-
378
- return solve
379
-
380
-
381
- @task
382
- def carbon_agent():
383
- sample = Sample(
384
- input="Sequence `variant` differs from `reference` at position 20 (A>T). Is the variant benign or pathogenic?",
385
- target="pathogenic",
386
- metadata={"sequences": {
387
- "reference": "ATGGTGCATCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
388
- "variant": "ATGGTGCATCTGACTCCTGTGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
389
- }},
390
- )
391
- agent = react(prompt="Consult Carbon-X with the ask_carbon tool before you answer.", tools=[ask_carbon()])
392
- return Task(dataset=[sample], solver=[load_sequences(), as_solver(agent)], scorer=includes())
393
 
 
394
 
395
- eval(carbon_agent(), model="openai/gpt-5") # any Inspect model with tool calling as the agent
396
- ```
 
 
397
 
398
- ### As a skill for coding agents
399
 
400
- This repository includes a ready-made skill in [`skills/carbon-dna/`](skills/carbon-dna/): a `SKILL.md` that tells the
401
- agent when and how to consult Carbon-X, and a standard-library Python script with two commands, `ask` (a question about
402
- named DNA files) and `compare` (rank equal-length variants by sequence likelihood).
403
 
404
- ```bash
405
- hf download HuggingFaceBio/Carbon-X --include "skills/*" --local-dir carbon-x
406
- cp -r carbon-x/skills/carbon-dna ~/.claude/skills/ # Claude Code; Codex CLI: ~/.codex/skills/
407
- export CARBON_BASE_URL=http://localhost:8000/v1 # your Carbon-X server
408
- ```
 
 
409
 
410
- The script also runs on its own:
411
 
412
- ```bash
413
- python carbon-x/skills/carbon-dna/scripts/carbon.py ask --sequence reference=ref.fa --sequence variant=alt.fa \
414
- --question "Is the variant benign or pathogenic?" --context "Position 20: A>T"
415
- ```
416
 
417
- ### Tips for agent use
418
 
419
- - **Pass DNA by reference.** Keep sequences in files or a lookup table and let the agent name them.
420
- - **Ask for Carbon-X explicitly.** Agents often answer DNA questions on their own instead of calling a tool. If you want Carbon-X's view, say so in the system prompt.
421
- - **Send only A, C, G and T.** Trim padding such as leading or trailing `N`s. Other characters become `<oov>` tokens.
422
- - **Leave exact operations to code.** Counting, coordinates and edits are more reliable in Python than in either model. For variants, compute the position and the reference and alternate bases in code and pass them as `context`.
423
- - **Cap generation length.** For next-base and continuation questions, set `max_tokens` to what you need and a client timeout, since long DNA generations are slow (see [Limitations](#limitations)).
 
26
  2. [How to use](#how-to-use)
27
  3. [Evaluation](#evaluation)
28
  4. [Training](#training)
29
+ 5. [Using Carbon-X as a tool for an agent](#using-carbon-x-as-a-tool-for-an-agent)
30
+ 6. [Limitations](#limitations)
31
+ 7. [License](#license)
32
+ 8. [Acknowledgements](#acknowledgements)
33
 
34
  ## Model Summary
35
 
 
228
 
229
  - **Post-training:** [TRL](https://github.com/huggingface/trl) for supervised fine-tuning and asynchronous GRPO, with [vLLM](https://github.com/vllm-project/vllm) for generation.
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  ## Using Carbon-X as a tool for an agent
232
 
233
+ A general-purpose assistant can call Carbon-X when a question involves DNA. Serve Carbon-X with vLLM
234
+ ([Serving with vLLM](#serving-with-vllm)) and wrap it in a function that takes a question and named DNA sequences:
 
235
 
236
  ```python
237
  from openai import OpenAI
 
252
  return response.choices[0].message.content
253
  ```
254
 
255
+ Ready-to-run examples in this repository build on this function:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
+ - **Any agent with tool calling:** [`examples/agent_tool.py`](examples/agent_tool.py) describes `ask_carbon` as an
258
+ OpenAI-style tool and runs a minimal agent loop. The agent refers to sequences by name and your code looks up the DNA,
259
+ because language models often miscopy long sequences.
260
+ - **Inspect:** [`examples/inspect_agent.py`](examples/inspect_agent.py) wraps Carbon-X in an `@tool` for an
261
+ [Inspect](https://inspect.aisi.org.uk/) `react()` agent, with each sample's DNA kept in metadata.
262
+ - **Coding agents (Claude Code, Codex CLI):** [`skills/carbon-dna/`](skills/carbon-dna/) is a ready-made skill. Its
263
+ standard-library script has two commands: `ask` (a question about named DNA files) and `compare` (rank equal-length
264
+ variants by sequence likelihood).
265
 
266
+ ```bash
267
+ hf download HuggingFaceBio/Carbon-X --include "skills/*" --local-dir carbon-x
268
+ cp -r carbon-x/skills/carbon-dna ~/.claude/skills/ # Codex CLI: ~/.codex/skills/
269
+ export CARBON_BASE_URL=http://localhost:8000/v1 # your Carbon-X server
270
+ ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ ### Tips for agent use
273
 
274
+ - **Pass DNA by reference.** Keep sequences in files or a lookup table and let the agent name them.
275
+ - **Ask for Carbon-X explicitly.** Agents often answer DNA questions on their own instead of calling a tool. If you want Carbon-X's view, say so in the system prompt.
276
+ - **Leave exact operations to code.** Counting, coordinates and edits are more reliable in Python than in either model. For variants, compute the position and the reference and alternate bases in code and pass them as `context`.
277
+ - **Cap generation length.** For next-base and continuation questions, set `max_tokens` to what you need and a client timeout, since long DNA generations are slow (see [Limitations](#limitations)).
278
 
279
+ ## Limitations
280
 
281
+ Carbon-X is a starting point for research on connecting DNA and language. It has **not been clinically validated**, and it should not be used to make medical decisions.
 
 
282
 
283
+ - ⚠️ **Genetic data is highly sensitive.** Depending on how you use this model (local download, inference API or endpoints, third-party inference providers, Spaces demos or others), input and output data may be processed or handled differently by different providers or Space owners. Make sure you understand and agree with how your data is handled before using the model.
284
+ - **Exact sequence operations.** The model can miscount bases, confuse coordinates or make incorrect edits, particularly when it locates exact positions in long sequences. For tasks that need exact operations, pair it with tools that perform them.
285
+ - **Missing context and hallucinations.** The model may invent a sequence's identity or function when the prompt does not give enough information. Include known annotations and context where available, and verify its biological claims.
286
+ - **Uneven task coverage.** Splice-site and histone-mark questions remain weak (29.8% and 34.6% on GenomeQA). Carbon-X is also less reliable on short DNA exercises and textbook biology than on its core variant-effect and sequence-recovery tasks.
287
+ - **Long sequence generation.** Quality degrades when the model is asked to generate long DNA sequences. Carbon-X reads and explains existing sequences. It was not trained to design new ones.
288
+ - **Long inputs.** Mid-training used 8,192-token sequences (up to ≈ 49 kbp of DNA), and post-training examples were shorter than 16k tokens. The model accepts up to 40,960 tokens, but we have not evaluated inputs much longer than those seen in training.
289
+ - **Thinking mode.** The chat template supports `enable_thinking=True`, but Carbon-X was post-trained and evaluated with thinking disabled.
290
 
291
+ ## License
292
 
293
+ Apache 2.0.
 
 
 
294
 
295
+ ## Acknowledgements
296
 
297
+ Carbon is a joint collaboration between the research teams at Hugging Face, Zhongguancun Academy, and TIGEM/University of Naples “Federico II”.
 
 
 
 
examples/agent_tool.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Use Carbon-X as a tool for any chat model with OpenAI-style tool calling.
2
+
3
+ Serve Carbon-X first: vllm serve HuggingFaceBio/Carbon-X --trust-remote-code
4
+ Then set the agent client and model below and run: python agent_tool.py
5
+ """
6
+ import json
7
+
8
+ from openai import OpenAI
9
+
10
+ carbon = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
11
+
12
+
13
+ def ask_carbon(question: str, sequences: dict[str, str], context: str = "") -> str:
14
+ """Ask Carbon-X a question about one or more named DNA sequences."""
15
+ parts = [context, question] if context else [question]
16
+ parts += [f"{name}: <dna>{seq}</dna>" for name, seq in sequences.items()]
17
+ response = carbon.chat.completions.create(
18
+ model="HuggingFaceBio/Carbon-X",
19
+ messages=[{"role": "user", "content": "\n\n".join(parts)}],
20
+ max_tokens=4096,
21
+ extra_body={"chat_template_kwargs": {"enable_thinking": False}},
22
+ )
23
+ return response.choices[0].message.content
24
+
25
+
26
+ sequences = {"reference.fa": "ATGGTGCATCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
27
+ "variant.fa": "ATGGTGCATCTGACTCCTGTGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG"}
28
+
29
+ carbon_tool = {
30
+ "type": "function",
31
+ "function": {
32
+ "name": "ask_carbon",
33
+ "description": (
34
+ "Ask Carbon-X, a DNA language model, a question about DNA sequences: variant effects, "
35
+ "regulatory elements, organism of origin, sequence features. Refer to sequences by name; "
36
+ "do not paste DNA. Its answers are model predictions, not verified facts."
37
+ ),
38
+ "parameters": {
39
+ "type": "object",
40
+ "properties": {
41
+ "question": {"type": "string", "description": "One self-contained question."},
42
+ "sequences": {"type": "array", "items": {"type": "string", "enum": list(sequences)}},
43
+ "context": {"type": "string", "description": "Known gene, coordinates or annotations, if any."},
44
+ },
45
+ "required": ["question", "sequences"],
46
+ },
47
+ },
48
+ }
49
+
50
+ agent = OpenAI() # the agent model's provider
51
+ messages = [
52
+ {"role": "system", "content": "For questions about DNA, consult Carbon-X with the ask_carbon tool before answering."},
53
+ {"role": "user", "content": "variant.fa differs from reference.fa by one base. Is the change likely benign or pathogenic?"},
54
+ ]
55
+ while True:
56
+ reply = agent.chat.completions.create(model="<agent model>", messages=messages, tools=[carbon_tool])
57
+ message = reply.choices[0].message
58
+ messages.append(message)
59
+ if not message.tool_calls:
60
+ break
61
+ for call in message.tool_calls:
62
+ args = json.loads(call.function.arguments)
63
+ answer = ask_carbon(args["question"], {name: sequences[name] for name in args["sequences"]}, args.get("context", ""))
64
+ messages.append({"role": "tool", "tool_call_id": call.id, "content": answer})
65
+ print(message.content)
examples/inspect_agent.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Use Carbon-X as a tool for an Inspect react() agent.
2
+
3
+ Serve Carbon-X first: vllm serve HuggingFaceBio/Carbon-X --trust-remote-code
4
+ Then run: python inspect_agent.py
5
+ """
6
+ from inspect_ai import Task, eval, task
7
+ from inspect_ai.agent import as_solver, react
8
+ from inspect_ai.dataset import Sample
9
+ from inspect_ai.scorer import includes
10
+ from inspect_ai.solver import solver
11
+ from inspect_ai.tool import ToolError, tool
12
+ from inspect_ai.util import store
13
+ from openai import AsyncOpenAI
14
+
15
+ carbon = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
16
+
17
+
18
+ @tool
19
+ def ask_carbon():
20
+ async def execute(question: str, sequences: list[str], context: str = "") -> str:
21
+ """Ask Carbon-X, a DNA language model, about this task's DNA sequences. Its answers are predictions.
22
+
23
+ Args:
24
+ question: One self-contained question.
25
+ sequences: Names of the sequences to send, as given in the task.
26
+ context: Known facts, such as the gene, positions or alleles.
27
+ """
28
+ dna = store().get("sequences")
29
+ if missing := [name for name in sequences if name not in dna]:
30
+ raise ToolError(f"Unknown sequence names: {missing}. Available: {list(dna)}")
31
+ parts = [context, question] if context else [question]
32
+ parts += [f"{name}: <dna>{dna[name]}</dna>" for name in sequences]
33
+ response = await carbon.chat.completions.create(
34
+ model="HuggingFaceBio/Carbon-X",
35
+ messages=[{"role": "user", "content": "\n\n".join(parts)}],
36
+ max_tokens=4096,
37
+ extra_body={"chat_template_kwargs": {"enable_thinking": False}},
38
+ )
39
+ return response.choices[0].message.content
40
+
41
+ return execute
42
+
43
+
44
+ @solver
45
+ def load_sequences():
46
+ async def solve(state, generate):
47
+ store().set("sequences", state.metadata["sequences"]) # the tool reads DNA from here
48
+ return state
49
+
50
+ return solve
51
+
52
+
53
+ @task
54
+ def carbon_agent():
55
+ sample = Sample(
56
+ input="Sequence `variant` differs from `reference` at position 20 (A>T). Is the variant benign or pathogenic?",
57
+ target="pathogenic",
58
+ metadata={"sequences": {
59
+ "reference": "ATGGTGCATCTGACTCCTGAGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
60
+ "variant": "ATGGTGCATCTGACTCCTGTGGAGAAGTCTGCCGTTACTGCCCTGTGGGGCAAGG",
61
+ }},
62
+ )
63
+ agent = react(prompt="Consult Carbon-X with the ask_carbon tool before you answer.", tools=[ask_carbon()])
64
+ return Task(dataset=[sample], solver=[load_sequences(), as_solver(agent)], scorer=includes())
65
+
66
+
67
+ eval(carbon_agent(), model="openai/gpt-5") # any Inspect model with tool calling as the agent