tinyagent-py 0.0.9__py3-none-any.whl → 0.0.12__py3-none-any.whl
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.
- tinyagent/code_agent/modal_sandbox.py +3 -1
- tinyagent/code_agent/providers/modal_provider.py +45 -13
- tinyagent/code_agent/safety.py +546 -0
- tinyagent/code_agent/tiny_code_agent.py +91 -0
- tinyagent/code_agent/utils.py +59 -10
- tinyagent/hooks/gradio_callback.py +97 -33
- tinyagent/prompts/code_agent.yaml +329 -0
- tinyagent/tiny_agent.py +4 -7
- {tinyagent_py-0.0.9.dist-info → tinyagent_py-0.0.12.dist-info}/METADATA +1 -1
- {tinyagent_py-0.0.9.dist-info → tinyagent_py-0.0.12.dist-info}/RECORD +13 -11
- {tinyagent_py-0.0.9.dist-info → tinyagent_py-0.0.12.dist-info}/WHEEL +0 -0
- {tinyagent_py-0.0.9.dist-info → tinyagent_py-0.0.12.dist-info}/licenses/LICENSE +0 -0
- {tinyagent_py-0.0.9.dist-info → tinyagent_py-0.0.12.dist-info}/top_level.txt +0 -0
@@ -62,6 +62,7 @@ class TinyCodeAgent:
|
|
62
62
|
self.user_variables = user_variables or {}
|
63
63
|
self.pip_packages = pip_packages or []
|
64
64
|
self.local_execution = local_execution
|
65
|
+
self.provider = provider # Store provider type for reuse
|
65
66
|
|
66
67
|
# Create the code execution provider
|
67
68
|
self.code_provider = self._create_provider(provider, self.provider_config)
|
@@ -96,8 +97,13 @@ class TinyCodeAgent:
|
|
96
97
|
config_pip_packages = config.get("pip_packages", [])
|
97
98
|
final_pip_packages = list(set(self.pip_packages + config_pip_packages))
|
98
99
|
|
100
|
+
# Merge authorized_imports from both sources (direct parameter and provider_config)
|
101
|
+
config_authorized_imports = config.get("authorized_imports", [])
|
102
|
+
final_authorized_imports = list(set(self.authorized_imports + config_authorized_imports))
|
103
|
+
|
99
104
|
final_config = config.copy()
|
100
105
|
final_config["pip_packages"] = final_pip_packages
|
106
|
+
final_config["authorized_imports"] = final_authorized_imports
|
101
107
|
|
102
108
|
return ModalProvider(
|
103
109
|
log_manager=self.log_manager,
|
@@ -258,6 +264,14 @@ class TinyCodeAgent:
|
|
258
264
|
result = await self.code_provider.execute_python(code_lines, timeout)
|
259
265
|
return str(result)
|
260
266
|
except Exception as e:
|
267
|
+
print("!"*100)
|
268
|
+
COLOR = {
|
269
|
+
"RED": "\033[91m",
|
270
|
+
"ENDC": "\033[0m",
|
271
|
+
}
|
272
|
+
print(f"{COLOR['RED']}{str(e)}{COLOR['ENDC']}")
|
273
|
+
print(f"{COLOR['RED']}{traceback.format_exc()}{COLOR['ENDC']}")
|
274
|
+
print("!"*100)
|
261
275
|
return f"Error executing code: {str(e)}"
|
262
276
|
|
263
277
|
self.agent.add_tool(run_python)
|
@@ -441,6 +455,69 @@ class TinyCodeAgent:
|
|
441
455
|
"""
|
442
456
|
return self.pip_packages.copy()
|
443
457
|
|
458
|
+
def add_authorized_imports(self, imports: List[str]):
|
459
|
+
"""
|
460
|
+
Add additional authorized imports to the execution environment.
|
461
|
+
|
462
|
+
Args:
|
463
|
+
imports: List of import names to authorize
|
464
|
+
"""
|
465
|
+
self.authorized_imports.extend(imports)
|
466
|
+
self.authorized_imports = list(set(self.authorized_imports)) # Remove duplicates
|
467
|
+
|
468
|
+
# Update the provider with the new authorized imports
|
469
|
+
# This requires recreating the provider
|
470
|
+
print("⚠️ Warning: Adding authorized imports after initialization requires recreating the Modal environment.")
|
471
|
+
print(" For better performance, set authorized_imports during TinyCodeAgent initialization.")
|
472
|
+
|
473
|
+
# Recreate the provider with new authorized imports
|
474
|
+
self.code_provider = self._create_provider(self.provider, self.provider_config)
|
475
|
+
|
476
|
+
# Re-set user variables if they exist
|
477
|
+
if self.user_variables:
|
478
|
+
self.code_provider.set_user_variables(self.user_variables)
|
479
|
+
|
480
|
+
# Rebuild system prompt to include new authorized imports
|
481
|
+
self.system_prompt = self._build_system_prompt()
|
482
|
+
# Update the agent's system prompt
|
483
|
+
self.agent.system_prompt = self.system_prompt
|
484
|
+
|
485
|
+
def get_authorized_imports(self) -> List[str]:
|
486
|
+
"""
|
487
|
+
Get a copy of current authorized imports.
|
488
|
+
|
489
|
+
Returns:
|
490
|
+
List of authorized imports
|
491
|
+
"""
|
492
|
+
return self.authorized_imports.copy()
|
493
|
+
|
494
|
+
def remove_authorized_import(self, import_name: str):
|
495
|
+
"""
|
496
|
+
Remove an authorized import.
|
497
|
+
|
498
|
+
Args:
|
499
|
+
import_name: Import name to remove
|
500
|
+
"""
|
501
|
+
if import_name in self.authorized_imports:
|
502
|
+
self.authorized_imports.remove(import_name)
|
503
|
+
|
504
|
+
# Update the provider with the new authorized imports
|
505
|
+
# This requires recreating the provider
|
506
|
+
print("⚠️ Warning: Removing authorized imports after initialization requires recreating the Modal environment.")
|
507
|
+
print(" For better performance, set authorized_imports during TinyCodeAgent initialization.")
|
508
|
+
|
509
|
+
# Recreate the provider with updated authorized imports
|
510
|
+
self.code_provider = self._create_provider(self.provider, self.provider_config)
|
511
|
+
|
512
|
+
# Re-set user variables if they exist
|
513
|
+
if self.user_variables:
|
514
|
+
self.code_provider.set_user_variables(self.user_variables)
|
515
|
+
|
516
|
+
# Rebuild system prompt to reflect updated authorized imports
|
517
|
+
self.system_prompt = self._build_system_prompt()
|
518
|
+
# Update the agent's system prompt
|
519
|
+
self.agent.system_prompt = self.system_prompt
|
520
|
+
|
444
521
|
async def close(self):
|
445
522
|
"""Clean up resources."""
|
446
523
|
await self.code_provider.cleanup()
|
@@ -498,6 +575,7 @@ async def run_example():
|
|
498
575
|
user_variables={
|
499
576
|
"sample_data": [1, 2, 3, 4, 5, 10, 15, 20]
|
500
577
|
},
|
578
|
+
authorized_imports=["tinyagent", "gradio", "requests", "numpy", "pandas"], # Explicitly specify authorized imports
|
501
579
|
local_execution=False # Remote execution via Modal (default)
|
502
580
|
)
|
503
581
|
|
@@ -524,6 +602,7 @@ async def run_example():
|
|
524
602
|
user_variables={
|
525
603
|
"sample_data": [1, 2, 3, 4, 5, 10, 15, 20]
|
526
604
|
},
|
605
|
+
authorized_imports=["tinyagent", "gradio", "requests"], # More restricted imports for local execution
|
527
606
|
local_execution=True # Local execution
|
528
607
|
)
|
529
608
|
|
@@ -550,6 +629,18 @@ async def run_example():
|
|
550
629
|
agent_remote.add_code_tool(validator)
|
551
630
|
agent_local.add_code_tool(validator)
|
552
631
|
|
632
|
+
# Demonstrate adding authorized imports dynamically
|
633
|
+
print("\n" + "="*80)
|
634
|
+
print("🔧 Testing with dynamically added authorized imports")
|
635
|
+
agent_remote.add_authorized_imports(["matplotlib", "seaborn"])
|
636
|
+
|
637
|
+
# Test with visualization libraries
|
638
|
+
viz_prompt = "Create a simple plot of the sample_data and save it as a base64 encoded image string."
|
639
|
+
|
640
|
+
response_viz = await agent_remote.run(viz_prompt)
|
641
|
+
print("Remote Agent Visualization Response:")
|
642
|
+
print(response_viz)
|
643
|
+
|
553
644
|
print("\n" + "="*80)
|
554
645
|
print("🔧 Testing with dynamically added tools")
|
555
646
|
|
tinyagent/code_agent/utils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import sys
|
2
2
|
import cloudpickle
|
3
|
-
from typing import Dict, Any
|
3
|
+
from typing import Dict, Any, List
|
4
|
+
from .safety import validate_code_safety, function_safety_context
|
4
5
|
|
5
6
|
|
6
7
|
def clean_response(resp: Dict[str, Any]) -> Dict[str, Any]:
|
@@ -40,7 +41,14 @@ def make_session_blob(ns: dict) -> bytes:
|
|
40
41
|
return cloudpickle.dumps(clean)
|
41
42
|
|
42
43
|
|
43
|
-
def _run_python(
|
44
|
+
def _run_python(
|
45
|
+
code: str,
|
46
|
+
globals_dict: Dict[str, Any] | None = None,
|
47
|
+
locals_dict: Dict[str, Any] | None = None,
|
48
|
+
authorized_imports: List[str] | None = None,
|
49
|
+
authorized_functions: List[str] | None = None,
|
50
|
+
trusted_code: bool = False,
|
51
|
+
):
|
44
52
|
"""
|
45
53
|
Execute Python code in a controlled environment with proper error handling.
|
46
54
|
|
@@ -48,6 +56,9 @@ def _run_python(code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dic
|
|
48
56
|
code: Python code to execute
|
49
57
|
globals_dict: Global variables dictionary
|
50
58
|
locals_dict: Local variables dictionary
|
59
|
+
authorized_imports: List of authorized imports that user code may access. Wildcards (e.g. "numpy.*") are supported. A value of None disables the allow-list and only blocks dangerous modules.
|
60
|
+
authorized_functions: List of authorized dangerous functions that user code may access. A value of None disables the allow-list and blocks all dangerous functions.
|
61
|
+
trusted_code: If True, skip security checks. Should only be used for framework code, tools, or default executed code.
|
51
62
|
|
52
63
|
Returns:
|
53
64
|
Dictionary containing execution results
|
@@ -56,16 +67,27 @@ def _run_python(code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dic
|
|
56
67
|
import traceback
|
57
68
|
import io
|
58
69
|
import ast
|
59
|
-
|
70
|
+
import builtins # Needed for import hook
|
71
|
+
import sys
|
72
|
+
|
73
|
+
# ------------------------------------------------------------------
|
74
|
+
# 1. Static safety analysis – refuse code containing dangerous imports or functions
|
75
|
+
# ------------------------------------------------------------------
|
76
|
+
validate_code_safety(code, authorized_imports=authorized_imports,
|
77
|
+
authorized_functions=authorized_functions, trusted_code=trusted_code)
|
78
|
+
|
60
79
|
# Make copies to avoid mutating the original parameters
|
61
80
|
globals_dict = globals_dict or {}
|
62
81
|
locals_dict = locals_dict or {}
|
63
82
|
updated_globals = globals_dict.copy()
|
64
83
|
updated_locals = locals_dict.copy()
|
65
84
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
85
|
+
# Only pre-import a **minimal** set of safe modules so that common helper
|
86
|
+
# functions work out of the box without giving user code access to the
|
87
|
+
# full standard library. Anything outside this list must be imported
|
88
|
+
# explicitly by the user – and will be blocked by the safety layer above
|
89
|
+
# if considered dangerous.
|
90
|
+
essential_modules = ['requests', 'json', 'time', 'datetime', 're', 'random', 'math','cloudpickle']
|
69
91
|
|
70
92
|
for module_name in essential_modules:
|
71
93
|
try:
|
@@ -75,12 +97,30 @@ def _run_python(code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dic
|
|
75
97
|
except ImportError:
|
76
98
|
print(f"⚠️ Warning: {module_name} module not available")
|
77
99
|
|
100
|
+
# Variable to store print output
|
101
|
+
output_buffer = []
|
102
|
+
|
103
|
+
# Create a custom print function that captures output
|
104
|
+
def custom_print(*args, **kwargs):
|
105
|
+
# Get the sep and end kwargs, defaulting to ' ' and '\n'
|
106
|
+
sep = kwargs.get('sep', ' ')
|
107
|
+
end = kwargs.get('end', '\n')
|
108
|
+
|
109
|
+
# Convert all arguments to strings and join them
|
110
|
+
output = sep.join(str(arg) for arg in args) + end
|
111
|
+
|
112
|
+
# Store the output
|
113
|
+
output_buffer.append(output)
|
114
|
+
|
115
|
+
# Add the custom print function to the globals
|
116
|
+
#updated_globals['print'] = custom_print
|
117
|
+
|
118
|
+
# Parse the code
|
78
119
|
tree = ast.parse(code, mode="exec")
|
79
120
|
compiled = compile(tree, filename="<ast>", mode="exec")
|
80
121
|
stdout_buf = io.StringIO()
|
81
|
-
stderr_buf = io.StringIO()
|
82
|
-
|
83
|
-
# Execute with stdout+stderr capture and exception handling
|
122
|
+
stderr_buf = io.StringIO()
|
123
|
+
# Execute with exception handling
|
84
124
|
error_traceback = None
|
85
125
|
output = None
|
86
126
|
|
@@ -92,8 +132,15 @@ def _run_python(code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dic
|
|
92
132
|
merged_globals = updated_globals.copy()
|
93
133
|
merged_globals.update(updated_locals)
|
94
134
|
|
135
|
+
# Add 'exec' to authorized_functions for internal use
|
136
|
+
internal_authorized_functions = ['exec','eval']
|
137
|
+
if authorized_functions is not None and not isinstance(authorized_functions, bool):
|
138
|
+
internal_authorized_functions.extend(authorized_functions)
|
139
|
+
|
95
140
|
# Execute with only globals - this fixes generator expression scoping issues
|
96
|
-
|
141
|
+
# Use the function_safety_context to block dangerous functions during execution
|
142
|
+
with function_safety_context(authorized_functions=internal_authorized_functions, trusted_code=trusted_code):
|
143
|
+
output = exec(compiled, merged_globals)
|
97
144
|
|
98
145
|
# Update both dictionaries with any new variables created during execution
|
99
146
|
for key, value in merged_globals.items():
|
@@ -106,6 +153,8 @@ def _run_python(code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dic
|
|
106
153
|
# Capture the full traceback as a string
|
107
154
|
error_traceback = traceback.format_exc()
|
108
155
|
|
156
|
+
# Join all captured output
|
157
|
+
#printed_output = ''.join(output_buffer)
|
109
158
|
printed_output = stdout_buf.getvalue()
|
110
159
|
stderr_output = stderr_buf.getvalue()
|
111
160
|
error_traceback_output = error_traceback
|
@@ -5,6 +5,7 @@ import os
|
|
5
5
|
import re
|
6
6
|
import shutil
|
7
7
|
import time
|
8
|
+
import io
|
8
9
|
from pathlib import Path
|
9
10
|
from typing import Any, Dict, List, Optional, Set, Union
|
10
11
|
|
@@ -36,6 +37,7 @@ class GradioCallback:
|
|
36
37
|
show_thinking: bool = True,
|
37
38
|
show_tool_calls: bool = True,
|
38
39
|
logger: Optional[logging.Logger] = None,
|
40
|
+
log_manager: Optional[Any] = None,
|
39
41
|
):
|
40
42
|
"""
|
41
43
|
Initialize the Gradio callback.
|
@@ -46,6 +48,7 @@ class GradioCallback:
|
|
46
48
|
show_thinking: Whether to show the thinking process
|
47
49
|
show_tool_calls: Whether to show tool calls
|
48
50
|
logger: Optional logger to use
|
51
|
+
log_manager: Optional LoggingManager instance to capture logs from
|
49
52
|
"""
|
50
53
|
self.logger = logger or logging.getLogger(__name__)
|
51
54
|
self.show_thinking = show_thinking
|
@@ -81,6 +84,37 @@ class GradioCallback:
|
|
81
84
|
# References to Gradio UI components (will be set in create_app)
|
82
85
|
self._chatbot_component = None
|
83
86
|
self._token_usage_component = None
|
87
|
+
|
88
|
+
# Log stream for displaying logs in the UI
|
89
|
+
self.log_stream = io.StringIO()
|
90
|
+
self._log_component = None
|
91
|
+
|
92
|
+
# Setup logging
|
93
|
+
self.log_manager = log_manager
|
94
|
+
if log_manager:
|
95
|
+
# Create a handler that writes to our StringIO stream
|
96
|
+
self.log_handler = logging.StreamHandler(self.log_stream)
|
97
|
+
self.log_handler.setFormatter(
|
98
|
+
logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
99
|
+
)
|
100
|
+
self.log_handler.setLevel(logging.DEBUG)
|
101
|
+
|
102
|
+
# Add the handler to the LoggingManager
|
103
|
+
log_manager.configure_handler(
|
104
|
+
self.log_handler,
|
105
|
+
format_string='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
|
106
|
+
level=logging.DEBUG
|
107
|
+
)
|
108
|
+
self.logger.debug("Added log handler to LoggingManager")
|
109
|
+
elif logger:
|
110
|
+
# Fall back to single logger if no LoggingManager is provided
|
111
|
+
self.log_handler = logging.StreamHandler(self.log_stream)
|
112
|
+
self.log_handler.setFormatter(
|
113
|
+
logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
114
|
+
)
|
115
|
+
self.log_handler.setLevel(logging.DEBUG)
|
116
|
+
logger.addHandler(self.log_handler)
|
117
|
+
self.logger.debug("Added log handler to logger")
|
84
118
|
|
85
119
|
self.logger.debug("GradioCallback initialized")
|
86
120
|
|
@@ -525,7 +559,7 @@ class GradioCallback:
|
|
525
559
|
typing_message_index = len(chatbot_history) - 1
|
526
560
|
|
527
561
|
# Initial yield to show user message and typing indicator
|
528
|
-
yield chatbot_history, self._get_token_usage_text()
|
562
|
+
yield chatbot_history, self._get_token_usage_text(), self.log_stream.getvalue() if self._log_component else None
|
529
563
|
|
530
564
|
# Kick off the agent in the background
|
531
565
|
loop = asyncio.get_event_loop()
|
@@ -632,9 +666,10 @@ class GradioCallback:
|
|
632
666
|
del in_progress_tool_calls[tid]
|
633
667
|
self.logger.debug(f"Updated tool call to completed: {tname}")
|
634
668
|
|
635
|
-
# yield updated history + token usage
|
669
|
+
# yield updated history + token usage + logs
|
636
670
|
token_text = self._get_token_usage_text()
|
637
|
-
|
671
|
+
logs = self.log_stream.getvalue() if self._log_component else None
|
672
|
+
yield chatbot_history, token_text, logs
|
638
673
|
self.last_update_yield_time = now
|
639
674
|
|
640
675
|
await asyncio.sleep(update_interval)
|
@@ -657,8 +692,9 @@ class GradioCallback:
|
|
657
692
|
)
|
658
693
|
self.logger.debug(f"Added final result: {final_text[:50]}...")
|
659
694
|
|
660
|
-
# final token usage
|
661
|
-
|
695
|
+
# final token usage and logs
|
696
|
+
logs = self.log_stream.getvalue() if self._log_component else None
|
697
|
+
yield chatbot_history, self._get_token_usage_text(), logs
|
662
698
|
|
663
699
|
def _format_response(self, response_text):
|
664
700
|
"""
|
@@ -839,6 +875,22 @@ class GradioCallback:
|
|
839
875
|
# Clear button
|
840
876
|
clear_btn = gr.Button("Clear Conversation")
|
841
877
|
|
878
|
+
# Log accordion - similar to the example provided
|
879
|
+
with gr.Accordion("Agent Logs", open=False) as log_accordion:
|
880
|
+
self._log_component = gr.Code(
|
881
|
+
label="Live Logs",
|
882
|
+
lines=15,
|
883
|
+
interactive=False,
|
884
|
+
value=self.log_stream.getvalue()
|
885
|
+
)
|
886
|
+
refresh_logs_btn = gr.Button("🔄 Refresh Logs")
|
887
|
+
refresh_logs_btn.click(
|
888
|
+
fn=lambda: self.log_stream.getvalue(),
|
889
|
+
inputs=None,
|
890
|
+
outputs=[self._log_component],
|
891
|
+
queue=False
|
892
|
+
)
|
893
|
+
|
842
894
|
# Store processed input temporarily between steps
|
843
895
|
processed_input_state = gr.State("")
|
844
896
|
|
@@ -859,7 +911,7 @@ class GradioCallback:
|
|
859
911
|
# 3. Run the main interaction loop (this yields updates)
|
860
912
|
fn=self.interact_with_agent,
|
861
913
|
inputs=[processed_input_state, self._chatbot_component],
|
862
|
-
outputs=[self._chatbot_component, self._token_usage_component], # Update chat and
|
914
|
+
outputs=[self._chatbot_component, self._token_usage_component, self._log_component], # Update chat, tokens, and logs
|
863
915
|
queue=True # Explicitly enable queue for this async generator
|
864
916
|
).then(
|
865
917
|
# 4. Re-enable the button after interaction finishes
|
@@ -885,7 +937,7 @@ class GradioCallback:
|
|
885
937
|
# 3. Run the main interaction loop (this yields updates)
|
886
938
|
fn=self.interact_with_agent,
|
887
939
|
inputs=[processed_input_state, self._chatbot_component],
|
888
|
-
outputs=[self._chatbot_component, self._token_usage_component], # Update chat and
|
940
|
+
outputs=[self._chatbot_component, self._token_usage_component, self._log_component], # Update chat, tokens, and logs
|
889
941
|
queue=True # Explicitly enable queue for this async generator
|
890
942
|
).then(
|
891
943
|
# 4. Re-enable the button after interaction finishes
|
@@ -899,8 +951,8 @@ class GradioCallback:
|
|
899
951
|
clear_btn.click(
|
900
952
|
fn=self.clear_conversation,
|
901
953
|
inputs=None, # No inputs needed
|
902
|
-
# Outputs: Clear chatbot
|
903
|
-
outputs=[self._chatbot_component, self._token_usage_component],
|
954
|
+
# Outputs: Clear chatbot, reset token text, and update logs
|
955
|
+
outputs=[self._chatbot_component, self._token_usage_component, self._log_component],
|
904
956
|
queue=False # Run quickly
|
905
957
|
)
|
906
958
|
|
@@ -917,6 +969,12 @@ class GradioCallback:
|
|
917
969
|
self.assistant_text_responses = []
|
918
970
|
self.token_usage = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
919
971
|
self.is_running = False
|
972
|
+
|
973
|
+
# Clear log stream
|
974
|
+
if hasattr(self, 'log_stream'):
|
975
|
+
self.log_stream.seek(0)
|
976
|
+
self.log_stream.truncate(0)
|
977
|
+
self.logger.info("Log stream cleared")
|
920
978
|
|
921
979
|
# Completely reset the agent state with a new session
|
922
980
|
try:
|
@@ -965,8 +1023,9 @@ class GradioCallback:
|
|
965
1023
|
except Exception as e:
|
966
1024
|
self.logger.error(f"Failed to reset TinyAgent completely: {e}")
|
967
1025
|
|
968
|
-
# Return cleared UI components: empty chat + fresh token usage
|
969
|
-
|
1026
|
+
# Return cleared UI components: empty chat + fresh token usage + empty logs
|
1027
|
+
logs = self.log_stream.getvalue() if hasattr(self, 'log_stream') else ""
|
1028
|
+
return [], self._get_token_usage_text(), logs
|
970
1029
|
|
971
1030
|
def launch(self, agent, title="TinyAgent Chat", description=None, share=False, **kwargs):
|
972
1031
|
"""
|
@@ -1028,21 +1087,31 @@ async def run_example():
|
|
1028
1087
|
from tinyagent import TinyAgent # Assuming TinyAgent is importable
|
1029
1088
|
from tinyagent.hooks.logging_manager import LoggingManager # Assuming LoggingManager exists
|
1030
1089
|
|
1031
|
-
# --- Logging Setup (
|
1090
|
+
# --- Logging Setup (Similar to the example provided) ---
|
1032
1091
|
log_manager = LoggingManager(default_level=logging.INFO)
|
1033
1092
|
log_manager.set_levels({
|
1034
1093
|
'tinyagent.hooks.gradio_callback': logging.DEBUG,
|
1035
1094
|
'tinyagent.tiny_agent': logging.DEBUG,
|
1036
1095
|
'tinyagent.mcp_client': logging.DEBUG,
|
1096
|
+
'tinyagent.code_agent': logging.DEBUG,
|
1037
1097
|
})
|
1098
|
+
|
1099
|
+
# Console handler for terminal output
|
1038
1100
|
console_handler = logging.StreamHandler(sys.stdout)
|
1039
1101
|
log_manager.configure_handler(
|
1040
1102
|
console_handler,
|
1041
1103
|
format_string='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
1042
1104
|
level=logging.DEBUG
|
1043
1105
|
)
|
1106
|
+
|
1107
|
+
# The Gradio UI will automatically set up its own log handler
|
1108
|
+
# through the LoggingManager when we pass it to GradioCallback
|
1109
|
+
|
1110
|
+
# Get loggers for different components
|
1044
1111
|
ui_logger = log_manager.get_logger('tinyagent.hooks.gradio_callback')
|
1045
1112
|
agent_logger = log_manager.get_logger('tinyagent.tiny_agent')
|
1113
|
+
mcp_logger = log_manager.get_logger('tinyagent.mcp_client')
|
1114
|
+
|
1046
1115
|
ui_logger.info("--- Starting GradioCallback Example ---")
|
1047
1116
|
# --- End Logging Setup ---
|
1048
1117
|
|
@@ -1064,12 +1133,13 @@ async def run_example():
|
|
1064
1133
|
|
1065
1134
|
agent.add_tool(get_weather)
|
1066
1135
|
|
1067
|
-
# Create the Gradio callback
|
1136
|
+
# Create the Gradio callback with LoggingManager integration
|
1068
1137
|
gradio_ui = GradioCallback(
|
1069
1138
|
file_upload_folder=upload_folder,
|
1070
1139
|
show_thinking=True,
|
1071
1140
|
show_tool_calls=True,
|
1072
|
-
logger=ui_logger
|
1141
|
+
logger=ui_logger,
|
1142
|
+
log_manager=log_manager # Pass the LoggingManager for comprehensive logging
|
1073
1143
|
)
|
1074
1144
|
agent.add_callback(gradio_ui)
|
1075
1145
|
|
@@ -1084,25 +1154,9 @@ async def run_example():
|
|
1084
1154
|
ui_logger.error(f"Failed to connect to MCP servers: {e}", exc_info=True)
|
1085
1155
|
# Continue without servers - we still have the local get_weather tool
|
1086
1156
|
|
1087
|
-
#
|
1088
|
-
#app = gradio_ui.create_app(
|
1089
|
-
# agent,
|
1090
|
-
# title="TinyAgent Chat Interface",
|
1091
|
-
# description="Chat with TinyAgent. Try asking: 'Plan a trip to Toronto for 7 days in the next month.'",
|
1092
|
-
#)
|
1093
|
-
|
1094
|
-
# Configure the queue without extra parameters
|
1095
|
-
#app.queue()
|
1096
|
-
|
1097
|
-
# Launch the app in a way that doesn't block our event loop
|
1157
|
+
# Launch the Gradio interface
|
1098
1158
|
ui_logger.info("Launching Gradio interface...")
|
1099
1159
|
try:
|
1100
|
-
# Launch without blocking
|
1101
|
-
#app.launch(
|
1102
|
-
# share=False,
|
1103
|
-
# prevent_thread_lock=True, # Critical to not block our event loop
|
1104
|
-
# show_error=True
|
1105
|
-
#)
|
1106
1160
|
gradio_ui.launch(
|
1107
1161
|
agent,
|
1108
1162
|
title="TinyAgent Chat Interface",
|
@@ -1113,9 +1167,19 @@ async def run_example():
|
|
1113
1167
|
)
|
1114
1168
|
ui_logger.info("Gradio interface launched (non-blocking).")
|
1115
1169
|
|
1170
|
+
# Generate some log messages to demonstrate the log panel
|
1171
|
+
# These will appear in both the terminal and the Gradio UI log panel
|
1172
|
+
ui_logger.info("UI component initialized successfully")
|
1173
|
+
agent_logger.debug("Agent ready to process requests")
|
1174
|
+
mcp_logger.info("MCP connection established")
|
1175
|
+
|
1176
|
+
for i in range(3):
|
1177
|
+
ui_logger.info(f"Example log message {i+1} from UI logger")
|
1178
|
+
agent_logger.debug(f"Example debug message {i+1} from agent logger")
|
1179
|
+
mcp_logger.warning(f"Example warning {i+1} from MCP logger")
|
1180
|
+
await asyncio.sleep(1)
|
1181
|
+
|
1116
1182
|
# Keep the main event loop running to handle both Gradio and MCP operations
|
1117
|
-
# This is the key part - we need to keep our main event loop running
|
1118
|
-
# but also allow it to process both Gradio and MCP client operations
|
1119
1183
|
while True:
|
1120
1184
|
await asyncio.sleep(1) # More efficient than an Event().wait()
|
1121
1185
|
|