Francois Lancelot commited on
Commit
43e9024
·
1 Parent(s): bf56316

Add initial "static" space for component demo

Browse files
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
  title: Gradio Agent Inspector
3
- emoji: 👁
4
  colorFrom: pink
5
  colorTo: indigo
6
  sdk: gradio
@@ -9,6 +9,384 @@ app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  short_description: Agent Inspector to help debugging your agent
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Gradio Agent Inspector
3
+ emoji: 🕵️
4
  colorFrom: pink
5
  colorTo: indigo
6
  sdk: gradio
 
9
  pinned: false
10
  license: apache-2.0
11
  short_description: Agent Inspector to help debugging your agent
12
+ tags:
13
+ - gradio-custom-component
14
+ - custom-component-track
15
+ - adk
16
+ - agent
17
  ---
18
 
19
+ # `gradio_agent_inspector`
20
+ <a href="https://pypi.org/project/gradio_agent_inspector/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_agent_inspector"></a>
21
+
22
+ Agent Inspector for ADK
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install gradio_agent_inspector
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```python
33
+ import json
34
+ from pathlib import Path
35
+ import gradio as gr
36
+ from gradio_agent_inspector import AgentInspector
37
+ import os
38
+
39
+
40
+ def simulate_conversation():
41
+ dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
42
+
43
+ initial_state = {
44
+ "state": {},
45
+ "events": [],
46
+ }
47
+
48
+ states = []
49
+ for i in range(2):
50
+ session_value_p = dir_path / "session-sample" / f"value-{i}.json"
51
+ with session_value_p.open("r", encoding="utf-8") as f:
52
+ session_value = json.load(f)
53
+
54
+ # attach event trace and graph info to the event
55
+ for e in session_value["events"]:
56
+ event_trace_p = dir_path / "event-trace" / f"trace-{e['id']}.json"
57
+ if event_trace_p.exists():
58
+ with event_trace_p.open("r", encoding="utf-8") as trace_f:
59
+ event_trace = json.load(trace_f)
60
+ if "gcp.vertex.agent.llm_request" in event_trace:
61
+ event_trace["gcp.vertex.agent.llm_request"] = json.loads(
62
+ event_trace["gcp.vertex.agent.llm_request"]
63
+ )
64
+ if "gcp.vertex.agent.llm_response" in event_trace:
65
+ event_trace["gcp.vertex.agent.llm_response"] = json.loads(
66
+ event_trace["gcp.vertex.agent.llm_response"]
67
+ )
68
+ e["trace"] = event_trace
69
+ event_graph_p = dir_path / "event-trace" / f"graph-{e['id']}.json"
70
+ if event_graph_p.exists():
71
+ with event_graph_p.open("r", encoding="utf-8") as graph_f:
72
+ event_graph = json.load(graph_f)
73
+ e["graph"] = event_graph
74
+ states.append(session_value)
75
+
76
+ return initial_state, states
77
+
78
+
79
+ def update_conversation_state(state_index, states):
80
+ if (state_index + 1) >= len(states):
81
+ return states[state_index], state_index
82
+ else:
83
+ new_index = state_index + 1
84
+ return states[new_index], new_index
85
+
86
+
87
+ initial_state, conversation_states = simulate_conversation()
88
+
89
+ with gr.Blocks() as demo:
90
+ gr.Markdown("# Agent Inspector")
91
+
92
+ state_counter = gr.State(-1)
93
+
94
+ agent_inspector = AgentInspector(json.dumps(initial_state))
95
+
96
+ with gr.Row():
97
+ next_btn = gr.Button(
98
+ f"▶️ Next ({0} / {len(conversation_states)})", variant="primary"
99
+ )
100
+ reset_btn = gr.Button("🔄 Reset", variant="secondary")
101
+
102
+ def next_state(current_counter):
103
+ new_state, new_counter = update_conversation_state(
104
+ current_counter, conversation_states
105
+ )
106
+
107
+ json_state = json.dumps(new_state)
108
+ next_button_label = f"▶️ Next ({new_counter+1} / {len(conversation_states)})"
109
+
110
+ return json_state, new_counter, next_button_label
111
+
112
+ def reset_conversation():
113
+ json_state = json.dumps(initial_state)
114
+ next_button_label = f"▶️ Next ({0} / {len(conversation_states)})"
115
+
116
+ return json_state, -1, next_button_label
117
+
118
+ next_btn.click(
119
+ next_state,
120
+ inputs=[state_counter],
121
+ outputs=[agent_inspector, state_counter, next_btn],
122
+ )
123
+
124
+ reset_btn.click(
125
+ reset_conversation, outputs=[agent_inspector, state_counter, next_btn]
126
+ )
127
+
128
+ # examples = gr.Examples(
129
+ # examples=[
130
+ # s for s in conversation_states
131
+ # ],
132
+ # inputs=[initial_state],
133
+ # )
134
+
135
+ if __name__ == "__main__":
136
+ demo.launch()
137
+
138
+ ```
139
+
140
+ ## `AgentInspector`
141
+
142
+ ### Initialization
143
+
144
+ <table>
145
+ <thead>
146
+ <tr>
147
+ <th align="left">name</th>
148
+ <th align="left" style="width: 25%;">type</th>
149
+ <th align="left">default</th>
150
+ <th align="left">description</th>
151
+ </tr>
152
+ </thead>
153
+ <tbody>
154
+ <tr>
155
+ <td align="left"><code>value</code></td>
156
+ <td align="left" style="width: 25%;">
157
+
158
+ ```python
159
+ str | Callable | None
160
+ ```
161
+
162
+ </td>
163
+ <td align="left"><code>None</code></td>
164
+ <td align="left">default text to provide in textbox. If a function is provided, the function will be called each time the app loads to set the initial value of this component.</td>
165
+ </tr>
166
+
167
+ <tr>
168
+ <td align="left"><code>placeholder</code></td>
169
+ <td align="left" style="width: 25%;">
170
+
171
+ ```python
172
+ str | None
173
+ ```
174
+
175
+ </td>
176
+ <td align="left"><code>None</code></td>
177
+ <td align="left">placeholder hint to provide behind textbox.</td>
178
+ </tr>
179
+
180
+ <tr>
181
+ <td align="left"><code>label</code></td>
182
+ <td align="left" style="width: 25%;">
183
+
184
+ ```python
185
+ str | I18nData | None
186
+ ```
187
+
188
+ </td>
189
+ <td align="left"><code>None</code></td>
190
+ <td align="left">the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.</td>
191
+ </tr>
192
+
193
+ <tr>
194
+ <td align="left"><code>every</code></td>
195
+ <td align="left" style="width: 25%;">
196
+
197
+ ```python
198
+ Timer | float | None
199
+ ```
200
+
201
+ </td>
202
+ <td align="left"><code>None</code></td>
203
+ <td align="left">Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.</td>
204
+ </tr>
205
+
206
+ <tr>
207
+ <td align="left"><code>inputs</code></td>
208
+ <td align="left" style="width: 25%;">
209
+
210
+ ```python
211
+ Component | Sequence[Component] | set[Component] | None
212
+ ```
213
+
214
+ </td>
215
+ <td align="left"><code>None</code></td>
216
+ <td align="left">Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.</td>
217
+ </tr>
218
+
219
+ <tr>
220
+ <td align="left"><code>show_label</code></td>
221
+ <td align="left" style="width: 25%;">
222
+
223
+ ```python
224
+ bool | None
225
+ ```
226
+
227
+ </td>
228
+ <td align="left"><code>None</code></td>
229
+ <td align="left">if True, will display label.</td>
230
+ </tr>
231
+
232
+ <tr>
233
+ <td align="left"><code>scale</code></td>
234
+ <td align="left" style="width: 25%;">
235
+
236
+ ```python
237
+ int | None
238
+ ```
239
+
240
+ </td>
241
+ <td align="left"><code>None</code></td>
242
+ <td align="left">relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.</td>
243
+ </tr>
244
+
245
+ <tr>
246
+ <td align="left"><code>min_width</code></td>
247
+ <td align="left" style="width: 25%;">
248
+
249
+ ```python
250
+ int
251
+ ```
252
+
253
+ </td>
254
+ <td align="left"><code>160</code></td>
255
+ <td align="left">minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.</td>
256
+ </tr>
257
+
258
+ <tr>
259
+ <td align="left"><code>interactive</code></td>
260
+ <td align="left" style="width: 25%;">
261
+
262
+ ```python
263
+ bool | None
264
+ ```
265
+
266
+ </td>
267
+ <td align="left"><code>None</code></td>
268
+ <td align="left">if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.</td>
269
+ </tr>
270
+
271
+ <tr>
272
+ <td align="left"><code>visible</code></td>
273
+ <td align="left" style="width: 25%;">
274
+
275
+ ```python
276
+ bool
277
+ ```
278
+
279
+ </td>
280
+ <td align="left"><code>True</code></td>
281
+ <td align="left">If False, component will be hidden.</td>
282
+ </tr>
283
+
284
+ <tr>
285
+ <td align="left"><code>rtl</code></td>
286
+ <td align="left" style="width: 25%;">
287
+
288
+ ```python
289
+ bool
290
+ ```
291
+
292
+ </td>
293
+ <td align="left"><code>False</code></td>
294
+ <td align="left">If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.</td>
295
+ </tr>
296
+
297
+ <tr>
298
+ <td align="left"><code>elem_id</code></td>
299
+ <td align="left" style="width: 25%;">
300
+
301
+ ```python
302
+ str | None
303
+ ```
304
+
305
+ </td>
306
+ <td align="left"><code>None</code></td>
307
+ <td align="left">An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
308
+ </tr>
309
+
310
+ <tr>
311
+ <td align="left"><code>elem_classes</code></td>
312
+ <td align="left" style="width: 25%;">
313
+
314
+ ```python
315
+ list[str] | str | None
316
+ ```
317
+
318
+ </td>
319
+ <td align="left"><code>None</code></td>
320
+ <td align="left">An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.</td>
321
+ </tr>
322
+
323
+ <tr>
324
+ <td align="left"><code>render</code></td>
325
+ <td align="left" style="width: 25%;">
326
+
327
+ ```python
328
+ bool
329
+ ```
330
+
331
+ </td>
332
+ <td align="left"><code>True</code></td>
333
+ <td align="left">If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.</td>
334
+ </tr>
335
+
336
+ <tr>
337
+ <td align="left"><code>key</code></td>
338
+ <td align="left" style="width: 25%;">
339
+
340
+ ```python
341
+ int | str | tuple[int | str, ...] | None
342
+ ```
343
+
344
+ </td>
345
+ <td align="left"><code>None</code></td>
346
+ <td align="left">in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.</td>
347
+ </tr>
348
+
349
+ <tr>
350
+ <td align="left"><code>preserved_by_key</code></td>
351
+ <td align="left" style="width: 25%;">
352
+
353
+ ```python
354
+ list[str] | str | None
355
+ ```
356
+
357
+ </td>
358
+ <td align="left"><code>"value"</code></td>
359
+ <td align="left">A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.</td>
360
+ </tr>
361
+ </tbody></table>
362
+
363
+
364
+ ### Events
365
+
366
+ | name | description |
367
+ |:-----|:------------|
368
+ | `change` | Triggered when the value of the AgentInspector changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
369
+ | `input` | This listener is triggered when the user changes the value of the AgentInspector. |
370
+ | `submit` | This listener is triggered when the user presses the Enter key while the AgentInspector is focused. |
371
+
372
+
373
+
374
+ ### User function
375
+
376
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
377
+
378
+ - When used as an Input, the component only impacts the input signature of the user function.
379
+ - When used as an output, the component only impacts the return signature of the user function.
380
+
381
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
382
+
383
+ - **As output:** Is passed, passes text value as a {str} into the function.
384
+ - **As input:** Should return, expects a {str} returned from function and sets textarea value to it.
385
+
386
+ ```python
387
+ def predict(
388
+ value: str | None
389
+ ) -> str | None:
390
+ return value
391
+ ```
392
+
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+ import gradio as gr
4
+ from gradio_agent_inspector import AgentInspector
5
+ import os
6
+
7
+
8
+ def simulate_conversation():
9
+ dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
10
+
11
+ initial_state = {
12
+ "state": {},
13
+ "events": [],
14
+ }
15
+
16
+ states = []
17
+ for i in range(2):
18
+ session_value_p = dir_path / "session-sample" / f"value-{i}.json"
19
+ with session_value_p.open("r", encoding="utf-8") as f:
20
+ session_value = json.load(f)
21
+
22
+ # attach event trace and graph info to the event
23
+ for e in session_value["events"]:
24
+ event_trace_p = dir_path / "event-trace" / f"trace-{e['id']}.json"
25
+ if event_trace_p.exists():
26
+ with event_trace_p.open("r", encoding="utf-8") as trace_f:
27
+ event_trace = json.load(trace_f)
28
+ if "gcp.vertex.agent.llm_request" in event_trace:
29
+ event_trace["gcp.vertex.agent.llm_request"] = json.loads(
30
+ event_trace["gcp.vertex.agent.llm_request"]
31
+ )
32
+ if "gcp.vertex.agent.llm_response" in event_trace:
33
+ event_trace["gcp.vertex.agent.llm_response"] = json.loads(
34
+ event_trace["gcp.vertex.agent.llm_response"]
35
+ )
36
+ e["trace"] = event_trace
37
+ event_graph_p = dir_path / "event-trace" / f"graph-{e['id']}.json"
38
+ if event_graph_p.exists():
39
+ with event_graph_p.open("r", encoding="utf-8") as graph_f:
40
+ event_graph = json.load(graph_f)
41
+ e["graph"] = event_graph
42
+ states.append(session_value)
43
+
44
+ return initial_state, states
45
+
46
+
47
+ def update_conversation_state(state_index, states):
48
+ if (state_index + 1) >= len(states):
49
+ return states[state_index], state_index
50
+ else:
51
+ new_index = state_index + 1
52
+ return states[new_index], new_index
53
+
54
+
55
+ initial_state, conversation_states = simulate_conversation()
56
+
57
+ with gr.Blocks() as demo:
58
+ gr.Markdown("# Agent Inspector")
59
+
60
+ state_counter = gr.State(-1)
61
+
62
+ agent_inspector = AgentInspector(json.dumps(initial_state))
63
+
64
+ with gr.Row():
65
+ next_btn = gr.Button(
66
+ f"▶️ Next ({0} / {len(conversation_states)})", variant="primary"
67
+ )
68
+ reset_btn = gr.Button("🔄 Reset", variant="secondary")
69
+
70
+ def next_state(current_counter):
71
+ new_state, new_counter = update_conversation_state(
72
+ current_counter, conversation_states
73
+ )
74
+
75
+ json_state = json.dumps(new_state)
76
+ next_button_label = f"▶️ Next ({new_counter+1} / {len(conversation_states)})"
77
+
78
+ return json_state, new_counter, next_button_label
79
+
80
+ def reset_conversation():
81
+ json_state = json.dumps(initial_state)
82
+ next_button_label = f"▶️ Next ({0} / {len(conversation_states)})"
83
+
84
+ return json_state, -1, next_button_label
85
+
86
+ next_btn.click(
87
+ next_state,
88
+ inputs=[state_counter],
89
+ outputs=[agent_inspector, state_counter, next_btn],
90
+ )
91
+
92
+ reset_btn.click(
93
+ reset_conversation, outputs=[agent_inspector, state_counter, next_btn]
94
+ )
95
+
96
+ # examples = gr.Examples(
97
+ # examples=[
98
+ # s for s in conversation_states
99
+ # ],
100
+ # inputs=[initial_state],
101
+ # )
102
+
103
+ if __name__ == "__main__":
104
+ demo.launch()
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
event-trace/graph-4nNtOvIp.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "dotSrc": "digraph {\n\tgraph [bgcolor=\"#333537\" rankdir=LR]\n\tweather_agent_v1 [label=\"🤖 weather_agent_v1\" color=\"#0F5223\" fillcolor=\"#0F5223\" fontcolor=\"#cccccc\" shape=ellipse style=\"filled,rounded\"]\n\tget_weather [label=\"🔧 get_weather\" color=\"#cccccc\" fontcolor=\"#cccccc\" shape=box style=rounded]\n\tweather_agent_v1 -> get_weather [arrowhead=none color=\"#cccccc\"]\n}\n"
3
+ }
event-trace/graph-HPTxQshW.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "dotSrc": "digraph {\n\tgraph [bgcolor=\"#333537\" rankdir=LR]\n\tweather_agent_v1 [label=\"🤖 weather_agent_v1\" color=\"#cccccc\" fontcolor=\"#cccccc\" shape=ellipse style=rounded]\n\tget_weather [label=\"🔧 get_weather\" color=\"#cccccc\" fontcolor=\"#cccccc\" shape=box style=rounded]\n\tweather_agent_v1 -> get_weather [arrowhead=none color=\"#cccccc\"]\n}\n"
3
+ }
event-trace/graph-J0thtik1.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "dotSrc": "digraph {\n\tgraph [bgcolor=\"#333537\" rankdir=LR]\n\tweather_agent_v1 [label=\"🤖 weather_agent_v1\" color=\"#cccccc\" fontcolor=\"#cccccc\" shape=ellipse style=rounded]\n\tget_weather [label=\"🔧 get_weather\" color=\"#cccccc\" fontcolor=\"#cccccc\" shape=box style=rounded]\n\tweather_agent_v1 -> get_weather [arrowhead=none color=\"#cccccc\"]\n}\n"
3
+ }
event-trace/graph-mizs2Mpl.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "dotSrc": "digraph {\n\tgraph [bgcolor=\"#333537\" rankdir=LR]\n\tweather_agent_v1 [label=\"🤖 weather_agent_v1\" color=\"#0F5223\" fillcolor=\"#0F5223\" fontcolor=\"#cccccc\" shape=ellipse style=\"filled,rounded\"]\n\tget_weather [label=\"🔧 get_weather\" color=\"#0F5223\" fillcolor=\"#0F5223\" fontcolor=\"#cccccc\" shape=box style=\"filled,rounded\"]\n\tweather_agent_v1 -> get_weather [color=\"#69CB87\" dir=back]\n}\n"
3
+ }
event-trace/graph-pjqr5AM1.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "dotSrc": "digraph {\n\tgraph [bgcolor=\"#333537\" rankdir=LR]\n\tweather_agent_v1 [label=\"🤖 weather_agent_v1\" color=\"#0F5223\" fillcolor=\"#0F5223\" fontcolor=\"#cccccc\" shape=ellipse style=\"filled,rounded\"]\n\tget_weather [label=\"🔧 get_weather\" color=\"#0F5223\" fillcolor=\"#0F5223\" fontcolor=\"#cccccc\" shape=box style=\"filled,rounded\"]\n\tweather_agent_v1 -> get_weather [color=\"#69CB87\"]\n}\n"
3
+ }
event-trace/trace-4nNtOvIp.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gen_ai.system": "gcp.vertex.agent",
3
+ "gen_ai.request.model": "gemini-2.0-flash",
4
+ "gcp.vertex.agent.invocation_id": "e-46b87f9c-6deb-4d6e-8031-0d8194aaae00",
5
+ "gcp.vertex.agent.session_id": "d6de8f93-5b2c-487f-9f54-bd6abfd06bbe",
6
+ "gcp.vertex.agent.event_id": "4nNtOvIp",
7
+ "gcp.vertex.agent.llm_request": "{\"model\": \"gemini-2.0-flash\", \"config\": {\"system_instruction\": \"You are a helpful weather assistant. Your primary goal is to provide current weather reports. When the user asks for the weather in a specific city, you MUST use the 'get_weather' tool to find the information. Analyze the tool's response: if the status is 'error', inform the user politely about the error message. If the status is 'success', present the weather 'report' clearly and concisely to the user. Only use the tool when a city is mentioned for a weather request.\\n\\nYou are an agent. Your internal name is \\\"weather_agent_v1\\\".\\n\\n The description about you is \\\"Provides weather information for specific cities.\\\"\", \"tools\": [{\"function_declarations\": [{\"description\": \"Retrieves the current weather report for a specified city.\\n\\n Args:\\n city (str): The name of the city (e.g., \\\"New York\\\", \\\"London\\\", \\\"Tokyo\\\").\\n\\n Returns:\\n dict: A dictionary containing the weather information.\\n Includes a 'status' key ('success' or 'error').\\n If 'success', includes a 'report' key with weather details.\\n If 'error', includes an 'error_message' key.\\n \", \"name\": \"get_weather\", \"parameters\": {\"properties\": {\"city\": {\"type\": \"STRING\"}}, \"required\": [\"city\"], \"type\": \"OBJECT\"}}]}]}, \"contents\": [{\"parts\": [{\"text\": \"hi\"}], \"role\": \"user\"}]}",
8
+ "gcp.vertex.agent.llm_response": "{\"content\":{\"parts\":[{\"text\":\"Hi there! How can I help you today?\\n\"}],\"role\":\"model\"},\"usage_metadata\":{\"candidates_token_count\":11,\"candidates_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":11}],\"prompt_token_count\":246,\"prompt_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":246}],\"total_token_count\":257}}",
9
+ "trace_id": 2.590244190807234e+37,
10
+ "span_id": 4003274593459785700
11
+ }
event-trace/trace-HPTxQshW.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gen_ai.system": "gcp.vertex.agent",
3
+ "gen_ai.request.model": "gemini-2.0-flash",
4
+ "gcp.vertex.agent.invocation_id": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
5
+ "gcp.vertex.agent.session_id": "d6de8f93-5b2c-487f-9f54-bd6abfd06bbe",
6
+ "gcp.vertex.agent.event_id": "HPTxQshW",
7
+ "gcp.vertex.agent.llm_request": "{\"model\": \"gemini-2.0-flash\", \"config\": {\"system_instruction\": \"You are a helpful weather assistant. Your primary goal is to provide current weather reports. When the user asks for the weather in a specific city, you MUST use the 'get_weather' tool to find the information. Analyze the tool's response: if the status is 'error', inform the user politely about the error message. If the status is 'success', present the weather 'report' clearly and concisely to the user. Only use the tool when a city is mentioned for a weather request.\\n\\nYou are an agent. Your internal name is \\\"weather_agent_v1\\\".\\n\\n The description about you is \\\"Provides weather information for specific cities.\\\"\", \"tools\": [{\"function_declarations\": [{\"description\": \"Retrieves the current weather report for a specified city.\\n\\n Args:\\n city (str): The name of the city (e.g., \\\"New York\\\", \\\"London\\\", \\\"Tokyo\\\").\\n\\n Returns:\\n dict: A dictionary containing the weather information.\\n Includes a 'status' key ('success' or 'error').\\n If 'success', includes a 'report' key with weather details.\\n If 'error', includes an 'error_message' key.\\n \", \"name\": \"get_weather\", \"parameters\": {\"properties\": {\"city\": {\"type\": \"STRING\"}}, \"required\": [\"city\"], \"type\": \"OBJECT\"}}]}]}, \"contents\": [{\"parts\": [{\"text\": \"hi\"}], \"role\": \"user\"}, {\"parts\": [{\"text\": \"Hi there! How can I help you today?\\n\"}], \"role\": \"model\"}, {\"parts\": [{\"text\": \"what is the weather in new york\"}], \"role\": \"user\"}, {\"parts\": [{\"function_call\": {\"args\": {\"city\": \"New York\"}, \"name\": \"get_weather\"}}], \"role\": \"model\"}, {\"parts\": [{\"function_response\": {\"name\": \"get_weather\", \"response\": {\"status\": \"success\", \"report\": \"The weather in New york is sunny with a temperature of 25°C.\"}}}], \"role\": \"user\"}]}",
8
+ "gcp.vertex.agent.llm_response": "{\"content\":{\"parts\":[{\"text\":\"The weather in New York is sunny with a temperature of 25°C.\\n\"}],\"role\":\"model\"},\"usage_metadata\":{\"candidates_token_count\":18,\"candidates_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":18}],\"prompt_token_count\":293,\"prompt_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":293}],\"total_token_count\":311}}",
9
+ "trace_id": 1.585818373009333e+38,
10
+ "span_id": 11193696278902387000
11
+ }
event-trace/trace-mizs2Mpl.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gen_ai.system": "gcp.vertex.agent",
3
+ "gen_ai.operation.name": "execute_tool",
4
+ "gen_ai.tool.name": "get_weather",
5
+ "gen_ai.tool.description": "Retrieves the current weather report for a specified city.\n\nArgs:\n city (str): The name of the city (e.g., \"New York\", \"London\", \"Tokyo\").\n\nReturns:\n dict: A dictionary containing the weather information.\n Includes a 'status' key ('success' or 'error').\n If 'success', includes a 'report' key with weather details.\n If 'error', includes an 'error_message' key.",
6
+ "gen_ai.tool.call.id": "adk-b488c3ed-5875-4999-89ed-383b0c94826d",
7
+ "gcp.vertex.agent.tool_call_args": "{\"city\": \"New York\"}",
8
+ "gcp.vertex.agent.event_id": "mizs2Mpl",
9
+ "gcp.vertex.agent.tool_response": "{\"status\": \"success\", \"report\": \"The weather in New york is sunny with a temperature of 25°C.\"}",
10
+ "gcp.vertex.agent.llm_request": "{}",
11
+ "gcp.vertex.agent.llm_response": "{}",
12
+ "trace_id": 1.585818373009333e+38,
13
+ "span_id": 17506961790282844000
14
+ }
event-trace/trace-pjqr5AM1.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gen_ai.system": "gcp.vertex.agent",
3
+ "gen_ai.request.model": "gemini-2.0-flash",
4
+ "gcp.vertex.agent.invocation_id": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
5
+ "gcp.vertex.agent.session_id": "d6de8f93-5b2c-487f-9f54-bd6abfd06bbe",
6
+ "gcp.vertex.agent.event_id": "pjqr5AM1",
7
+ "gcp.vertex.agent.llm_request": "{\"model\": \"gemini-2.0-flash\", \"config\": {\"system_instruction\": \"You are a helpful weather assistant. Your primary goal is to provide current weather reports. When the user asks for the weather in a specific city, you MUST use the 'get_weather' tool to find the information. Analyze the tool's response: if the status is 'error', inform the user politely about the error message. If the status is 'success', present the weather 'report' clearly and concisely to the user. Only use the tool when a city is mentioned for a weather request.\\n\\nYou are an agent. Your internal name is \\\"weather_agent_v1\\\".\\n\\n The description about you is \\\"Provides weather information for specific cities.\\\"\", \"tools\": [{\"function_declarations\": [{\"description\": \"Retrieves the current weather report for a specified city.\\n\\n Args:\\n city (str): The name of the city (e.g., \\\"New York\\\", \\\"London\\\", \\\"Tokyo\\\").\\n\\n Returns:\\n dict: A dictionary containing the weather information.\\n Includes a 'status' key ('success' or 'error').\\n If 'success', includes a 'report' key with weather details.\\n If 'error', includes an 'error_message' key.\\n \", \"name\": \"get_weather\", \"parameters\": {\"properties\": {\"city\": {\"type\": \"STRING\"}}, \"required\": [\"city\"], \"type\": \"OBJECT\"}}]}]}, \"contents\": [{\"parts\": [{\"text\": \"hi\"}], \"role\": \"user\"}, {\"parts\": [{\"text\": \"Hi there! How can I help you today?\\n\"}], \"role\": \"model\"}, {\"parts\": [{\"text\": \"what is the weather in new york\"}], \"role\": \"user\"}]}",
8
+ "gcp.vertex.agent.llm_response": "{\"content\":{\"parts\":[{\"function_call\":{\"args\":{\"city\":\"New York\"},\"name\":\"get_weather\"}}],\"role\":\"model\"},\"usage_metadata\":{\"candidates_token_count\":6,\"candidates_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":6}],\"prompt_token_count\":264,\"prompt_tokens_details\":[{\"modality\":\"TEXT\",\"token_count\":264}],\"total_token_count\":270}}",
9
+ "trace_id": 1.585818373009333e+38,
10
+ "span_id": 15023233232456225000
11
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_agent_inspector
session-sample/value-0.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "d6de8f93-5b2c-487f-9f54-bd6abfd06bbe",
3
+ "appName": "weather_agent",
4
+ "userId": "user",
5
+ "state": {
6
+ "last_response": "Hi there! How can I help you today?\n"
7
+ },
8
+ "events": [
9
+ {
10
+ "content": {
11
+ "parts": [
12
+ {
13
+ "text": "hi"
14
+ }
15
+ ],
16
+ "role": "user"
17
+ },
18
+ "invocationId": "e-46b87f9c-6deb-4d6e-8031-0d8194aaae00",
19
+ "author": "user",
20
+ "actions": {
21
+ "stateDelta": {},
22
+ "artifactDelta": {},
23
+ "requestedAuthConfigs": {}
24
+ },
25
+ "id": "UuzEKV1U",
26
+ "timestamp": 1749409305.556533
27
+ },
28
+ {
29
+ "content": {
30
+ "parts": [
31
+ {
32
+ "text": "Hi there! How can I help you today?\n"
33
+ }
34
+ ],
35
+ "role": "model"
36
+ },
37
+ "usageMetadata": {
38
+ "candidatesTokenCount": 11,
39
+ "candidatesTokensDetails": [
40
+ {
41
+ "modality": "TEXT",
42
+ "tokenCount": 11
43
+ }
44
+ ],
45
+ "promptTokenCount": 246,
46
+ "promptTokensDetails": [
47
+ {
48
+ "modality": "TEXT",
49
+ "tokenCount": 246
50
+ }
51
+ ],
52
+ "totalTokenCount": 257
53
+ },
54
+ "invocationId": "e-46b87f9c-6deb-4d6e-8031-0d8194aaae00",
55
+ "author": "weather_agent_v1",
56
+ "actions": {
57
+ "stateDelta": {
58
+ "last_response": "Hi there! How can I help you today?\n"
59
+ },
60
+ "artifactDelta": {},
61
+ "requestedAuthConfigs": {}
62
+ },
63
+ "id": "4nNtOvIp",
64
+ "timestamp": 1749409305.556533
65
+ }
66
+ ],
67
+ "lastUpdateTime": 1749409305.556533
68
+ }
session-sample/value-1.json ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "d6de8f93-5b2c-487f-9f54-bd6abfd06bbe",
3
+ "appName": "weather_agent",
4
+ "userId": "user",
5
+ "state": {
6
+ "last_response": "The weather in New York is sunny with a temperature of 25°C.\n",
7
+ "last_city_checked_stateful": "New York"
8
+ },
9
+ "events": [
10
+ {
11
+ "content": {
12
+ "parts": [
13
+ {
14
+ "text": "hi"
15
+ }
16
+ ],
17
+ "role": "user"
18
+ },
19
+ "invocationId": "e-46b87f9c-6deb-4d6e-8031-0d8194aaae00",
20
+ "author": "user",
21
+ "actions": {
22
+ "stateDelta": {},
23
+ "artifactDelta": {},
24
+ "requestedAuthConfigs": {}
25
+ },
26
+ "id": "UuzEKV1U",
27
+ "timestamp": 1749409305.556533
28
+ },
29
+ {
30
+ "content": {
31
+ "parts": [
32
+ {
33
+ "text": "Hi there! How can I help you today?\n"
34
+ }
35
+ ],
36
+ "role": "model"
37
+ },
38
+ "usageMetadata": {
39
+ "candidatesTokenCount": 11,
40
+ "candidatesTokensDetails": [
41
+ {
42
+ "modality": "TEXT",
43
+ "tokenCount": 11
44
+ }
45
+ ],
46
+ "promptTokenCount": 246,
47
+ "promptTokensDetails": [
48
+ {
49
+ "modality": "TEXT",
50
+ "tokenCount": 246
51
+ }
52
+ ],
53
+ "totalTokenCount": 257
54
+ },
55
+ "invocationId": "e-46b87f9c-6deb-4d6e-8031-0d8194aaae00",
56
+ "author": "weather_agent_v1",
57
+ "actions": {
58
+ "stateDelta": {
59
+ "last_response": "Hi there! How can I help you today?\n"
60
+ },
61
+ "artifactDelta": {},
62
+ "requestedAuthConfigs": {}
63
+ },
64
+ "id": "4nNtOvIp",
65
+ "timestamp": 1749409305.556533
66
+ },
67
+ {
68
+ "content": {
69
+ "parts": [
70
+ {
71
+ "text": "what is the weather in new york"
72
+ }
73
+ ],
74
+ "role": "user"
75
+ },
76
+ "invocationId": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
77
+ "author": "user",
78
+ "actions": {
79
+ "stateDelta": {},
80
+ "artifactDelta": {},
81
+ "requestedAuthConfigs": {}
82
+ },
83
+ "id": "J0thtik1",
84
+ "timestamp": 1749409604.09957
85
+ },
86
+ {
87
+ "content": {
88
+ "parts": [
89
+ {
90
+ "functionCall": {
91
+ "id": "adk-b488c3ed-5875-4999-89ed-383b0c94826d",
92
+ "args": {
93
+ "city": "New York"
94
+ },
95
+ "name": "get_weather"
96
+ }
97
+ }
98
+ ],
99
+ "role": "model"
100
+ },
101
+ "usageMetadata": {
102
+ "candidatesTokenCount": 6,
103
+ "candidatesTokensDetails": [
104
+ {
105
+ "modality": "TEXT",
106
+ "tokenCount": 6
107
+ }
108
+ ],
109
+ "promptTokenCount": 264,
110
+ "promptTokensDetails": [
111
+ {
112
+ "modality": "TEXT",
113
+ "tokenCount": 264
114
+ }
115
+ ],
116
+ "totalTokenCount": 270
117
+ },
118
+ "invocationId": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
119
+ "author": "weather_agent_v1",
120
+ "actions": {
121
+ "stateDelta": {},
122
+ "artifactDelta": {},
123
+ "requestedAuthConfigs": {}
124
+ },
125
+ "longRunningToolIds": [],
126
+ "id": "pjqr5AM1",
127
+ "timestamp": 1749409604.09957
128
+ },
129
+ {
130
+ "content": {
131
+ "parts": [
132
+ {
133
+ "functionResponse": {
134
+ "id": "adk-b488c3ed-5875-4999-89ed-383b0c94826d",
135
+ "name": "get_weather",
136
+ "response": {
137
+ "status": "success",
138
+ "report": "The weather in New york is sunny with a temperature of 25°C."
139
+ }
140
+ }
141
+ }
142
+ ],
143
+ "role": "user"
144
+ },
145
+ "invocationId": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
146
+ "author": "weather_agent_v1",
147
+ "actions": {
148
+ "stateDelta": {
149
+ "last_city_checked_stateful": "New York"
150
+ },
151
+ "artifactDelta": {},
152
+ "requestedAuthConfigs": {}
153
+ },
154
+ "id": "mizs2Mpl",
155
+ "timestamp": 1749409604.677217
156
+ },
157
+ {
158
+ "content": {
159
+ "parts": [
160
+ {
161
+ "text": "The weather in New York is sunny with a temperature of 25°C.\n"
162
+ }
163
+ ],
164
+ "role": "model"
165
+ },
166
+ "usageMetadata": {
167
+ "candidatesTokenCount": 18,
168
+ "candidatesTokensDetails": [
169
+ {
170
+ "modality": "TEXT",
171
+ "tokenCount": 18
172
+ }
173
+ ],
174
+ "promptTokenCount": 293,
175
+ "promptTokensDetails": [
176
+ {
177
+ "modality": "TEXT",
178
+ "tokenCount": 293
179
+ }
180
+ ],
181
+ "totalTokenCount": 311
182
+ },
183
+ "invocationId": "e-cee39ecc-0cef-4e03-9047-0b8d5e728964",
184
+ "author": "weather_agent_v1",
185
+ "actions": {
186
+ "stateDelta": {
187
+ "last_response": "The weather in New York is sunny with a temperature of 25°C.\n"
188
+ },
189
+ "artifactDelta": {},
190
+ "requestedAuthConfigs": {}
191
+ },
192
+ "id": "HPTxQshW",
193
+ "timestamp": 1749409604.677217
194
+ }
195
+ ],
196
+ "lastUpdateTime": 1749409604.677217
197
+ }
session-sample/value-init.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "7b31fe12-e9a7-44ab-8b8f-712168050621",
3
+ "appName": "agent",
4
+ "userId": "user",
5
+ "state": {},
6
+ "events": [],
7
+ "lastUpdateTime": 1748896263.8251631
8
+ }
space.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'AgentInspector': {'description': 'Creates a very simple textbox for user to enter string input or display string output.', 'members': {'__init__': {'value': {'type': 'str | Callable | None', 'default': 'None', 'description': 'default text to provide in textbox. If a function is provided, the function will be called each time the app loads to set the initial value of this component.'}, 'placeholder': {'type': 'str | None', 'default': 'None', 'description': 'placeholder hint to provide behind textbox.'}, 'label': {'type': 'str | I18nData | None', 'default': 'None', 'description': 'the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.'}, 'every': {'type': 'Timer | float | None', 'default': 'None', 'description': 'Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.'}, 'inputs': {'type': 'Component | Sequence[Component] | set[Component] | None', 'default': 'None', 'description': 'Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.'}, 'show_label': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will display label.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.'}, 'interactive': {'type': 'bool | None', 'default': 'None', 'description': 'if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, component will be hidden.'}, 'rtl': {'type': 'bool', 'default': 'False', 'description': 'If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.'}, 'key': {'type': 'int | str | tuple[int | str, ...] | None', 'default': 'None', 'description': "in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render."}, 'preserved_by_key': {'type': 'list[str] | str | None', 'default': '"value"', 'description': "A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor."}}, 'postprocess': {'value': {'type': 'str | None', 'description': 'Expects a {str} returned from function and sets textarea value to it.'}}, 'preprocess': {'return': {'type': 'str | None', 'description': 'Passes text value as a {str} into the function.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the AgentInspector changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the AgentInspector.'}, 'submit': {'type': None, 'default': None, 'description': 'This listener is triggered when the user presses the Enter key while the AgentInspector is focused.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'AgentInspector': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_agent_inspector`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <a href="https://pypi.org/project/gradio_agent_inspector/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_agent_inspector"></a>
25
+ </div>
26
+
27
+ Agent Inspector for ADK
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_agent_inspector
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ import json
42
+ from pathlib import Path
43
+ import gradio as gr
44
+ from gradio_agent_inspector import AgentInspector
45
+ import os
46
+
47
+
48
+ def simulate_conversation():
49
+ dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
50
+
51
+ initial_state = {
52
+ "state": {},
53
+ "events": [],
54
+ }
55
+
56
+ states = []
57
+ for i in range(2):
58
+ session_value_p = dir_path / "session-sample" / f"value-{i}.json"
59
+ with session_value_p.open("r", encoding="utf-8") as f:
60
+ session_value = json.load(f)
61
+
62
+ # attach event trace and graph info to the event
63
+ for e in session_value["events"]:
64
+ event_trace_p = dir_path / "event-trace" / f"trace-{e['id']}.json"
65
+ if event_trace_p.exists():
66
+ with event_trace_p.open("r", encoding="utf-8") as trace_f:
67
+ event_trace = json.load(trace_f)
68
+ if "gcp.vertex.agent.llm_request" in event_trace:
69
+ event_trace["gcp.vertex.agent.llm_request"] = json.loads(
70
+ event_trace["gcp.vertex.agent.llm_request"]
71
+ )
72
+ if "gcp.vertex.agent.llm_response" in event_trace:
73
+ event_trace["gcp.vertex.agent.llm_response"] = json.loads(
74
+ event_trace["gcp.vertex.agent.llm_response"]
75
+ )
76
+ e["trace"] = event_trace
77
+ event_graph_p = dir_path / "event-trace" / f"graph-{e['id']}.json"
78
+ if event_graph_p.exists():
79
+ with event_graph_p.open("r", encoding="utf-8") as graph_f:
80
+ event_graph = json.load(graph_f)
81
+ e["graph"] = event_graph
82
+ states.append(session_value)
83
+
84
+ return initial_state, states
85
+
86
+
87
+ def update_conversation_state(state_index, states):
88
+ if (state_index + 1) >= len(states):
89
+ return states[state_index], state_index
90
+ else:
91
+ new_index = state_index + 1
92
+ return states[new_index], new_index
93
+
94
+
95
+ initial_state, conversation_states = simulate_conversation()
96
+
97
+ with gr.Blocks() as demo:
98
+ gr.Markdown("# Agent Inspector")
99
+
100
+ state_counter = gr.State(-1)
101
+
102
+ agent_inspector = AgentInspector(json.dumps(initial_state))
103
+
104
+ with gr.Row():
105
+ next_btn = gr.Button(
106
+ f"▶️ Next ({0} / {len(conversation_states)})", variant="primary"
107
+ )
108
+ reset_btn = gr.Button("🔄 Reset", variant="secondary")
109
+
110
+ def next_state(current_counter):
111
+ new_state, new_counter = update_conversation_state(
112
+ current_counter, conversation_states
113
+ )
114
+
115
+ json_state = json.dumps(new_state)
116
+ next_button_label = f"▶️ Next ({new_counter+1} / {len(conversation_states)})"
117
+
118
+ return json_state, new_counter, next_button_label
119
+
120
+ def reset_conversation():
121
+ json_state = json.dumps(initial_state)
122
+ next_button_label = f"▶️ Next ({0} / {len(conversation_states)})"
123
+
124
+ return json_state, -1, next_button_label
125
+
126
+ next_btn.click(
127
+ next_state,
128
+ inputs=[state_counter],
129
+ outputs=[agent_inspector, state_counter, next_btn],
130
+ )
131
+
132
+ reset_btn.click(
133
+ reset_conversation, outputs=[agent_inspector, state_counter, next_btn]
134
+ )
135
+
136
+ # examples = gr.Examples(
137
+ # examples=[
138
+ # s for s in conversation_states
139
+ # ],
140
+ # inputs=[initial_state],
141
+ # )
142
+
143
+ if __name__ == "__main__":
144
+ demo.launch()
145
+
146
+ ```
147
+ """, elem_classes=["md-custom"], header_links=True)
148
+
149
+
150
+ gr.Markdown("""
151
+ ## `AgentInspector`
152
+
153
+ ### Initialization
154
+ """, elem_classes=["md-custom"], header_links=True)
155
+
156
+ gr.ParamViewer(value=_docs["AgentInspector"]["members"]["__init__"], linkify=[])
157
+
158
+
159
+ gr.Markdown("### Events")
160
+ gr.ParamViewer(value=_docs["AgentInspector"]["events"], linkify=['Event'])
161
+
162
+
163
+
164
+
165
+ gr.Markdown("""
166
+
167
+ ### User function
168
+
169
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
170
+
171
+ - When used as an Input, the component only impacts the input signature of the user function.
172
+ - When used as an output, the component only impacts the return signature of the user function.
173
+
174
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
175
+
176
+ - **As input:** Is passed, passes text value as a {str} into the function.
177
+ - **As output:** Should return, expects a {str} returned from function and sets textarea value to it.
178
+
179
+ ```python
180
+ def predict(
181
+ value: str | None
182
+ ) -> str | None:
183
+ return value
184
+ ```
185
+ """, elem_classes=["md-custom", "AgentInspector-user-fn"], header_links=True)
186
+
187
+
188
+
189
+
190
+ demo.load(None, js=r"""function() {
191
+ const refs = {};
192
+ const user_fn_refs = {
193
+ AgentInspector: [], };
194
+ requestAnimationFrame(() => {
195
+
196
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
197
+ if (refs.length > 0) {
198
+ const el = document.querySelector(`.${key}-user-fn`);
199
+ if (!el) return;
200
+ refs.forEach(ref => {
201
+ el.innerHTML = el.innerHTML.replace(
202
+ new RegExp("\\b"+ref+"\\b", "g"),
203
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
204
+ );
205
+ })
206
+ }
207
+ })
208
+
209
+ Object.entries(refs).forEach(([key, refs]) => {
210
+ if (refs.length > 0) {
211
+ const el = document.querySelector(`.${key}`);
212
+ if (!el) return;
213
+ refs.forEach(ref => {
214
+ el.innerHTML = el.innerHTML.replace(
215
+ new RegExp("\\b"+ref+"\\b", "g"),
216
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
217
+ );
218
+ })
219
+ }
220
+ })
221
+ })
222
+ }
223
+
224
+ """)
225
+
226
+ demo.launch()