Skip to content

Commit ebf7848

Browse files
authored
Merge pull request #1183 from optuna/feat/edit-graph-generated-by-llm
Introduce edit functionality to plotly graph generation feature
2 parents 71c387e + a5032ad commit ebf7848

8 files changed

Lines changed: 519 additions & 59 deletions

File tree

jupyterlab/src/apiClient.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
ParamImportancesResponse,
1010
PlotResponse,
1111
PlotType,
12+
ReGeneratePlotlyGraphQueryRequest,
13+
ReGeneratePlotlyGraphQueryResponse,
1214
RenameStudyResponse,
1315
StudyDetail,
1416
StudyDetailResponse,
@@ -318,4 +320,11 @@ export class JupyterlabAPIClient extends APIClient {
318320
"Generate plotly graph query is not implemented in JupyterLab API client."
319321
)
320322
}
323+
callReGeneratePlotlyGraphQuery(
324+
request: ReGeneratePlotlyGraphQueryRequest
325+
): Promise<ReGeneratePlotlyGraphQueryResponse> {
326+
throw new Error(
327+
"Re-generate plotly graph query is not implemented in JupyterLab API client."
328+
)
329+
}
321330
}

optuna_dashboard/llm/_api_views.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from optuna_dashboard.llm.prompt_templates._generate_plotly_graph import (
1111
get_generate_plotly_graph_prompt,
1212
get_generate_plotly_graph_title_prompt,
13+
get_re_generate_plotly_graph_prompt,
1314
)
1415
from .._bottle_util import json_api_view
1516
from .prompt_templates._trial_filter import get_trial_filtering_prompt
@@ -103,3 +104,42 @@ def get_generate_plotly_graph_func_str() -> dict[str, str]:
103104
"generate_plotly_graph_func_str": generate_plotly_graph_func_str,
104105
"generate_plotly_graph_title": generated_plotly_graph_title,
105106
}
107+
108+
@app.post("/api/llm/re_generate_plotly_graph_query")
109+
@json_api_view
110+
def get_re_generate_plotly_graph_func_str() -> dict[str, str]:
111+
if llm_provider is None:
112+
response.status = 400 # Bad Request
113+
return {"reason": "No access to the LLM provider."}
114+
115+
modification_request_query = request.json.get("modification_request_query", "")
116+
previous_function = request.json.get("previous_function", "")
117+
if not modification_request_query or not previous_function:
118+
response.status = 400 # Bad Request
119+
return {"reason": "Insufficient data provided."}
120+
121+
func_str = request.json.get("last_response", {}).get("func_str")
122+
err_msg = request.json.get("last_response", {}).get("error_message")
123+
124+
try:
125+
prompt = get_re_generate_plotly_graph_prompt(
126+
previous_function,
127+
modification_request_query,
128+
func_str,
129+
err_msg,
130+
)
131+
re_generated_plotly_graph_func_str = llm_provider.call(prompt)
132+
except RateLimitExceeded as e:
133+
response.status = 429 # Too Many Requests
134+
reason = f"Rate limit exceeded. Try again later. The actual error: {str(e)}"
135+
return {"reason": reason}
136+
except InvalidAuthentication as e:
137+
response.status = 401 # Unauthorized
138+
reason = f"Invalid authentication. Check your API key. The actual error: {str(e)}"
139+
return {"reason": reason}
140+
except Exception as e:
141+
response.status = 500
142+
return {"reason": str(e)}
143+
144+
response.status = 200
145+
return {"re_generated_plotly_graph_func_str": re_generated_plotly_graph_func_str}

