ndurner commited on
Commit
0c39b50
·
1 Parent(s): fd27ff3

Python Use

Browse files
Files changed (4) hide show
  1. app.py +94 -25
  2. code_exec.py +109 -0
  3. llm.py +45 -13
  4. 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
- response = br.converse_stream(
58
- modelId = model,
59
- messages = messages,
60
- system = sys_prompt,
61
- inferenceConfig = {
62
- "temperature": temperature,
63
- "maxTokens": max_tokens,
64
- }
65
- )
66
- response_stream = response.get('stream')
67
-
68
- partial_response = ""
69
- for chunk in llm.read_response(response_stream):
70
- partial_response += chunk
71
- yield partial_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- for event in response_stream:
236
- if 'contentBlockDelta' in event:
237
- yield event['contentBlockDelta']['delta']['text']
238
- if 'messageStop' in event:
239
- if log_to_console:
240
- print(f"\nStop reason: {event['messageStop']['stopReason']}")
241
- if 'metadata' in event:
242
- metadata = event['metadata']
243
- if 'usage' in metadata and log_to_console:
244
- print("\nToken usage:")
245
- print(f"Input tokens: {metadata['usage']['inputTokens']}")
246
- print(f"Output tokens: {metadata['usage']['outputTokens']}")
247
- print(f"Total tokens: {metadata['usage']['totalTokens']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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