RooAgent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rooagent-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aman Desai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: RooAgent
3
+ Version: 0.1.0
4
+ Summary: Agent For ROOT
5
+ Author-email: Aman Desai <amanmukeshdesai@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/amanmdesai/RooAgent
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: langchain
12
+ Requires-Dist: langchain-core
13
+ Requires-Dist: langgraph
14
+ Requires-Dist: langchain-openai
15
+ Requires-Dist: uproot
16
+ Requires-Dist: pandas
17
+ Requires-Dist: numpy
18
+ Dynamic: license-file
19
+
20
+ # RooAgent
21
+ Agent for ROOT
22
+
23
+
24
+ Download and Install:
25
+
26
+ ```
27
+ git clone https://github.com/amanmdesai/RooAgent.git
28
+ cd RooAgent
29
+ pip install .
30
+ ```
31
+
32
+ Installation:
33
+
34
+ ```
35
+ pip install rooagent
36
+ ```
37
+
38
+ External Dependencies
39
+
40
+ Requires a pre-installed version of ROOT that is compatible to work with Python
41
+
42
+ ```
43
+ CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
44
+ ```
45
+
46
+ Install ollama's gpt-oss model:
47
+
48
+ ```
49
+ curl -fsSL https://ollama.com/install.sh | sh
50
+ ollama pull gpt-oss
51
+ ```
52
+
@@ -0,0 +1,33 @@
1
+ # RooAgent
2
+ Agent for ROOT
3
+
4
+
5
+ Download and Install:
6
+
7
+ ```
8
+ git clone https://github.com/amanmdesai/RooAgent.git
9
+ cd RooAgent
10
+ pip install .
11
+ ```
12
+
13
+ Installation:
14
+
15
+ ```
16
+ pip install rooagent
17
+ ```
18
+
19
+ External Dependencies
20
+
21
+ Requires a pre-installed version of ROOT that is compatible to work with Python
22
+
23
+ ```
24
+ CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
25
+ ```
26
+
27
+ Install ollama's gpt-oss model:
28
+
29
+ ```
30
+ curl -fsSL https://ollama.com/install.sh | sh
31
+ ollama pull gpt-oss
32
+ ```
33
+
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: RooAgent
3
+ Version: 0.1.0
4
+ Summary: Agent For ROOT
5
+ Author-email: Aman Desai <amanmukeshdesai@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/amanmdesai/RooAgent
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: langchain
12
+ Requires-Dist: langchain-core
13
+ Requires-Dist: langgraph
14
+ Requires-Dist: langchain-openai
15
+ Requires-Dist: uproot
16
+ Requires-Dist: pandas
17
+ Requires-Dist: numpy
18
+ Dynamic: license-file
19
+
20
+ # RooAgent
21
+ Agent for ROOT
22
+
23
+
24
+ Download and Install:
25
+
26
+ ```
27
+ git clone https://github.com/amanmdesai/RooAgent.git
28
+ cd RooAgent
29
+ pip install .
30
+ ```
31
+
32
+ Installation:
33
+
34
+ ```
35
+ pip install rooagent
36
+ ```
37
+
38
+ External Dependencies
39
+
40
+ Requires a pre-installed version of ROOT that is compatible to work with Python
41
+
42
+ ```
43
+ CERN ROOT: - ROOT >= 6.34 (https://root.cern/)
44
+ ```
45
+
46
+ Install ollama's gpt-oss model:
47
+
48
+ ```
49
+ curl -fsSL https://ollama.com/install.sh | sh
50
+ ollama pull gpt-oss
51
+ ```
52
+
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ RooAgent.egg-info/PKG-INFO
5
+ RooAgent.egg-info/SOURCES.txt
6
+ RooAgent.egg-info/dependency_links.txt
7
+ RooAgent.egg-info/entry_points.txt
8
+ RooAgent.egg-info/requires.txt
9
+ RooAgent.egg-info/top_level.txt
10
+ rooagent/__init__.py
11
+ rooagent/agent.py
12
+ rooagent/tools/__init__.py
13
+ rooagent/tools/converter.py
14
+ rooagent/tools/fit_tools.py
15
+ rooagent/tools/histogram_tools.py
16
+ rooagent/tools/plot_tools.py
17
+ rooagent/tools/rdataframe_tools.py
18
+ rooagent/tools/tfile.py
19
+ rooagent/tools/utils.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ rooagent = rooagent.agent:main
@@ -0,0 +1,7 @@
1
+ langchain
2
+ langchain-core
3
+ langgraph
4
+ langchain-openai
5
+ uproot
6
+ pandas
7
+ numpy
@@ -0,0 +1 @@
1
+ rooagent
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "RooAgent"
7
+ version = "0.1.0"
8
+ description = "Agent For ROOT"
9
+ authors = [
10
+ { name="Aman Desai", email="amanmukeshdesai@gmail.com"}
11
+ ]
12
+ readme = "README.md"
13
+ license = {text = "MIT"}
14
+ dependencies = [
15
+ "langchain",
16
+ "langchain-core",
17
+ "langgraph",
18
+ "langchain-openai",
19
+ "uproot",
20
+ "pandas",
21
+ "numpy",
22
+ ]
23
+
24
+ requires-python = ">=3.10"
25
+ [project.scripts]
26
+ rooagent = "rooagent.agent:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/amanmdesai/RooAgent"
@@ -0,0 +1,2 @@
1
+ # Makes rooagent a package
2
+ from .agent import model_with_tools, agent, tools
@@ -0,0 +1,153 @@
1
+ from langchain_openai import ChatOpenAI
2
+ from langgraph.graph import StateGraph, START, END
3
+ from langchain.messages import HumanMessage, SystemMessage, AnyMessage, ToolMessage
4
+ from typing_extensions import TypedDict, Annotated
5
+ from langgraph.prebuilt import ToolNode
6
+ from langgraph.checkpoint.memory import InMemorySaver
7
+ import operator
8
+ import os
9
+ # Import all tools
10
+ from .tools import *
11
+
12
+ # -----------------------------
13
+ # SYSTEM PROMPT (Clean Version)
14
+ # -----------------------------
15
+ SYSTEM_PROMPT = """
16
+ ROOT-based HEP analysis assistant.
17
+
18
+ RULES:
19
+ 1. Always use tools. Never guess.
20
+ 2. Verify everything (files, trees, branches) before use.
21
+ 3. Workflow: inspect file -> trees -> branches -> analyze.
22
+ 4. Auto-use single tree; list if multiple.
23
+ 5. Handle missing data and empty results safely.
24
+ 6. Compare signal vs background when relevant.
25
+ 7. Output: clear plots, labeled axes, report statistics.
26
+ 8. No assumptions. State uncertainty if unverified.
27
+
28
+ Prioritize correctness, efficiency, reproducibility.
29
+ """
30
+
31
+ # -----------------------------
32
+ # Initialize Model (GitHub Copilot / GitHub Models API)
33
+ # -----------------------------
34
+ DEFAULT_MODEL_NAME = "gpt-4o-mini"
35
+ MODEL_NAME = os.getenv("MODEL", DEFAULT_MODEL_NAME)
36
+
37
+ model = ChatOpenAI(
38
+ model=MODEL_NAME,
39
+ api_key=os.getenv("GITHUB_TOKEN"),
40
+ base_url="https://models.inference.ai.azure.com"
41
+ )
42
+
43
+ # Bind tools
44
+ tools = [
45
+ list_root_file_contents,
46
+ list_tree_branches,
47
+ get_histogram_stats,
48
+ apply_cut_and_count,
49
+ compute_significance,
50
+ plot_tree_variable,
51
+ compare_tree_variables,
52
+ root_tree_to_csv,
53
+ define_variable_and_plot,
54
+ fit_tree_variable,
55
+ fit_histogram,
56
+ draw_histograms_same_canvas,
57
+ draw_2d_histogram,
58
+ find_optimal_cut,
59
+ draw_1d_histogram,
60
+ define_variable,
61
+ draw_2d_histogram_from_tree,
62
+ generate_cutflow,
63
+ discover_root_data,
64
+ list_ttrees,
65
+ ]
66
+
67
+ tools_by_name = {tool.name: tool for tool in tools}
68
+ model_with_tools = model.bind_tools(tools)
69
+
70
+ # -----------------------------
71
+ # State Definition
72
+ # -----------------------------
73
+ class MessagesState(TypedDict):
74
+ messages: Annotated[list[AnyMessage], operator.add]
75
+ llm_calls: int
76
+
77
+ # -----------------------------
78
+ # Tool Node
79
+ # -----------------------------
80
+ def tool_node(state: MessagesState):
81
+ results = []
82
+
83
+ for tool_call in state["messages"][-1].tool_calls:
84
+
85
+ tool = tools_by_name[tool_call["name"]]
86
+ observation = tool.invoke(tool_call["args"])
87
+
88
+ results.append(
89
+ ToolMessage(
90
+ content=str(observation),
91
+ tool_call_id=tool_call["id"]
92
+ )
93
+ )
94
+
95
+ return {"messages": results}
96
+
97
+ # -----------------------------
98
+ # LLM Node
99
+ # -----------------------------
100
+ def llm_call(state: MessagesState):
101
+ response = model_with_tools.invoke(
102
+ [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
103
+ )
104
+
105
+ return {
106
+ "messages": [response],
107
+ "llm_calls": state.get("llm_calls", 0) + 1
108
+ }
109
+
110
+ # -----------------------------
111
+ # Routing
112
+ # -----------------------------
113
+ def should_continue(state: MessagesState):
114
+ last_message = state["messages"][-1]
115
+ if getattr(last_message, "tool_calls", None):
116
+ return "tool_node"
117
+ return END
118
+
119
+ # -----------------------------
120
+ # Build Graph
121
+ # -----------------------------
122
+ builder = StateGraph(MessagesState)
123
+
124
+ builder.add_node("llm_call", llm_call)
125
+ builder.add_node("tool_node",ToolNode(tools,handle_tool_errors=True))
126
+ # tool_node)
127
+
128
+ builder.add_edge(START, "llm_call")
129
+ builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
130
+ builder.add_edge("tool_node", "llm_call")
131
+
132
+ agent = builder.compile()
133
+
134
+ # -----------------------------
135
+ # CLI
136
+ # -----------------------------
137
+ def main():
138
+ print(f"\nROOT Physics Analysis Agent using ({MODEL_NAME})")
139
+ print("Type 'exit' to quit\n")
140
+
141
+ state = {"messages": [], "llm_calls": 0}
142
+
143
+ while True:
144
+ user_input = input("User: ")
145
+
146
+ if user_input.lower() in ["exit", "quit"]:
147
+ print("Thanks for using RooAgent!")
148
+ break
149
+
150
+ # FIXED BUG HERE
151
+ state["messages"].append(HumanMessage(content=user_input))
152
+
153
+ state = agent.invoke(state)
@@ -0,0 +1,29 @@
1
+ from .fit_tools import *
2
+ from .histogram_tools import *
3
+ from .converter import *
4
+ from .plot_tools import *
5
+ from .rdataframe_tools import *
6
+ from .tfile import *
7
+ from .utils import *
8
+
9
+
10
+ # ================= ROOT STYLE =================
11
+ ROOT.gStyle.SetOptStat(0)
12
+
13
+ ROOT.gStyle.SetTitleFont(42, "XYZ")
14
+ ROOT.gStyle.SetLabelFont(42, "XYZ")
15
+
16
+ ROOT.gStyle.SetTitleSize(0.05, "XYZ")
17
+ ROOT.gStyle.SetLabelSize(0.04, "XYZ")
18
+
19
+ ROOT.gStyle.SetPadLeftMargin(0.12)
20
+ ROOT.gStyle.SetPadBottomMargin(0.12)
21
+
22
+ ROOT.gStyle.SetLegendBorderSize(0)
23
+ ROOT.gStyle.SetLegendFillColor(0)
24
+
25
+ ROOT.gStyle.SetFrameLineWidth(2)
26
+ ROOT.gStyle.SetLineWidth(2)
27
+
28
+ ROOT.gStyle.SetPadGridX(False)
29
+ ROOT.gStyle.SetPadGridY(False)
@@ -0,0 +1,37 @@
1
+ from typing import List
2
+ import ROOT
3
+ import pandas as pd
4
+ from langchain_core.tools import tool
5
+
6
+ @tool
7
+ def root_tree_to_csv(file_path: str, tree_name: str, branches: List[str], output_csv: str, max_vector_size: int = 5) -> str:
8
+ """
9
+ Convert ROOT TTree to CSV, flattening vector branches.
10
+
11
+ Parameters
12
+ ----------
13
+ file_path : str
14
+ Path to ROOT file.
15
+ tree_name : str
16
+ Name of TTree.
17
+ branches : List[str]
18
+ Branches to extract (scalar or vector).
19
+ output_csv : str
20
+ Output CSV file path.
21
+ max_vector_size : int
22
+ Max number of elements for vector branches (columns will be suffixed _0, _1, ...).
23
+ """
24
+ df = ROOT.RDataFrame(tree_name, file_path)
25
+ data = df.AsNumpy(branches)
26
+
27
+ flat_dict = {}
28
+ for branch, array in data.items():
29
+ if array.ndim == 1: # scalar
30
+ flat_dict[branch] = array
31
+ else: # vector branch
32
+ for i in range(max_vector_size):
33
+ flat_dict[f"{branch}_{i}"] = [a[i] if i < len(a) else None for a in array]
34
+
35
+ pandas_df = pd.DataFrame(flat_dict)
36
+ pandas_df.to_csv(output_csv, index=False)
37
+ return f"Flattened CSV saved to {output_csv}"
@@ -0,0 +1,173 @@
1
+ import ROOT
2
+ from langchain_core.tools import tool
3
+
4
+ @tool
5
+ def fit_tree_variable(
6
+ file_path: str,
7
+ tree_name: str,
8
+ variable: str,
9
+ bins: int,
10
+ xmin: float,
11
+ xmax: float,
12
+ fit_function: str,
13
+ output_plot: str
14
+ ) -> str:
15
+ """
16
+ Create a histogram from a ROOT TTree variable, perform a statistical fit,
17
+ and save the resulting plot with legend.
18
+
19
+ Parameters
20
+ ----------
21
+ file_path : str
22
+ Path to the ROOT file containing the TTree.
23
+ tree_name : str
24
+ Name of the TTree to read.
25
+ variable : str
26
+ Name of the variable to histogram and fit.
27
+ bins : int
28
+ Number of bins in the histogram.
29
+ xmin : float
30
+ Minimum x-axis value.
31
+ xmax : float
32
+ Maximum x-axis value.
33
+ fit_function : str
34
+ ROOT fit function (examples: 'gaus', 'expo', 'pol1', 'pol2').
35
+ output_plot : str
36
+ File name where the canvas with histogram and fit will be saved.
37
+
38
+ Returns
39
+ -------
40
+ str
41
+ Summary of fit parameters and chi-square/NDF.
42
+ """
43
+ df = ROOT.RDataFrame(tree_name, file_path)
44
+ hist_ptr = df.Histo1D((variable, variable, bins, xmin, xmax), variable)
45
+ h = hist_ptr.GetValue()
46
+ ROOT.SetOwnership(h, False)
47
+
48
+ canvas = ROOT.TCanvas("c_fit", "", 900, 700)
49
+ #canvas.SetGrid()
50
+
51
+ # Perform fit
52
+ h.Fit(fit_function, "S")
53
+
54
+ # Style histogram
55
+ h.SetLineWidth(3)
56
+ h.SetLineColor(ROOT.kBlue + 1)
57
+ h.SetMarkerStyle(20)
58
+ h.SetMarkerSize(1.2)
59
+ h.GetXaxis().SetTitle(variable)
60
+ h.GetYaxis().SetTitle("Events")
61
+ h.Draw("E")
62
+
63
+ # Style fit function
64
+ func = h.GetFunction(fit_function)
65
+ if func:
66
+ func.SetLineColor(ROOT.kRed)
67
+ func.SetLineWidth(2)
68
+
69
+ # Add legend
70
+ legend = ROOT.TLegend(0.65, 0.7, 0.88, 0.88)
71
+ legend.SetBorderSize(0)
72
+ legend.SetFillStyle(0)
73
+ legend.AddEntry(h, "Histogram", "lep")
74
+ if func:
75
+ legend.AddEntry(func, f"Fit: {fit_function}", "l")
76
+ legend.Draw()
77
+
78
+ canvas.Update()
79
+ canvas.SaveAs(output_plot)
80
+
81
+ # Extract fit parameters
82
+ params = [f"p{i} = {func.GetParameter(i):.4f}" for i in range(func.GetNpar())]
83
+ chi2 = func.GetChisquare()
84
+ ndf = func.GetNDF()
85
+
86
+ return (
87
+ f"Fit function: {fit_function}\n"
88
+ f"Parameters: {', '.join(params)}\n"
89
+ f"Chi2/NDF: {chi2:.3f}/{ndf}"
90
+ )
91
+
92
+
93
+ @tool
94
+ def fit_histogram(
95
+ file_path: str,
96
+ hist_name: str,
97
+ fit_function: str,
98
+ output_plot: str
99
+ ) -> str:
100
+ """
101
+ Fit an existing histogram in a ROOT file, overlay the fit, and save with legend.
102
+
103
+ Parameters
104
+ ----------
105
+ file_path : str
106
+ Path to the ROOT file containing the histogram.
107
+ hist_name : str
108
+ Name of the histogram to fit.
109
+ fit_function : str
110
+ ROOT fit function (examples: 'gaus', 'expo', 'pol1', 'pol2').
111
+ output_plot : str
112
+ File name where the canvas with histogram and fit will be saved.
113
+
114
+ Returns
115
+ -------
116
+ str
117
+ Summary of fit parameters and chi-square/NDF.
118
+ """
119
+ f = ROOT.TFile.Open(file_path)
120
+ if not f or f.IsZombie():
121
+ return "Error: could not open ROOT file."
122
+
123
+ h = f.Get(hist_name)
124
+ if not h:
125
+ f.Close()
126
+ return f"Histogram '{hist_name}' not found."
127
+
128
+ ROOT.SetOwnership(h, False)
129
+
130
+ canvas = ROOT.TCanvas("c_fit_hist", "", 900, 700)
131
+ #canvas.SetGrid()
132
+
133
+ # Perform fit
134
+ h.Fit(fit_function, "S")
135
+
136
+ # Style histogram
137
+ h.SetLineWidth(3)
138
+ h.SetLineColor(ROOT.kBlue + 1)
139
+ h.SetMarkerStyle(20)
140
+ h.SetMarkerSize(1.2)
141
+ h.Draw("E")
142
+
143
+ # Style fit function
144
+ func = h.GetFunction(fit_function)
145
+ if func:
146
+ func.SetLineColor(ROOT.kRed)
147
+ func.SetLineWidth(2)
148
+
149
+ # Add legend
150
+ legend = ROOT.TLegend(0.65, 0.7, 0.88, 0.88)
151
+ legend.SetBorderSize(0)
152
+ legend.SetFillStyle(0)
153
+ legend.AddEntry(h, "Histogram", "lep")
154
+ if func:
155
+ legend.AddEntry(func, f"Fit: {fit_function}", "l")
156
+ legend.Draw()
157
+
158
+ canvas.Update()
159
+ canvas.SaveAs(output_plot)
160
+
161
+ # Extract fit parameters
162
+ params = [f"p{i}={func.GetParameter(i):.4f}" for i in range(func.GetNpar())]
163
+ chi2 = func.GetChisquare()
164
+ ndf = func.GetNDF()
165
+
166
+ f.Close()
167
+
168
+ return (
169
+ f"Histogram fit completed.\n"
170
+ f"Function: {fit_function}\n"
171
+ f"Parameters: {', '.join(params)}\n"
172
+ f"Chi2/NDF: {chi2:.3f}/{ndf}"
173
+ )
@@ -0,0 +1,38 @@
1
+ from pydantic import BaseModel
2
+ import ROOT
3
+ from langchain_core.tools import tool
4
+
5
+
6
+ @tool
7
+ def get_histogram_stats(file_path: str, hist_name: str) -> str:
8
+ """
9
+ Retrieve basic statistical information (mean, RMS, entries) of a histogram
10
+ stored in a ROOT file.
11
+
12
+ Parameters
13
+ ----------
14
+ file_path : str
15
+ Path to the ROOT file containing the histogram.
16
+ hist_name : str
17
+ Name of the histogram within the ROOT file.
18
+
19
+ Returns
20
+ -------
21
+ str
22
+ A formatted string reporting the histogram's mean, RMS, and total entries.
23
+ Example:
24
+ "myHist -> Mean: 0.123, RMS: 1.234, Entries: 1000"
25
+
26
+ """
27
+ f = ROOT.TFile.Open(file_path)
28
+ if not f or f.IsZombie():
29
+ return f"Error: could not open file {file_path}."
30
+ h = f.Get(hist_name)
31
+ if not h:
32
+ f.Close()
33
+ return f"Error: histogram '{hist_name}' not found in file {file_path}."
34
+ mean = h.GetMean()
35
+ rms = h.GetRMS()
36
+ entries = int(h.GetEntries())
37
+ f.Close()
38
+ return f"{hist_name} -> Mean: {mean:.3f}, RMS: {rms:.3f}, Entries: {entries}"