optuna_dashboard/llm/prompt_templates/_generate_plotly_graph.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,44 @@
159159
Return ONLY the final title string.
160160
""" # noqa: E501
161161

162+
_RE_GENERATE_PLOTLY_GRAPH_PROMPT_TEMPLATE = """Please rewrite a JavaScript function that, given the Study object named `study`, returns an array of Plotly trace objects (PlotData[]) to visualize the user's request. Apply the user's modification request to the previously generated function while preserving all unaffected behavior.
163+
164+
Requirements:
165+
1. Input: a variable `study` of type Study (see definitions below). Do not redefine `study`.
166+
2. Output: an array (e.g. `[trace1, trace2, ...]`) of Plotly trace objects (each object corresponds to a Plotly `data` item). Do NOT include the layout object, only data traces.
167+
3. The function must be a pure function: it must not modify `study` or any trials inside it.
168+
4. No network requests, no DOM access, no external I/O, no eval, no Function constructor.
169+
5. Handle missing or incomplete trials safely: skip trials where required values are undefined (e.g. failed trials without `values`).
170+
6. Ensure arrays `x` and `y` are the same length for each trace. Filter out undefined entries first.
171+
7. Avoid extremely large custom objects; keep each trace under ~5000 points (if more, sample evenly).
172+
8. Return only JavaScript function code; no surrounding code fences or commentary.
173+
174+
Study / Trial type definitions (for reference):
175+
{study_definition_in_typescript}
176+
177+
====== Instructions Finished ======
178+
179+
Context:
180+
181+
Previously generated function:
182+
------
183+
{previous_function}
184+
------
185+
186+
Your task:
187+
Using the context above, return a valid JavaScript function code as is (note: do not wrap your output using code blocks such as ```javascript``` ).
188+
At the very top of the function, include a brief comment summarizing how you applied the modification and any reasonable assumptions you had to make.
189+
If the modification request is ambiguous, make a reasonable assumption and note it in a code comment at the top of the function.
190+
191+
Here is the user's modification request:
192+
------
193+
{modification_request_query}
194+
------
195+
196+
====== User Query Finished ======
197+
{re_generate_plotly_graph_failure_message}
198+
""" # noqa: E501
199+
162200

163201
def get_generate_plotly_graph_prompt(
164202
user_query: str, last_func_str: str | None = None, last_error_msg: str | None = None
@@ -182,3 +220,23 @@ def get_generate_plotly_graph_title_prompt(user_query: str, generated_function:
182220
generated_function=generated_function,
183221
study_definition_in_typescript=_STUDY_DEFINITION_IN_TYPESCRIPT,
184222
)
223+
224+
225+
def get_re_generate_plotly_graph_prompt(
226+
previous_function: str,
227+
modification_request_query: str,
228+
last_func_str: str | None = None,
229+
last_error_msg: str | None = None,
230+
) -> str:
231+
failure_msg = ""
232+
if last_func_str is not None:
233+
failure_msg = GENERATE_PLOTLY_GRAPH_FAILURE_MESSAGE_TEMPLATE.format(
234+
last_func_str=last_func_str,
235+
error_message=last_error_msg or "No Error Message Provided.",
236+
)
237+
return _RE_GENERATE_PLOTLY_GRAPH_PROMPT_TEMPLATE.format(
238+
previous_function=previous_function,
239+
modification_request_query=modification_request_query,
240+
re_generate_plotly_graph_failure_message=failure_msg,
241+
study_definition_in_typescript=_STUDY_DEFINITION_IN_TYPESCRIPT,
242+
)

optuna_dashboard/ts/apiClient.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ export type GeneratePlotlyGraphQueryResponse = {
164164
generate_plotly_graph_title: string
165165
}
166166

167+
export type ReGeneratePlotlyGraphQueryRequest = {
168+
modification_request_query: string
169+
previous_function: string
170+
last_response?: {
171+
func_str: string
172+
error_message: string
173+
}
174+
}
175+
176+
export type ReGeneratePlotlyGraphQueryResponse = {
177+
re_generated_plotly_graph_func_str: string
178+
}
179+
167180
export abstract class APIClient {
168181
constructor() {}
169182

@@ -291,4 +304,7 @@ export abstract class APIClient {
291304
abstract callGeneratePlotlyGraphQuery(
292305
request: GeneratePlotlyGraphQueryRequest
293306
): Promise<GeneratePlotlyGraphQueryResponse>
307+
abstract callReGeneratePlotlyGraphQuery(
308+
request: ReGeneratePlotlyGraphQueryRequest
309+
): Promise<ReGeneratePlotlyGraphQueryResponse>
294310
}

optuna_dashboard/ts/axiosClient.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
ParamImportancesResponse,
1111
PlotResponse,
1212
PlotType,
13+
ReGeneratePlotlyGraphQueryRequest,
14+
ReGeneratePlotlyGraphQueryResponse,
1315
RenameStudyResponse,
1416
StudyDetailResponse,
1517
StudySummariesResponse,
@@ -321,4 +323,13 @@ export class AxiosClient extends APIClient {
321323
request
322324
)
323325
.then<GeneratePlotlyGraphQueryResponse>((res) => res.data)
326+
callReGeneratePlotlyGraphQuery = (
327+
request: ReGeneratePlotlyGraphQueryRequest
328+
): Promise<ReGeneratePlotlyGraphQueryResponse> =>
329+
this.axiosInstance
330+
.post<ReGeneratePlotlyGraphQueryResponse>(
331+
"/api/llm/re_generate_plotly_graph_query",
332+
request
333+
)
334+
.then<ReGeneratePlotlyGraphQueryResponse>((res) => res.data)
324335
}

0 commit comments

Comments
 (0)