Spaces:
Running
Running
Python Use
Browse files- app.py +94 -25
- code_exec.py +109 -0
- llm.py +45 -13
- requirements.txt +2 -1
app.py
CHANGED
@@ -7,6 +7,7 @@ from PIL import Image
|
|
7 |
|
8 |
from settings_mgr import generate_download_settings_js, generate_upload_settings_js
|
9 |
from llm import LLM, log_to_console
|
|
|
10 |
from botocore.config import Config
|
11 |
|
12 |
dump_controls = False
|
@@ -29,24 +30,39 @@ def process_values_js():
|
|
29 |
}
|
30 |
"""
|
31 |
|
32 |
-
def bot(message, history, aws_access, aws_secret, aws_token, system_prompt, temperature, max_tokens, model: str, region):
|
33 |
try:
|
34 |
llm = LLM.create_llm(model)
|
35 |
messages = llm.generate_body(message, history)
|
36 |
-
if system_prompt
|
37 |
-
sys_prompt = [{"text": system_prompt}]
|
38 |
-
else:
|
39 |
-
sys_prompt = []
|
40 |
|
41 |
config = Config(
|
42 |
read_timeout = 600,
|
43 |
connect_timeout = 30,
|
44 |
-
retries = {
|
45 |
-
'max_attempts': 10,
|
46 |
-
'mode': 'adaptive'
|
47 |
-
}
|
48 |
)
|
49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
sess = boto3.Session(
|
51 |
aws_access_key_id = aws_access,
|
52 |
aws_secret_access_key = aws_secret,
|
@@ -54,21 +70,73 @@ def bot(message, history, aws_access, aws_secret, aws_token, system_prompt, temp
|
|
54 |
region_name = region)
|
55 |
br = sess.client(service_name="bedrock-runtime", config = config)
|
56 |
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
except Exception as e:
|
74 |
raise gr.Error(f"Error: {str(e)}")
|
@@ -136,6 +204,7 @@ with gr.Blocks(delete_cache=(86400, 86400)) as demo:
|
|
136 |
choices=["eu-central-1", "eu-west-3", "us-east-1", "us-west-1", "us-west-2"])
|
137 |
temp = gr.Slider(0, 1, label="Temperature", elem_id="temp", value=1)
|
138 |
max_tokens = gr.Slider(1, 8192, label="Max. Tokens", elem_id="max_tokens", value=4096)
|
|
|
139 |
save_button = gr.Button("Save Settings")
|
140 |
load_button = gr.Button("Load Settings")
|
141 |
dl_settings_button = gr.Button("Download Settings")
|
@@ -174,7 +243,7 @@ with gr.Blocks(delete_cache=(86400, 86400)) as demo:
|
|
174 |
('max_tokens', '#max_tokens input'),
|
175 |
('model', '#model'),
|
176 |
('region', '#region')]
|
177 |
-
controls = [aws_access, aws_secret, aws_token, system_prompt, temp, max_tokens, model, region]
|
178 |
|
179 |
dl_settings_button.click(None, controls, js=generate_download_settings_js("amz_chat_settings.bin", control_ids))
|
180 |
ul_settings_button.click(None, None, None, js=generate_upload_settings_js(control_ids))
|
|
|
7 |
|
8 |
from settings_mgr import generate_download_settings_js, generate_upload_settings_js
|
9 |
from llm import LLM, log_to_console
|
10 |
+
from code_exec import eval_restricted_script
|
11 |
from botocore.config import Config
|
12 |
|
13 |
dump_controls = False
|
|
|
30 |
}
|
31 |
"""
|
32 |
|
33 |
+
def bot(message, history, aws_access, aws_secret, aws_token, system_prompt, temperature, max_tokens, model: str, region, python_use):
|
34 |
try:
|
35 |
llm = LLM.create_llm(model)
|
36 |
messages = llm.generate_body(message, history)
|
37 |
+
sys_prompt = [{"text": system_prompt}] if system_prompt else []
|
|
|
|
|
|
|
38 |
|
39 |
config = Config(
|
40 |
read_timeout = 600,
|
41 |
connect_timeout = 30,
|
42 |
+
retries = {'max_attempts': 10, 'mode': 'adaptive'}
|
|
|
|
|
|
|
43 |
)
|
44 |
|
45 |
+
tool_config = {
|
46 |
+
"tools": [{
|
47 |
+
"toolSpec": {
|
48 |
+
"name": "eval_python",
|
49 |
+
"description": "Evaluate RestrictedPython script",
|
50 |
+
"inputSchema": {
|
51 |
+
"json": {
|
52 |
+
"type": "object",
|
53 |
+
"properties": {
|
54 |
+
"script": {
|
55 |
+
"type": "string",
|
56 |
+
"description": "The Python script that will run in a RestrictedPython context"
|
57 |
+
}
|
58 |
+
},
|
59 |
+
"required": ["script"]
|
60 |
+
}
|
61 |
+
}
|
62 |
+
}
|
63 |
+
}]
|
64 |
+
} if python_use else None
|
65 |
+
|
66 |
sess = boto3.Session(
|
67 |
aws_access_key_id = aws_access,
|
68 |
aws_secret_access_key = aws_secret,
|
|
|
70 |
region_name = region)
|
71 |
br = sess.client(service_name="bedrock-runtime", config = config)
|
72 |
|
73 |
+
whole_response = ""
|
74 |
+
while True:
|
75 |
+
response = br.converse_stream(
|
76 |
+
modelId = model,
|
77 |
+
messages = messages,
|
78 |
+
system = sys_prompt,
|
79 |
+
inferenceConfig = {
|
80 |
+
"temperature": temperature,
|
81 |
+
"maxTokens": max_tokens,
|
82 |
+
},
|
83 |
+
**({'toolConfig': tool_config} if python_use else {})
|
84 |
+
)
|
85 |
+
|
86 |
+
for stop_reason, message in llm.read_response(response.get('stream')):
|
87 |
+
if isinstance(message, str):
|
88 |
+
whole_response += message
|
89 |
+
yield whole_response
|
90 |
+
|
91 |
+
if stop_reason:
|
92 |
+
if stop_reason == "tool_use":
|
93 |
+
messages.append(message)
|
94 |
+
|
95 |
+
for content in message['content']:
|
96 |
+
if 'toolUse' in content:
|
97 |
+
tool = content['toolUse']
|
98 |
+
|
99 |
+
if tool['name'] == 'eval_python':
|
100 |
+
tool_result = {}
|
101 |
+
try:
|
102 |
+
tool_script = tool["input"]["script"]
|
103 |
+
|
104 |
+
whole_response += f"\n``` script\n{tool_script}\n```\n"
|
105 |
+
yield whole_response
|
106 |
+
|
107 |
+
tool_result = eval_restricted_script(tool_script)
|
108 |
+
tool_result_message = {
|
109 |
+
"role": "user",
|
110 |
+
"content": [
|
111 |
+
{
|
112 |
+
"toolResult": {
|
113 |
+
"toolUseId": tool['toolUseId'],
|
114 |
+
"content": [{"json": tool_result}]
|
115 |
+
}
|
116 |
+
}
|
117 |
+
]
|
118 |
+
}
|
119 |
+
|
120 |
+
whole_response += f"\n``` result\n{tool_result}\n```\n"
|
121 |
+
yield whole_response
|
122 |
+
except Exception as e:
|
123 |
+
tool_result_message = {
|
124 |
+
"role": "user",
|
125 |
+
"content": [
|
126 |
+
{
|
127 |
+
"toolResult": {
|
128 |
+
"content": [{"text": e.args[0]}],
|
129 |
+
"status": 'error'
|
130 |
+
}
|
131 |
+
}
|
132 |
+
]
|
133 |
+
}
|
134 |
+
whole_response += f"\n``` error\n{e.args[0]}\n```\n"
|
135 |
+
yield whole_response
|
136 |
+
|
137 |
+
messages.append(tool_result_message)
|
138 |
+
else:
|
139 |
+
return
|
140 |
|
141 |
except Exception as e:
|
142 |
raise gr.Error(f"Error: {str(e)}")
|
|
|
204 |
choices=["eu-central-1", "eu-west-3", "us-east-1", "us-west-1", "us-west-2"])
|
205 |
temp = gr.Slider(0, 1, label="Temperature", elem_id="temp", value=1)
|
206 |
max_tokens = gr.Slider(1, 8192, label="Max. Tokens", elem_id="max_tokens", value=4096)
|
207 |
+
python_use = gr.Checkbox(label="Python Use")
|
208 |
save_button = gr.Button("Save Settings")
|
209 |
load_button = gr.Button("Load Settings")
|
210 |
dl_settings_button = gr.Button("Download Settings")
|
|
|
243 |
('max_tokens', '#max_tokens input'),
|
244 |
('model', '#model'),
|
245 |
('region', '#region')]
|
246 |
+
controls = [aws_access, aws_secret, aws_token, system_prompt, temp, max_tokens, model, region, python_use]
|
247 |
|
248 |
dl_settings_button.click(None, controls, js=generate_download_settings_js("amz_chat_settings.bin", control_ids))
|
249 |
ul_settings_button.click(None, None, None, js=generate_upload_settings_js(control_ids))
|
code_exec.py
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from RestrictedPython import compile_restricted
|
2 |
+
from RestrictedPython.PrintCollector import PrintCollector
|
3 |
+
from RestrictedPython.Guards import safe_globals, safe_builtins, guarded_iter_unpack_sequence
|
4 |
+
from RestrictedPython.Eval import default_guarded_getiter
|
5 |
+
from RestrictedPython.Utilities import utility_builtins
|
6 |
+
from io import StringIO
|
7 |
+
|
8 |
+
def eval_restricted_script(script):
|
9 |
+
# Set up print collector and output handling
|
10 |
+
all_prints = StringIO()
|
11 |
+
|
12 |
+
class CustomPrintCollector:
|
13 |
+
"""Collect printed text, accumulating in shared StringIO"""
|
14 |
+
|
15 |
+
def __init__(self, _getattr_=None):
|
16 |
+
self.txt = []
|
17 |
+
self._getattr_ = _getattr_
|
18 |
+
|
19 |
+
def write(self, text):
|
20 |
+
all_prints.write(text)
|
21 |
+
self.txt.append(text)
|
22 |
+
|
23 |
+
def __call__(self):
|
24 |
+
result = ''.join(self.txt)
|
25 |
+
return result
|
26 |
+
|
27 |
+
def _call_print(self, *objects, **kwargs):
|
28 |
+
if kwargs.get('file', None) is None:
|
29 |
+
kwargs['file'] = self
|
30 |
+
else:
|
31 |
+
self._getattr_(kwargs['file'], 'write')
|
32 |
+
|
33 |
+
print(*objects, **kwargs)
|
34 |
+
|
35 |
+
# Create the restricted builtins dictionary
|
36 |
+
restricted_builtins = dict(safe_builtins)
|
37 |
+
restricted_builtins.update(utility_builtins) # Add safe __import__
|
38 |
+
restricted_builtins.update({
|
39 |
+
# Print handling
|
40 |
+
'_print_': CustomPrintCollector,
|
41 |
+
'_getattr_': getattr,
|
42 |
+
'_getiter_': default_guarded_getiter,
|
43 |
+
'_iter_unpack_sequence_': guarded_iter_unpack_sequence,
|
44 |
+
|
45 |
+
# Define allowed imports
|
46 |
+
'__allowed_modules__': ['math'],
|
47 |
+
'__import__': __import__,
|
48 |
+
|
49 |
+
# Basic functions
|
50 |
+
'len': len,
|
51 |
+
'range': range,
|
52 |
+
'enumerate': enumerate,
|
53 |
+
'zip': zip,
|
54 |
+
|
55 |
+
# Math operations
|
56 |
+
'sum': sum,
|
57 |
+
'max': max,
|
58 |
+
'min': min,
|
59 |
+
'abs': abs,
|
60 |
+
'round': round,
|
61 |
+
'pow': pow,
|
62 |
+
|
63 |
+
# Type conversions
|
64 |
+
'int': int,
|
65 |
+
'float': float,
|
66 |
+
'str': str,
|
67 |
+
'bool': bool,
|
68 |
+
'list': list,
|
69 |
+
'tuple': tuple,
|
70 |
+
'set': set,
|
71 |
+
'dict': dict,
|
72 |
+
'bytes': bytes,
|
73 |
+
'bytearray': bytearray,
|
74 |
+
|
75 |
+
# Sequence operations
|
76 |
+
'all': all,
|
77 |
+
'any': any,
|
78 |
+
'sorted': sorted,
|
79 |
+
'reversed': reversed,
|
80 |
+
|
81 |
+
# String operations
|
82 |
+
'chr': chr,
|
83 |
+
'ord': ord,
|
84 |
+
|
85 |
+
# Other safe operations
|
86 |
+
'isinstance': isinstance,
|
87 |
+
'issubclass': issubclass,
|
88 |
+
'hasattr': hasattr,
|
89 |
+
'callable': callable,
|
90 |
+
'format': format,
|
91 |
+
})
|
92 |
+
|
93 |
+
# Create the restricted globals dictionary
|
94 |
+
restricted_globals = dict(safe_globals)
|
95 |
+
restricted_globals['__builtins__'] = restricted_builtins
|
96 |
+
|
97 |
+
try:
|
98 |
+
byte_code = compile_restricted(script, filename='<inline>', mode='exec')
|
99 |
+
exec(byte_code, restricted_globals)
|
100 |
+
|
101 |
+
return {
|
102 |
+
'prints': all_prints.getvalue(),
|
103 |
+
'success': True
|
104 |
+
}
|
105 |
+
except Exception as e:
|
106 |
+
return {
|
107 |
+
'error': str(e),
|
108 |
+
'success': False
|
109 |
+
}
|
llm.py
CHANGED
@@ -232,16 +232,48 @@ class LLM:
|
|
232 |
}
|
233 |
|
234 |
def read_response(self, response_stream):
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
}
|
233 |
|
234 |
def read_response(self, response_stream):
|
235 |
+
"""
|
236 |
+
Handles response stream that may contain both regular text and tool use requests.
|
237 |
+
Yields tuples of (text, tool_request, stop_reason) where:
|
238 |
+
- text: accumulated text response
|
239 |
+
- tool_request: dict with tool use details if present, None otherwise
|
240 |
+
- stop_reason: string indicating why stream stopped, None while streaming
|
241 |
+
"""
|
242 |
+
message = {}
|
243 |
+
content = []
|
244 |
+
message['content'] = content
|
245 |
+
tool_use = {}
|
246 |
+
text = ''
|
247 |
+
stop_reason = None
|
248 |
+
|
249 |
+
for chunk in response_stream:
|
250 |
+
if 'messageStart' in chunk:
|
251 |
+
message['role'] = chunk['messageStart']['role']
|
252 |
+
elif 'contentBlockStart' in chunk:
|
253 |
+
tool = chunk['contentBlockStart']['start']['toolUse']
|
254 |
+
tool_use['toolUseId'] = tool['toolUseId']
|
255 |
+
tool_use['name'] = tool['name']
|
256 |
+
elif 'contentBlockDelta' in chunk:
|
257 |
+
delta = chunk['contentBlockDelta']['delta']
|
258 |
+
if 'toolUse' in delta:
|
259 |
+
if 'input' not in tool_use:
|
260 |
+
tool_use['input'] = ''
|
261 |
+
tool_use['input'] += delta['toolUse']['input']
|
262 |
+
elif 'text' in delta:
|
263 |
+
text += delta['text']
|
264 |
+
yield None, delta['text']
|
265 |
+
elif 'contentBlockStop' in chunk:
|
266 |
+
if 'input' in tool_use:
|
267 |
+
tool_use['input'] = json.loads(tool_use['input'])
|
268 |
+
content.append({'toolUse': tool_use})
|
269 |
+
tool_use = {}
|
270 |
+
else:
|
271 |
+
content.append({'text': text})
|
272 |
+
elif 'messageStop' in chunk:
|
273 |
+
stop_reason = chunk['messageStop']['stopReason']
|
274 |
+
yield stop_reason, message
|
275 |
+
elif 'metadata' in chunk and 'usage' in chunk['metadata'] and log_to_console:
|
276 |
+
print("\nToken usage:")
|
277 |
+
print(f"Input tokens: {metadata['usage']['inputTokens']}")
|
278 |
+
print(f"Output tokens: {metadata['usage']['outputTokens']}")
|
279 |
+
print(f"Total tokens: {metadata['usage']['totalTokens']}")
|
requirements.txt
CHANGED
@@ -2,4 +2,5 @@ gradio == 5.1
|
|
2 |
langchain
|
3 |
boto3>1.34.54
|
4 |
lxml
|
5 |
-
PyMuPDF
|
|
|
|
2 |
langchain
|
3 |
boto3>1.34.54
|
4 |
lxml
|
5 |
+
PyMuPDF
|
6 |
+
RestrictedPython
|