diff --git a/interpreter/commands.py b/interpreter/commands.py index 7da4f6de5..06fba98a4 100644 --- a/interpreter/commands.py +++ b/interpreter/commands.py @@ -160,6 +160,9 @@ def _handle_set_command(self, parts: list[str]) -> bool: value_str = parts[2] type_hint, _ = SETTINGS[param] try: + self.interpreter._client = ( + None # Reset client, in case they changed API key or API base + ) value = parse_value(value_str, type_hint) setattr(self.interpreter, param, value) print(f"Set {param} = {value}") diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index 8a6d72372..771fcc655 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -249,8 +249,8 @@ async def async_respond(self, user_input=None): provider = self.provider # Keep existing provider if set max_tokens = self.max_tokens # Keep existing max_tokens if set - if self.model == "claude-3-5-sonnet-latest": - # For some reason, Litellm can't find the model info for claude-3-5-sonnet-latest + if self.model in ["claude-3-5-sonnet-latest", "claude-3-5-sonnet-20241022"]: + # For some reason, Litellm can't find the model info for these provider = "anthropic" # Only try to get model info if we need either provider or max_tokens @@ -294,33 +294,33 @@ async def async_respond(self, user_input=None): self._spinner.start() - enable_prompt_caching = False betas = [COMPUTER_USE_BETA_FLAG] - if enable_prompt_caching: - betas.append(PROMPT_CACHING_BETA_FLAG) - image_truncation_threshold = 50 - system["cache_control"] = {"type": "ephemeral"} - edit = ToolRenderer() if ( provider == "anthropic" and not self.serve ): # Server can't handle Anthropic yet if self._client is None: - if self.api_key: - self._client = Anthropic(api_key=self.api_key) - else: - self._client = Anthropic() + anthropic_params = {} + if self.api_key is not None: + anthropic_params["api_key"] = self.api_key + if self.api_base is not None: + anthropic_params["base_url"] = self.api_base + self._client = Anthropic(**anthropic_params) if self.debug: print("Sending messages:", self.messages, "\n") + model = self.model + if model.startswith("anthropic/"): + model = model[len("anthropic/") :] + # Use Anthropic API which supports betas raw_response = self._client.beta.messages.create( max_tokens=max_tokens, messages=self.messages, - model=self.model, + model=model, system=system["text"], tools=tool_collection.to_params(), betas=betas, @@ -698,7 +698,7 @@ async def async_respond(self, user_input=None): "temperature": self.temperature, "api_key": self.api_key, "api_version": self.api_version, - "parallel_tool_calls": False, + # "parallel_tool_calls": True, } if self.tool_calling: @@ -707,13 +707,32 @@ async def async_respond(self, user_input=None): params["stream"] = False stream = False - if self.debug: - print(params) + if provider == "anthropic" and self.tool_calling: + params["tools"] = tool_collection.to_params() + for t in params["tools"]: + t["function"] = {"name": t["name"]} + if t["name"] == "computer": + t["function"]["parameters"] = { + "display_height_px": t["display_height_px"], + "display_width_px": t["display_width_px"], + "display_number": t["display_number"], + } + params["extra_headers"] = { + "anthropic-beta": "computer-use-2024-10-22" + } - if self.debug: - print("Sending request...", params) + # if self.debug: + # print("Sending request...", params) + # time.sleep(3) - time.sleep(3) + if self.debug: + print("Messages:") + for m in self.messages: + if len(str(m)) > 1000: + print(str(m)[:1000] + "...") + else: + print(str(m)) + print() raw_response = litellm.completion(**params) @@ -856,6 +875,8 @@ async def async_respond(self, user_input=None): else: user_approval = input("\nRun tool(s)? (y/n): ").lower().strip() + user_content_to_add = [] + for tool_call in message.tool_calls: function_arguments = json.loads(tool_call.function.arguments) @@ -869,43 +890,46 @@ async def async_respond(self, user_input=None): if self.tool_calling: if result.base64_image: - # Add image to tool result self.messages.append( { "role": "tool", - "content": "The user will reply with the image outputted by the tool.", + "content": "The user will reply with the tool's image output.", "tool_call_id": tool_call.id, } ) - self.messages.append( + user_content_to_add.append( { - "role": "user", - "content": [ - { - "type": "image_url", - "image_url": { - "url": f"data:image/png;base64,{result.base64_image}", - }, - } - ], - } - ) - else: - self.messages.append( - { - "role": "tool", - "content": json.dumps(dataclasses.asdict(result)), - "tool_call_id": tool_call.id, + "type": "image_url", + "image_url": { + "url": f"data:image/png;base64,{result.base64_image}", + }, } ) else: - self.messages.append( - { - "role": "user", - "content": "This was the output of the tool call. What does it mean/what's next?" - + json.dumps(dataclasses.asdict(result)), - } + text_content = ( + "This was the output of the tool call. What does it mean/what's next?\n" + + (result.output or "") ) + if result.base64_image: + content = [ + {"type": "text", "text": text_content}, + { + "type": "image", + "image_url": { + "url": "data:image/png;base64," + + result.base64_image + }, + }, + ] + else: + content = text_content + + self.messages.append({"role": "user", "content": content}) + + if user_content_to_add: + self.messages.append( + {"role": "user", "content": user_content_to_add} + ) def _ask_user_approval(self) -> str: """Ask user for approval to run a tool""" diff --git a/interpreter/misc/get_input.py b/interpreter/misc/get_input.py index a973b5f3b..1409afac5 100644 --- a/interpreter/misc/get_input.py +++ b/interpreter/misc/get_input.py @@ -11,7 +11,8 @@ async def async_get_input( placeholder_color: str = "gray", multiline_support: bool = True, ) -> str: - placeholder_text = "Describe command" + # placeholder_text = "Describe command" + placeholder_text = 'Use """ for multi-line input' history = InMemoryHistory() session = PromptSession( history=history, @@ -27,11 +28,16 @@ async def async_get_input( def _(event): current_line = event.current_buffer.document.current_line.rstrip() - if current_line == '"""': - multiline[0] = not multiline[0] + if not multiline[0] and current_line.endswith('"""'): + # Enter multiline mode + multiline[0] = True event.current_buffer.insert_text("\n") - if not multiline[0]: # If exiting multiline mode, submit - event.current_buffer.validate_and_handle() + return + + if multiline[0] and current_line.startswith('"""'): + # Exit multiline mode and submit + multiline[0] = False + event.current_buffer.validate_and_handle() return if multiline[0]: @@ -55,50 +61,50 @@ def _(event): return result -def get_input( - placeholder_text: Optional[str] = None, - placeholder_color: str = "gray", - multiline_support: bool = True, -) -> str: - placeholder_text = "Describe command" - history = InMemoryHistory() - session = PromptSession( - history=history, - enable_open_in_editor=False, - enable_history_search=False, - auto_suggest=None, - multiline=True, - ) - kb = KeyBindings() - multiline = [False] +# def get_input( +# placeholder_text: Optional[str] = None, +# placeholder_color: str = "gray", +# multiline_support: bool = True, +# ) -> str: +# placeholder_text = "Describe command" +# history = InMemoryHistory() +# session = PromptSession( +# history=history, +# enable_open_in_editor=False, +# enable_history_search=False, +# auto_suggest=None, +# multiline=True, +# ) +# kb = KeyBindings() +# multiline = [False] - @kb.add("enter") - def _(event): - current_line = event.current_buffer.document.current_line.rstrip() +# @kb.add("enter") +# def _(event): +# current_line = event.current_buffer.document.current_line.rstrip() - if current_line == '"""': - multiline[0] = not multiline[0] - event.current_buffer.insert_text("\n") - if not multiline[0]: # If exiting multiline mode, submit - event.current_buffer.validate_and_handle() - return +# if current_line == '"""': +# multiline[0] = not multiline[0] +# event.current_buffer.insert_text("\n") +# if not multiline[0]: # If exiting multiline mode, submit +# event.current_buffer.validate_and_handle() +# return - if multiline[0]: - event.current_buffer.insert_text("\n") - else: - event.current_buffer.validate_and_handle() +# if multiline[0]: +# event.current_buffer.insert_text("\n") +# else: +# event.current_buffer.validate_and_handle() - result = session.prompt( - "> ", - placeholder=HTML(f'') - if placeholder_text - else None, - key_bindings=kb, - complete_while_typing=False, - enable_suspend=False, - search_ignore_case=True, - include_default_pygments_style=False, - input_processors=[], - enable_system_prompt=False, - ) - return result +# result = session.prompt( +# "> ", +# placeholder=HTML(f'') +# if placeholder_text +# else None, +# key_bindings=kb, +# complete_while_typing=False, +# enable_suspend=False, +# search_ignore_case=True, +# include_default_pygments_style=False, +# input_processors=[], +# enable_system_prompt=False, +# ) +# return result diff --git a/interpreter/profiles.py b/interpreter/profiles.py index 6a1d6f53c..140361c14 100644 --- a/interpreter/profiles.py +++ b/interpreter/profiles.py @@ -32,7 +32,7 @@ class Profile: def __init__(self): # Default values if no profile exists # Model configuration - self.model = "claude-3-5-sonnet-latest" # The LLM model to use + self.model = "claude-3-5-sonnet-20241022" # The LLM model to use self.provider = ( None # The model provider (e.g. anthropic, openai) None will auto-detect )