Chapter 9 AI Module
TCMDATA includes an AI module built on top of aisdk, providing three layers of AI capability: interpretation (one-shot analysis of R objects), agent (multi-turn interactive analysis with tool use), and skill (domain-specific workflow knowledge). This chapter demonstrates the key features of each layer.
9.1 Prerequisites
The AI module requires aisdk version 1.5.0 or later. DeepSeek and the other additional providers also require the companion aisdk.providers package.
remotes::install_github("YuLab-SMU/aisdk")
remotes::install_github("YuLab-SMU/aisdk.providers") # required for DeepSeek
library(TCMDATA)tcm_setup() registers one model for the current R session. Setting save = TRUE also writes the configuration to the project .env file, allowing later sessions to call tcm_setup() without repeating the credentials. Keep .env out of version control.
9.2 Provider configuration
9.2.2 Official Anthropic API
Use provider = "anthropic" for the native Anthropic Messages API. Replace the example model with an exact model ID available to the account.
9.2.3 Official DeepSeek API
DeepSeek is resolved from aisdk.providers. Without a custom endpoint, the provider uses the official DeepSeek API and retains DeepSeek-specific model handling.
# Explicitly reset a previously configured relay endpoint, if necessary
Sys.setenv(DEEPSEEK_BASE_URL = "https://api.deepseek.com")
Sys.unsetenv("DEEPSEEK_BASE_URLS")
tcm_setup(
provider = "deepseek",
api_key = "sk-xxx",
model = "deepseek-chat", # or an available DeepSeek V4 model ID
save = TRUE
)Provider-specific environment variables remain active when no TCM_BASE_URL is configured. If a DeepSeek request unexpectedly reaches a relay, inspect the endpoint variables before setup:
9.2.4 Relay and proxy endpoints
Pass the API root through base_url and explicitly specify the protocol implemented by the relay. The model ID must exactly match the ID listed by that relay.
For an OpenAI Chat Completions-compatible relay, including a relay serving Claude or DeepSeek models:
tcm_setup(
provider = "anthropic",
api_key = "relay-key",
model = "relay-claude-model-id",
base_url = "https://relay.example.com/v1",
api_format = "chat_completions",
save = TRUE
)For a relay implementing the native Anthropic Messages protocol:
tcm_setup(
provider = "anthropic",
api_key = "relay-key",
model = "relay-claude-model-id",
base_url = "https://relay.example.com/v1",
api_format = "anthropic_messages",
save = TRUE
)For a relay implementing the OpenAI Responses API:
tcm_setup(
provider = "openai",
api_key = "relay-key",
model = "relay-model-id",
base_url = "https://relay.example.com/v1",
api_format = "responses",
responses_state_mode = "stateless",
save = TRUE
)Relay implementations vary. Set supports_native_tools = FALSE when native tool calls are rejected; TCMDATA then uses JSON-mode structured output instead of the aisdk 1.5 forced-tool mode. Use tcm_chat(stream = FALSE) when streaming is unsupported, and set force_json_schema = FALSE only when a JSON-only endpoint rejects response_format. Calling tcm_setup(test = TRUE) sends a small live request to verify the key and endpoint.
9.3 Interpretation layer
The interpretation layer provides one-shot AI analysis of R objects and free-text queries, with no tool use or multi-turn conversation. The primary interface is tcm_interpret().
9.3.2 Interpreting enrichment objects
tcm_interpret() can directly accept a clusterProfiler enrichment object. The package automatically compresses the relevant terms and genes into a compact representation before sending to the model:
library(clusterProfiler)
library(org.Hs.eg.db)
lz_targets <- search_herb("lingzhi", type = "Herb_pinyin_name")$target
lz_targets <- sample(unique(na.omit(lz_targets)), 100)
bp <- enrichGO(
gene = lz_targets,
ont = "BP",
OrgDb = org.Hs.eg.db,
keyType = "SYMBOL")
tcm_interpret(
bp,
prompt = "Summarise the main biological implications of this Lingzhi GO enrichment.",
language = "en")=== TCM AI Analysis: enrichment ===
Model: openai:gpt-4o | Language: en | Audience: researcher
-- Summary --
The enrichment profile indicates that Lingzhi's putative targets cluster in innate
immune and inflammatory signaling, particularly TLR/IL-1/TNF-NF-κB pathways,
leukocyte adhesion, and matrix remodeling...
-- Key Findings --
* Strong enrichment for cellular response to stress and external stimuli
* Activation of innate immune and inflammatory pathways (IRAK1, NFKBIA, TNF, ADAM17)
* Leukocyte adhesion and endothelial activation (VCAM1, MMP9)
...
9.3.3 Interpreting PPI objects
PPI network objects with topological metrics computed by compute_nodeinfo() can also be directly interpreted:
9.3.4 Drafting result paragraphs
draft_result_paragraph() transforms an interpretation object into a publication-ready paragraph:
9.3.5 Custom structured output
tcm_interpret_schema() allows user-defined output schemas for integration into downstream pipelines:
my_schema <- tcm_schema(
summary = tcm_field_string("A concise 2-3 sentence summary"),
mechanism = tcm_field_string("Core mechanistic interpretation"),
key_targets = tcm_field_array("Most important targets"),
confidence = tcm_field_enum(c("high", "medium", "low"),
"Confidence level")
)
custom_res <- tcm_interpret_schema(
bp,
schema = my_schema,
type = "enrichment",
prompt = "Focus on inflammation-related processes.")
print(custom_res)Available field types: tcm_field_string(), tcm_field_number(), tcm_field_boolean(), tcm_field_array(), tcm_field_enum().
On endpoints with native tool support, these functions use aisdk’s validated forced-tool structured output. Validation status and retry count are recorded in result$metadata$structured_output_valid and result$metadata$structured_output_attempts.
9.4 Agent layer
The agent layer adds tool use and multi-turn conversation on top of the interpretation engine. TCMDATA provides 30+ analysis tools (target search, enrichment, PPI, ML screening, compound lookup, visualization, etc.) that the agent can call autonomously.
9.4.1 One-shot task: tcm_agent()
For a single analysis request, tcm_agent() automatically routes the query to the appropriate tools and returns the result:
# Simple herb target lookup — agent selects the right tool automatically
result <- tcm_agent("Search the targets of Huangqi (Astragalus)")
cat(result$text)
# The result also contains any generated artifacts
result$artifactsThe built-in router matches user queries to relevant tools using keyword patterns. For example, “enrichment” routes to GO/KEGG tools, “ppi” routes to network tools, and “machine learning” routes to ML screening tools. When multiple patterns match, tools are merged.
9.4.2 Interactive session: tcm_chat()
tcm_chat() opens an interactive REPL for multi-turn exploratory analysis:
╔══════════════════════════════════════════════════╗
║ TCM-Pharmacist · Interactive Session ║
║ Type /help for commands · /quit to exit ║
╚══════════════════════════════════════════════════╝
[1] You > Search targets of Huangqi and sepsis, then compute intersection
✓ Route: target_lookup + disease_lookup (high)
✓ Tools called: search_herb_records → search_disease_targets → compute_target_intersection
✓ New artifacts: intersect_001
Agent > Found 121 intersection targets between Huangqi and sepsis...
[2] You > Run GO and KEGG enrichment on the intersection
✓ Route: enrichment (high)
✓ Tools called: run_go_enrichment → run_kegg_enrichment
✓ New artifacts: enrich_001, enrich_002
Agent > GO enrichment identified 245 significant BP terms...
[3] You > /artifacts
intersect_001 gene_list character[121] 2026-04-10 14:32:01
enrich_001 enrichment enrichResult 2026-04-10 14:32:15
enrich_002 enrichment enrichResult 2026-04-10 14:32:16
[4] You > /save 10x8
✓ Exported 3 artifacts to tcm_output/
[5] You > /quit
Key commands: /help, /artifacts, /save [WxH], /history, /stats, /quit.
After each turn that executes one or more tools, tcm_chat() creates a deterministic replay script in .GlobalEnv. Script objects are named sequentially (tcm_script_001, tcm_script_002, etc.) and contain the actual tool names, arguments, and artifact dependencies used during that turn:
tcm_script_001 # print the generated R script
writeLines(tcm_script_001, "analysis_turn_001.R")
source(textConnection(tcm_script_001)) # rerun the recorded tool sequenceThe session returns history, artifacts, the complete cross-turn tool_calls trajectory, and the exported script names for programmatic access:
9.4.3 Programmatic agent: create_tcm_task_agent()
For scripted workflows, create a reusable agent and execute tasks programmatically:
agent <- create_tcm_task_agent()
r1 <- run_tcm_task(agent, "Search Huangqi targets and sepsis targets, compute intersection")
r2 <- run_tcm_task(agent, "Run GO enrichment on the intersection genes")
r3 <- run_tcm_task(agent, "Build PPI network and rank hub genes")
# $tool_calls contains calls from every agent step; $generation_result keeps
# the complete aisdk result for audit or debugging.
r3$text
r3$tool_calls9.4.4 Artifact management
All analysis results are automatically stored as artifacts with generated IDs (e.g., enrich_001, ppi_002). Tool-generated artifacts are also exported to .GlobalEnv under the same ID. When the user requests a visualization, the agent must create a plot artifact, export the plot object to .GlobalEnv, and report its object name so that it can be inspected directly in RStudio.
9.5 Skill layer
Skills are domain-knowledge packages that guide the agent through complex multi-step workflows. Unlike tools (which perform specific operations), skills provide strategic context — what to do, in what order, and what to watch out for.
9.5.1 Built-in skills
TCMDATA ships with two package skills, and also loads aisdk’s skill-creator by default:
| Skill | Purpose |
|---|---|
tcm-network-pharmacology |
Guides the full network pharmacology workflow (target retrieval → intersection → PPI → enrichment → validation → report). Only activates when user explicitly requests systematic analysis |
analysis-preferences |
Background constraint layer. Sets default parameters (e.g., p-value cutoffs, PPI score thresholds, visualization defaults) and quality standards. Active on every turn |
skill-creator |
Meta-skill from aisdk for creating new custom skills |
9.5.2 How skills work
When the agent detects a request that matches a skill’s trigger condition, the skill’s instructions are injected into the conversation context. For example, asking “Help me do a network pharmacology analysis of Huangqi treating sepsis” activates the tcm-network-pharmacology skill, which guides the agent through:
- Phase 1: Target collection (herb targets + disease targets → intersection)
- Phase 2: Network & enrichment (PPI construction, GO/KEGG analysis, hub gene ranking)
- Phase 3: Expression validation (WGCNA, ML screening, DEG integration — if data available)
- Phase 4: Single-cell validation (if scRNA-seq data available)
- Phase 5: Literature validation, cross-database verification, and report generation
Importantly, the skill is scope-aware: asking “Just search the targets of Huangqi” will only run the relevant step, not the full pipeline.
9.5.3 Creating custom skills
Use the skill-creator skill to define new skills. A minimal skill only needs a SKILL.md file with YAML frontmatter:
# Example: ask the agent to create a new skill
tcm_chat()
# > Help me create a skill for molecular docking analysisA skill directory can optionally contain references/ (detailed documentation), scripts/ (executable scripts), and assets/ (templates).
9.5.4 Managing skills
By default, the agent uses the TCMDATA skill directory plus aisdk’s skill-creator. To customize preferences or add new skills, initialize a local skills directory:
This creates a local tcm_skills/ directory containing the TCMDATA package skills. You can then:
# Edit analysis preferences (e.g., change default p-value cutoff)
file.edit("tcm_skills/analysis-preferences/SKILL.md")
# Add a new skill created by skill-creator
# Just place the skill directory under tcm_skills/:
# tcm_skills/my-new-skill/SKILL.md
# Check which skills directory is currently active
tcm_skill_dir()
# Switch to a different skills directory
tcm_use_skills("path/to/other/skills")
# Reset to package defaults
tcm_reset_skills()Once a local skills directory is active, all agent functions (tcm_agent(), tcm_chat(), create_tcm_task_agent()) will automatically use it.
9.6 Session information
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] caret_7.0-1 lattice_0.22-9 org.Hs.eg.db_3.23.1
#> [4] AnnotationDbi_1.74.0 IRanges_2.46.0 S4Vectors_0.50.2
#> [7] Biobase_2.72.0 BiocGenerics_0.58.1 generics_0.1.4
#> [10] clusterProfiler_4.20.0 aplot_0.3.1 ggrepel_0.9.8
#> [13] ggtangle_0.1.2 igraph_2.3.3 ggplot2_4.0.3
#> [16] dplyr_1.2.1 ivolcano_0.0.6 enrichplot_1.32.0
#> [19] TCMDATA_1.0.0
#>
#> loaded via a namespace (and not attached):
#> [1] splines_4.6.1 ggplotify_0.1.3 tibble_3.3.1
#> [4] polyclip_1.10-7 hardhat_1.4.3 enrichit_0.2.1
#> [7] pROC_1.19.1 rpart_4.1.27 lifecycle_1.0.5
#> [10] httr2_1.3.0 doParallel_1.0.17 globals_0.19.1
#> [13] processx_3.9.0 MASS_7.3-65 magrittr_2.0.5
#> [16] sass_0.4.10 rmarkdown_2.31 jquerylib_0.1.4
#> [19] yaml_2.3.12 otel_0.2.0 ggvenn_0.1.19
#> [22] DBI_1.3.0 RColorBrewer_1.1-3 lubridate_1.9.5
#> [25] purrr_1.2.2 yulab.utils_0.2.5 nnet_7.3-20
#> [28] tweenr_2.0.3 rappdirs_0.3.4 ipred_0.9-16
#> [31] aisdk_1.5.0 gdtools_0.5.1 circlize_0.4.18
#> [34] lava_1.9.3 listenv_1.0.0 tidytree_0.4.8
#> [37] fru_1.0.0 parallelly_1.48.0 codetools_0.2-20
#> [40] DOSE_4.6.0 ggforce_0.5.0 tidyselect_1.2.1
#> [43] shape_1.4.6.1 farver_2.1.2 matrixStats_1.5.0
#> [46] Seqinfo_1.2.0 jsonlite_2.0.0 GetoptLong_1.1.1
#> [49] e1071_1.7-17 ggridges_0.5.7 ggalluvial_0.12.6
#> [52] survival_3.8-6 iterators_1.0.14 systemfonts_1.3.2
#> [55] foreach_1.5.2 tools_4.6.1 ggnewscale_0.5.2
#> [58] treeio_1.36.1 Rcpp_1.1.2 glue_1.8.1
#> [61] prodlim_2026.03.11 gridExtra_2.3.1 xfun_0.60
#> [64] qvalue_2.44.0 withr_3.0.3 fastmap_1.2.0
#> [67] callr_3.8.0 digest_0.6.39 timechange_0.4.0
#> [70] R6_2.6.1 gridGraphics_0.5-1 colorspace_2.1-3
#> [73] GO.db_3.23.1 RSQLite_3.53.3 tidyr_1.3.2
#> [76] fontLiberation_0.1.0 data.table_1.18.6.1 recipes_1.4.0
#> [79] class_7.3-23 httr_1.4.8 htmlwidgets_1.6.4
#> [82] scatterpie_0.2.6 ModelMetrics_1.2.2.2 pkgconfig_2.0.3
#> [85] gtable_0.3.6 timeDate_4052.112 blob_1.3.0
#> [88] ComplexHeatmap_2.28.0 S7_0.2.2 XVector_0.52.0
#> [91] htmltools_0.5.9 fontBitstreamVera_0.1.1 bookdown_0.48
#> [94] clue_0.3-68 scales_1.4.0 png_0.1-9
#> [97] gower_1.0.2 Boruta_10.0.0 ggfun_0.2.1
#> [100] knitr_1.51 rstudioapi_0.19.0 reshape2_1.4.5
#> [103] rjson_0.2.23 nlme_3.1-169 proxy_0.4-29
#> [106] cachem_1.1.0 GlobalOptions_0.1.4 stringr_1.6.0
#> [109] parallel_4.6.1 pillar_1.11.1 grid_4.6.1
#> [112] vctrs_0.7.3 randomForest_4.7-1.2 tidydr_0.0.6
#> [115] cluster_2.1.8.2 evaluate_1.0.5 cli_3.6.6
#> [118] compiler_4.6.1 rlang_1.3.0 crayon_1.5.3
#> [121] future.apply_1.20.2 labeling_0.4.3 ps_1.9.3
#> [124] plyr_1.8.9 fs_2.1.0 ggiraph_0.9.6
#> [127] stringi_1.8.9 viridisLite_0.4.3 Biostrings_2.80.1
#> [130] lazyeval_0.2.3 glmnet_5.0 GOSemSim_2.38.3
#> [133] fontquiver_0.2.1 Matrix_1.7-6 patchwork_1.3.2
#> [136] bit64_4.8.4 future_1.75.0 KEGGREST_1.52.2
#> [139] kernlab_0.9-33 memoise_2.0.1 bslib_0.12.0
#> [142] ggtree_4.2.0 bit_4.6.0 xgboost_3.2.1.1
#> [145] ape_5.8-1 gson_0.2.1