├── .env.example ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ └── python-publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── dev-setup.sh ├── docs ├── bitcoin_chart.md ├── code_interpreter_response.md ├── code_interpreter_session.md ├── codebox.md ├── concepts_overview.md ├── deploy.md ├── file.md ├── index.md ├── installation.md ├── iris_dataset.md ├── settings.md ├── streamlit_webapp.md ├── usage.md └── user_request.md ├── examples ├── analyze_dataset.py ├── anthropic_claude.py ├── assets │ ├── bitcoin_chart.png │ ├── iris.csv │ └── iris_analysis.png ├── chat_cli.py ├── chat_history_backend.py ├── convert_file.py ├── frontend │ ├── __init__.py │ ├── app.py │ ├── chainlitui.py │ └── utils.py ├── plot_sin_wave.py ├── show_bitcoin_chart.py └── use_additional_tools.py ├── mkdocs.yml ├── pyproject.toml ├── requirements-dev.lock ├── requirements.lock ├── roadmap.todo ├── src └── codeinterpreterapi │ ├── __init__.py │ ├── _patch_parser.py │ ├── chains │ ├── __init__.py │ ├── extract_code.py │ ├── modifications_check.py │ └── rm_dl_link.py │ ├── chat_history.py │ ├── config.py │ ├── prompts │ ├── __init__.py │ ├── modifications_check.py │ ├── remove_dl_link.py │ └── system_message.py │ ├── schema.py │ └── session.py └── tests ├── chain_test.py ├── general_test.py └── run_examples.py /.env.example: -------------------------------------------------------------------------------- 1 | # (set True to enable logging) 2 | VERBOSE=False 3 | 4 | # (required) 5 | OPENAI_API_KEY= 6 | # ANTHROPIC_API_KEY= 7 | 8 | # (optional, required for production) 9 | # CODEBOX_API_KEY= 10 | 11 | # (optional, required for Azure OpenAI) 12 | # OPENAI_API_TYPE=azure 13 | # OPENAI_API_VERSION=2023-07-01-preview 14 | # OPENAI_API_BASE= 15 | # DEPLOYMENT_NAME= 16 | 17 | # (optional, [codebox, postgres or redis]) 18 | # HISTORY_BACKEND=postgres 19 | # REDIS_URL= 20 | # POSTGRES_URL= 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: shroominic 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: shroominic 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "poetry" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v4 15 | with: 16 | python-version: 3.x 17 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 18 | - uses: actions/cache@v3 19 | with: 20 | key: mkdocs-material-${{ env.cache_id }} 21 | path: .cache 22 | restore-keys: | 23 | mkdocs-material- 24 | - run: pip install mkdocs-material 25 | - run: mkdocs gh-deploy --force 26 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Python 18 | uses: actions/setup-python@v3 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package 28 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | .Python 3 | build/ 4 | develop-eggs/ 5 | dist/ 6 | downloads/ 7 | eggs/ 8 | .eggs/ 9 | lib/ 10 | lib64/ 11 | parts/ 12 | sdist/ 13 | var/ 14 | wheels/ 15 | share/python-wheels/ 16 | *.egg-info/ 17 | .installed.cfg 18 | *.pyc 19 | *.egg 20 | MANIFEST 21 | .vscode 22 | 23 | # Unit test / coverage reports 24 | htmlcov/ 25 | .tox/ 26 | .nox/ 27 | .coverage 28 | .coverage.* 29 | .cache 30 | nosetests.xml 31 | coverage.xml 32 | *.cover 33 | *.py,cover 34 | .hypothesis/ 35 | .pytest_cache/ 36 | cover/ 37 | 38 | # Jupyter Notebook 39 | .ipynb_checkpoints 40 | 41 | # IPython 42 | profile_default/ 43 | ipython_config.py 44 | 45 | # PEP 582 46 | __pypackages__/ 47 | 48 | # Environments 49 | .env 50 | .venv 51 | env/ 52 | venv/ 53 | ENV/ 54 | env.bak/ 55 | venv.bak/ 56 | .codebox 57 | .context 58 | 59 | # mkdocs documentation 60 | /site 61 | 62 | # mypy 63 | .mypy_cache 64 | .dmypy.json 65 | dmypy.json 66 | 67 | .DS_Store 68 | foodb_2020_04_07_json 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 Dominic Bäumer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👾 Code Interpreter API 2 | 3 | [![Version](https://badge.fury.io/py/codeinterpreterapi.svg)](https://badge.fury.io/py/codeinterpreterapi) 4 | ![Downloads](https://img.shields.io/pypi/dm/codeinterpreterapi) 5 | ![License](https://img.shields.io/pypi/l/codeinterpreterapi) 6 | ![PyVersion](https://img.shields.io/pypi/pyversions/codeinterpreterapi) 7 | 8 | A [LangChain](https://github.com/langchain-ai/langchain) implementation of the ChatGPT Code Interpreter. 9 | Using CodeBoxes as backend for sandboxed python code execution. 10 | [CodeBox](https://github.com/shroominic/codebox-api/tree/main) is the simplest cloud infrastructure for your LLM Apps. 11 | You can run everything local except the LLM using your own OpenAI API Key. 12 | 13 | ## Features 14 | 15 | - Dataset Analysis, Stock Charting, Image Manipulation, .... 16 | - Internet access and auto Python package installation 17 | - Input `text + files` -> Receive `text + files` 18 | - Conversation Memory: respond based on previous inputs 19 | - Run everything local except the OpenAI API (OpenOrca or others maybe soon) 20 | - Use CodeBox API for easy scaling in production 21 | 22 | ## Docs 23 | 24 | Checkout the [documentation](https://shroominic.github.io/codeinterpreter-api/) for more information. 25 | 26 | ## Installation 27 | 28 | Get your OpenAI API Key [here](https://platform.openai.com/account/api-keys) and install the package. 29 | 30 | ```bash 31 | pip install "codeinterpreterapi[all]" 32 | ``` 33 | 34 | Everything for local experiments are installed with the `all` extra. 35 | For deployments, you can use `pip install codeinterpreterapi` instead which does not install the additional dependencies. 36 | 37 | ## Usage 38 | 39 | To configure OpenAI and Azure OpenAI, ensure that you set the appropriate environment variables (or use a .env file): 40 | 41 | For OpenAI, set the OPENAI_API_KEY environment variable: 42 | 43 | ```bash 44 | export OPENAI_API_KEY=sk-********** 45 | ``` 46 | 47 | ```python 48 | from codeinterpreterapi import CodeInterpreterSession, settings 49 | 50 | 51 | # create a session and close it automatically 52 | with CodeInterpreterSession() as session: 53 | # generate a response based on user input 54 | response = session.generate_response( 55 | "Plot the bitcoin chart of year 2023" 56 | ) 57 | # output the response 58 | response.show() 59 | ``` 60 | 61 | ![Bitcoin YTD](https://github.com/shroominic/codeinterpreter-api/blob/main/examples/assets/bitcoin_chart.png?raw=true) 62 | Bitcoin YTD Chart Output 63 | 64 | ## Dataset Analysis 65 | 66 | ```python 67 | from codeinterpreterapi import CodeInterpreterSession, File 68 | 69 | # this example uses async but normal sync like above works too 70 | async def main(): 71 | # context manager for auto start/stop of the session 72 | async with CodeInterpreterSession() as session: 73 | # define the user request 74 | user_request = "Analyze this dataset and plot something interesting about it." 75 | files = [ 76 | # attach files to the request 77 | File.from_path("examples/assets/iris.csv"), 78 | ] 79 | 80 | # generate the response 81 | response = await session.generate_response( 82 | user_request, files=files 83 | ) 84 | 85 | # output to the user 86 | print("AI: ", response.content) 87 | for file in response.files: 88 | # iterate over the files (display if image) 89 | file.show_image() 90 | 91 | 92 | if __name__ == "__main__": 93 | import asyncio 94 | 95 | asyncio.run(main()) 96 | ``` 97 | 98 | ![Iris Dataset Analysis](https://github.com/shroominic/codeinterpreter-api/blob/main/examples/assets/iris_analysis.png?raw=true) 99 | Iris Dataset Analysis Output 100 | 101 | ## Production 102 | 103 | In case you want to deploy to production, you can utilize the CodeBox API for seamless scalability. 104 | 105 | Please contact me if you are interested in this, as it is still in the early stages of development. 106 | 107 | ## Contributing 108 | 109 | There are some remaining TODOs in the code. 110 | So, if you want to contribute, feel free to do so. 111 | You can also suggest new features. Code refactoring is also welcome. 112 | Just open an issue or pull request and I will review it. 113 | 114 | Please also submit any bugs you find as an issue with a minimal code example or screenshot. 115 | This helps me a lot in improving the code. 116 | 117 | ## Contact 118 | 119 | You can contact me at [contact@shroominic.com](mailto:contact@shroominic.com). 120 | But I prefer to use [Twitter](https://twitter.com/shroominic) or [Discord](https://discord.gg/Vaq25XJvvW) DMs. 121 | 122 | ## Support this project 123 | 124 | If you would like to help this project with a donation, you can [click here](https://ko-fi.com/shroominic). 125 | Thanks, this helps a lot! ❤️ 126 | -------------------------------------------------------------------------------- /dev-setup.sh: -------------------------------------------------------------------------------- 1 | # AUTO DEV SETUP 2 | 3 | # check if rye is installed 4 | if ! command -v rye &> /dev/null 5 | then 6 | echo "rye could not be found: installing now ..." 7 | curl -sSf https://rye-up.com/get | bash 8 | echo "Check the rye docs for more info: https://rye-up.com/" 9 | fi 10 | 11 | echo "SYNC: setup .venv" 12 | rye sync 13 | 14 | echo "ACTIVATE: activate .venv" 15 | rye shell 16 | 17 | echo "SETUP: install pre-commit hooks" 18 | pre-commit install 19 | 20 | echo "TESTING ..." 21 | pytest 22 | -------------------------------------------------------------------------------- /docs/bitcoin_chart.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Chart 2 | 3 | This example creates a CodeInterpreterSession and generates a response to plot the bitcoin chart for year 2023: 4 | 5 | ```python 6 | from codeinterpreterapi import CodeInterpreterSession 7 | 8 | with CodeInterpreterSession() as session: 9 | response = session.generate_response("Plot the bitcoin chart for year 2023") 10 | 11 | print(response.content) 12 | response.files[0].show_image() # Show the chart image 13 | ``` 14 | 15 | The session handles executing the python code to generate the chart in the sandboxed environment. The response contains the chart image that can be displayed. 16 | 17 | ![Bitcoin Chart Output](https://raw.githubusercontent.com/shroominic/codeinterpreter-api/main/examples/assets/bitcoin_chart.png) 18 | Bitcoin Chart Output 19 | -------------------------------------------------------------------------------- /docs/code_interpreter_response.md: -------------------------------------------------------------------------------- 1 | # CodeInterpreterResponse 2 | 3 | The CodeInterpreterResponse contains the AI agent's response. 4 | 5 | It contains: 6 | 7 | - `content`: text response content 8 | - `files`: list of generated File attachments 9 | - `code_log`: log of executed code snippets 10 | -------------------------------------------------------------------------------- /docs/code_interpreter_session.md: -------------------------------------------------------------------------------- 1 | # CodeInterpreterSession 2 | 3 | The CodeInterpreterSession is the main class that manages a conversational session between the user and AI agent. It handles starting, stopping and checking status of the secure isolated code execution environment. 4 | 5 | Key responsibilities: 6 | 7 | - Starting and stopping the compute session 8 | - Sending user input and files to the agent 9 | - Running code in the compute container 10 | - Retrieving output files and images 11 | - Managing the chat history 12 | - Logging 13 | 14 | It provides methods like: 15 | 16 | - `generate_response()`: Generate AI response for user input 17 | - `start() / stop()`: Start and stop the session 18 | - `log()`: Log messages 19 | - `show_code` - Callback to print code before running. 20 | 21 | The response generation happens through a pipeline: 22 | 23 | 1. User input is parsed 24 | 2. Code is executed if needed 25 | 3. Files are processed if needed 26 | 4. Final response is formatted and returned 27 | 28 | The `generate_response()` method handles this entire pipeline. 29 | 30 | Usage: 31 | 32 | ```python 33 | from codeinterpreterapi import CodeInterpreterSession 34 | 35 | with CodeInterpreterSession() as session: 36 | response = session.generate_response("Plot a histogram of the data") 37 | print(response.content) # print AI response 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/codebox.md: -------------------------------------------------------------------------------- 1 | # CodeBox 2 | 3 | The CodeBox class provides the isolated secure environment for executing python code. It is used by the CodeInterpreterSession internally. 4 | 5 | It provides methods like: 6 | 7 | - `upload() / download()`: Upload and download files 8 | - `run()`: Run python code 9 | - `install()`: Install python packages 10 | 11 | The CodeBox handles setting up the environment, installing packages, running code, capturing output and making it available. 12 | 13 | It uses Docker containers under the hood to provide the isolated env. 14 | 15 | Usage: 16 | 17 | ```python 18 | from codeboxapi import CodeBox 19 | 20 | codebox = CodeBox() 21 | codebox.upload("data.csv", b"1,2,3\ 22 | 4,5,6") 23 | output = codebox.run("import pandas as pd; df = pd.read_csv('data.csv')") 24 | print(output.content) 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/concepts_overview.md: -------------------------------------------------------------------------------- 1 | # Concepts Overview 2 | 3 | | name | description | 4 | |-|-| 5 | | CodeInterpreterSession | Main class that manages a code execution session | 6 | | CodeBox | Handles code execution in a sandboxed environment | 7 | | File | Represents a file for upload/download to CodeBox | 8 | | UserRequest | User input message with optional file attachments | 9 | | CodeInterpreterResponse | AI response message with optional files and code log | 10 | -------------------------------------------------------------------------------- /docs/deploy.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | CodeInterpreterAPI can be easily deployed to production using the CodeBox framework. 4 | 5 | ## Prerequisites 6 | 7 | - CodeBox API key 8 | - Get your API key from [CodeBox](https://pay.codeboxapi.com/b/00g3e6dZX2fTg0gaEE) (you get an email with the api-key) 9 | - CodeInterpreterAPI installed 10 | - `pip install codeinterpreterapi` 11 | 12 | ## Setup 13 | 14 | Set the `CODEBOX_API_KEY` environment variable or directly in your code: 15 | 16 | ```python 17 | from codeinterpreterapi import settings 18 | 19 | settings.CODEBOX_API_KEY = "sk-..." 20 | ``` 21 | 22 | ## Stopping the Session 23 | 24 | Don't forget to stop the session when finished: 25 | 26 | ```python 27 | session.stop() 28 | ``` 29 | 30 | This will shutdown the CodeBox instance. 31 | 32 | ## Next Steps 33 | 34 | - See the [CodeBox docs](https://codeboxapi.com/docs) for more details on deployment options. 35 | - Look at the [examples](https://github.com/shroominic/codebox-api/tree/main/examples) for more usage ideas. 36 | - Contact [Shroominic](https://twitter.com/shroominic) for assistance with deployment. 37 | -------------------------------------------------------------------------------- /docs/file.md: -------------------------------------------------------------------------------- 1 | # File Object 2 | 3 | The File class is used to represent files that are uploaded or downloaded during the session. 4 | 5 | It stores the filename and binary content of the file. 6 | 7 | It provides utility methods like: 8 | 9 | - `from_path()`: Create File from filesystem path 10 | - `from_url` - Create File from URL 11 | - `save()`: Save File to filesystem path 12 | - `show_image()`: Display image File 13 | 14 | Usage: 15 | 16 | ```python 17 | from codeinterpreterapi import File 18 | 19 | file = File.from_path("image.png") 20 | file.show_image() # display image 21 | file.save("copy.png") # save copy 22 | ``` 23 | 24 | File objects can be passed to `CodeInterpreterSession.generate_response` to make them available to the agent. 25 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 👾 Code Interpreter API 2 | 3 | [![Version](https://badge.fury.io/py/codeinterpreterapi.svg)](https://badge.fury.io/py/codeinterpreterapi) 4 | [![code-check](https://github.com/shroominic/codeinterpreter-api/actions/workflows/code-check.yml/badge.svg)](https://github.com/shroominic/codeinterpreter-api/actions/workflows/code-check.yml) 5 | ![Downloads](https://img.shields.io/pypi/dm/codeinterpreterapi) 6 | ![License](https://img.shields.io/pypi/l/codeinterpreterapi) 7 | ![PyVersion](https://img.shields.io/pypi/pyversions/codeinterpreterapi) 8 | 9 | CodeInterpreterAPI allows you to easily build apps like the "Advanced Data Analysis" you may know from ChatGPT. Build on top of [LangChain](https://github.com/langchain-ai/langchain) and [CodeBox](https://github.com/shroominic/codebox-api), it provides a simple API for chatting with an AI that can run Python code to do anything you want. 10 | 11 | ## Key features 12 | 13 | - Dataset Analysis, Stock Charting, Image Manipulation, .... 14 | - Internet access and auto Python package installation 15 | - Input `text + files` -> Receive `text + files` 16 | - Conversation Memory: respond based on previous inputs 17 | - Run everything local except the OpenAI API (OpenOrca or others maybe soon) 18 | - Use CodeBox API for easy scaling in production 19 | 20 | ## Resources 21 | 22 | - [Blog Post](https://blog.langchain.dev/code-interpreter-api/) 23 | - [Github Repo](https://github.com/shroominic/codeinterpreter-api/) 24 | - [Documentation](https://shroominic.github.io/codeinterpreter-api/) 25 | - [Join the Discord](https://discord.gg/Vaq25XJvvW) 26 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Install the package: 4 | 5 | ```bash 6 | pip install "codeinterpreterapi[all]" 7 | ``` 8 | 9 | Everything for local experiments are installed with the all extra. For deployments, you can use `pip install codeinterpreterapi` instead which does not install the additional dependencies. 10 | 11 | ## Set Up Environment Variables 12 | 13 | You will also need to configure API keys for the AI model you want to use, either OpenAI, Anthropic, or Azure. 14 | 15 | For OpenAI, create a `.env` file with: 16 | 17 | ```bash 18 | OPENAI_API_KEY=sk-********** 19 | ``` 20 | 21 | or export as an environment variable in your terminal before running your code: 22 | 23 | ```bash 24 | export OPENAI_API_KEY=sk-********** 25 | ``` 26 | 27 | For Azure, use: 28 | 29 | ```bash 30 | OPENAI_API_TYPE=azure 31 | OPENAI_API_VERSION=2023-07-01-preview 32 | OPENAI_API_BASE= 33 | DEPLOYMENT_NAME= 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/iris_dataset.md: -------------------------------------------------------------------------------- 1 | # Analyzing the Iris Dataset 2 | 3 | ```python 4 | from codeinterpreterapi import CodeInterpreterSession, File 5 | 6 | async def main(): 7 | # context manager for auto start/stop of the session 8 | async with CodeInterpreterSession() as session: 9 | # define the user request 10 | user_request = "Analyze this dataset and plot something interesting about it." 11 | files = [ 12 | File.from_path("examples/assets/iris.csv"), 13 | ] 14 | 15 | # generate the response 16 | response = await session.generate_response( 17 | user_request, files=files 18 | ) 19 | 20 | # output to the user 21 | print("AI: ", response.content) 22 | for file in response.files: 23 | file.show_image() 24 | 25 | 26 | if __name__ == "__main__": 27 | import asyncio 28 | 29 | asyncio.run(main()) 30 | ``` 31 | 32 | ![Iris Dataset Analysis](https://github.com/shroominic/codeinterpreter-api/blob/main/examples/assets/iris_analysis.png?raw=true) 33 | Iris Dataset Analysis Output 34 | -------------------------------------------------------------------------------- /docs/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | ## Settings Class Overview 4 | 5 | The configuration is defined in a class named `CodeInterpreterAPISettings`, which inherits from Pydantic's `BaseSettings` class. 6 | 7 | `codeinterpreterapi/config.py` 8 | ```python 9 | class CodeInterpreterAPISettings(BaseSettings): 10 | ... 11 | ``` 12 | 13 | ## Setting Descriptions 14 | 15 | ### Debug Settings 16 | 17 | - `DEBUG: bool = False` 18 | Enables or disables the debug mode. 19 | 20 | ### API Keys 21 | 22 | - `OPENAI_API_KEY: Optional[str] = None` 23 | API key for the OpenAI service. 24 | 25 | - `AZURE_API_KEY: Optional[str] = None` 26 | API key for the Azure service. 27 | 28 | - `AZURE_API_BASE: Optional[str] = None` 29 | Base URL for Azure API. 30 | 31 | - `AZURE_API_VERSION: Optional[str] = None` 32 | API version for Azure service. 33 | 34 | - `AZURE_DEPLOYMENT_NAME: Optional[str] = None` 35 | Deployment name for Azure service. 36 | 37 | - `ANTHROPIC_API_KEY: Optional[SecretStr] = None` 38 | API key for the Anthropic service, stored securely. 39 | 40 | ### LLM Settings 41 | 42 | - `MODEL: str = "gpt-3.5-turbo"` 43 | The language model to be used. 44 | 45 | - `TEMPERATURE: float = 0.03` 46 | Controls randomness in the model's output. 47 | 48 | - `DETAILED_ERROR: bool = True` 49 | Enables or disables detailed error messages. 50 | 51 | - `SYSTEM_MESSAGE: SystemMessage = code_interpreter_system_message` 52 | Sets the default system message 53 | 54 | - `REQUEST_TIMEOUT: int = 3 * 60` 55 | API request timeout in seconds. 56 | 57 | - `MAX_ITERATIONS: int = 12` 58 | Maximum number of iterations for certain operations. 59 | 60 | - `MAX_RETRY: int = 3` 61 | Maximum number of API request retries. 62 | 63 | ### Production Settings 64 | 65 | - `HISTORY_BACKEND: Optional[str] = None` 66 | Specifies the history backend to be used. 67 | 68 | - `REDIS_URL: str = "redis://localhost:6379"` 69 | URL for Redis server. 70 | 71 | - `POSTGRES_URL: str = "postgresql://postgres:postgres@localhost:5432/postgres"` 72 | URL for PostgreSQL server. 73 | 74 | ### CodeBox 75 | 76 | - `CODEBOX_API_KEY: Optional[str] = None` 77 | API key for the CodeBox service. 78 | 79 | - `CUSTOM_PACKAGES: list[str] = []` 80 | List of custom Python packages to be used. 81 | 82 | ### Deprecated 83 | 84 | - `VERBOSE: bool = DEBUG` 85 | This setting is deprecated and should not be used. It defaults to the value of `DEBUG`. 86 | -------------------------------------------------------------------------------- /docs/streamlit_webapp.md: -------------------------------------------------------------------------------- 1 | # Using the Streamlit Webapp 2 | 3 | The streamlit webapp allows interacting with the API through a GUI: 4 | 5 | ```bash 6 | streamlit run frontend/app.py --browser.gatherUsageStats=False 7 | ``` 8 | 9 | This will launch the webapp where you can: 10 | 11 | - Write prompts and see results immediately 12 | - Upload files that get passed to the API 13 | - Download any files produced by the API 14 | - Switch between different models like GPT-3.5 Turbo 15 | 16 | So the webapp allows easily leveraging the API through a graphical interface. 17 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | To create a session and generate a response: 4 | 5 | ```python 6 | from codeinterpreterapi import CodeInterpreterSession, settings 7 | 8 | # set api key (or automatically loads from env vars) 9 | settings.OPENAI_API_KEY = "sk-***************" 10 | 11 | # create a session 12 | with CodeInterpreterSession() as session: 13 | # generate a response based on user input 14 | response = session.generate_response( 15 | "Plot the bitcoin chart of year 2023" 16 | ) 17 | 18 | # output the response 19 | response.show() 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/user_request.md: -------------------------------------------------------------------------------- 1 | # UserRequest 2 | 3 | The UserRequest class represents the user input to the agent. 4 | 5 | It contains: 6 | 7 | - `content`: text content of user message 8 | - `files`: list of File attachments 9 | 10 | Usage: 11 | 12 | ```python 13 | from codeinterpreterapi import UserRequest, File 14 | 15 | request = UserRequest( 16 | content="Here is an image", 17 | files=[File.from_path("image.png")] 18 | ) 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/analyze_dataset.py: -------------------------------------------------------------------------------- 1 | from codeinterpreterapi import CodeInterpreterSession, File 2 | 3 | 4 | async def main() -> None: 5 | # context manager for start/stop of the session 6 | async with CodeInterpreterSession() as session: 7 | # define the user request 8 | user_request = "Analyze this dataset and plot something interesting about it." 9 | files = [ 10 | File.from_path("examples/assets/iris.csv"), 11 | ] 12 | 13 | # generate the response 14 | response = await session.agenerate_response(user_request, files=files) 15 | 16 | # output the response (text + image) 17 | response.show() 18 | 19 | 20 | if __name__ == "__main__": 21 | import asyncio 22 | 23 | # run the async function 24 | asyncio.run(main()) 25 | -------------------------------------------------------------------------------- /examples/anthropic_claude.py: -------------------------------------------------------------------------------- 1 | from codeinterpreterapi import CodeInterpreterSession 2 | 3 | with CodeInterpreterSession(model="claude-2") as session: 4 | result = session.generate_response( 5 | "Plot the nvidea stock vs microsoft stock over the last 6 months." 6 | ) 7 | result.show() 8 | -------------------------------------------------------------------------------- /examples/assets/bitcoin_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shroominic/codeinterpreter-api/9482d36f32841a1ffd641db67f772bad1fc622b7/examples/assets/bitcoin_chart.png -------------------------------------------------------------------------------- /examples/assets/iris.csv: -------------------------------------------------------------------------------- 1 | SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species 2 | 5.1,3.5,1.4,0.2,Iris-setosa 3 | 4.9,3,1.4,0.2,Iris-setosa 4 | 4.7,3.2,1.3,0.2,Iris-setosa 5 | 4.6,3.1,1.5,0.2,Iris-setosa 6 | 5,3.6,1.4,0.2,Iris-setosa 7 | 5.4,3.9,1.7,0.4,Iris-setosa 8 | 4.6,3.4,1.4,0.3,Iris-setosa 9 | 5,3.4,1.5,0.2,Iris-setosa 10 | 4.4,2.9,1.4,0.2,Iris-setosa 11 | 4.9,3.1,1.5,0.1,Iris-setosa 12 | 5.4,3.7,1.5,0.2,Iris-setosa 13 | 4.8,3.4,1.6,0.2,Iris-setosa 14 | 4.8,3,1.4,0.1,Iris-setosa 15 | 4.3,3,1.1,0.1,Iris-setosa 16 | 5.8,4,1.2,0.2,Iris-setosa 17 | 5.7,4.4,1.5,0.4,Iris-setosa 18 | 5.4,3.9,1.3,0.4,Iris-setosa 19 | 5.1,3.5,1.4,0.3,Iris-setosa 20 | 5.7,3.8,1.7,0.3,Iris-setosa 21 | 5.1,3.8,1.5,0.3,Iris-setosa 22 | 5.4,3.4,1.7,0.2,Iris-setosa 23 | 5.1,3.7,1.5,0.4,Iris-setosa 24 | 4.6,3.6,1,0.2,Iris-setosa 25 | 5.1,3.3,1.7,0.5,Iris-setosa 26 | 4.8,3.4,1.9,0.2,Iris-setosa 27 | 5,3,1.6,0.2,Iris-setosa 28 | 5,3.4,1.6,0.4,Iris-setosa 29 | 5.2,3.5,1.5,0.2,Iris-setosa 30 | 5.2,3.4,1.4,0.2,Iris-setosa 31 | 4.7,3.2,1.6,0.2,Iris-setosa 32 | 4.8,3.1,1.6,0.2,Iris-setosa 33 | 5.4,3.4,1.5,0.4,Iris-setosa 34 | 5.2,4.1,1.5,0.1,Iris-setosa 35 | 5.5,4.2,1.4,0.2,Iris-setosa 36 | 4.9,3.1,1.5,0.1,Iris-setosa 37 | 5,3.2,1.2,0.2,Iris-setosa 38 | 5.5,3.5,1.3,0.2,Iris-setosa 39 | 4.9,3.1,1.5,0.1,Iris-setosa 40 | 4.4,3,1.3,0.2,Iris-setosa 41 | 5.1,3.4,1.5,0.2,Iris-setosa 42 | 5,3.5,1.3,0.3,Iris-setosa 43 | 4.5,2.3,1.3,0.3,Iris-setosa 44 | 4.4,3.2,1.3,0.2,Iris-setosa 45 | 5,3.5,1.6,0.6,Iris-setosa 46 | 5.1,3.8,1.9,0.4,Iris-setosa 47 | 4.8,3,1.4,0.3,Iris-setosa 48 | 5.1,3.8,1.6,0.2,Iris-setosa 49 | 4.6,3.2,1.4,0.2,Iris-setosa 50 | 5.3,3.7,1.5,0.2,Iris-setosa 51 | 5,3.3,1.4,0.2,Iris-setosa 52 | 7,3.2,4.7,1.4,Iris-versicolor 53 | 6.4,3.2,4.5,1.5,Iris-versicolor 54 | 6.9,3.1,4.9,1.5,Iris-versicolor 55 | 5.5,2.3,4,1.3,Iris-versicolor 56 | 6.5,2.8,4.6,1.5,Iris-versicolor 57 | 5.7,2.8,4.5,1.3,Iris-versicolor 58 | 6.3,3.3,4.7,1.6,Iris-versicolor 59 | 4.9,2.4,3.3,1,Iris-versicolor 60 | 6.6,2.9,4.6,1.3,Iris-versicolor 61 | 5.2,2.7,3.9,1.4,Iris-versicolor 62 | 5,2,3.5,1,Iris-versicolor 63 | 5.9,3,4.2,1.5,Iris-versicolor 64 | 6,2.2,4,1,Iris-versicolor 65 | 6.1,2.9,4.7,1.4,Iris-versicolor 66 | 5.6,2.9,3.6,1.3,Iris-versicolor 67 | 6.7,3.1,4.4,1.4,Iris-versicolor 68 | 5.6,3,4.5,1.5,Iris-versicolor 69 | 5.8,2.7,4.1,1,Iris-versicolor 70 | 6.2,2.2,4.5,1.5,Iris-versicolor 71 | 5.6,2.5,3.9,1.1,Iris-versicolor 72 | 5.9,3.2,4.8,1.8,Iris-versicolor 73 | 6.1,2.8,4,1.3,Iris-versicolor 74 | 6.3,2.5,4.9,1.5,Iris-versicolor 75 | 6.1,2.8,4.7,1.2,Iris-versicolor 76 | 6.4,2.9,4.3,1.3,Iris-versicolor 77 | 6.6,3,4.4,1.4,Iris-versicolor 78 | 6.8,2.8,4.8,1.4,Iris-versicolor 79 | 6.7,3,5,1.7,Iris-versicolor 80 | 6,2.9,4.5,1.5,Iris-versicolor 81 | 5.7,2.6,3.5,1,Iris-versicolor 82 | 5.5,2.4,3.8,1.1,Iris-versicolor 83 | 5.5,2.4,3.7,1,Iris-versicolor 84 | 5.8,2.7,3.9,1.2,Iris-versicolor 85 | 6,2.7,5.1,1.6,Iris-versicolor 86 | 5.4,3,4.5,1.5,Iris-versicolor 87 | 6,3.4,4.5,1.6,Iris-versicolor 88 | 6.7,3.1,4.7,1.5,Iris-versicolor 89 | 6.3,2.3,4.4,1.3,Iris-versicolor 90 | 5.6,3,4.1,1.3,Iris-versicolor 91 | 5.5,2.5,4,1.3,Iris-versicolor 92 | 5.5,2.6,4.4,1.2,Iris-versicolor 93 | 6.1,3,4.6,1.4,Iris-versicolor 94 | 5.8,2.6,4,1.2,Iris-versicolor 95 | 5,2.3,3.3,1,Iris-versicolor 96 | 5.6,2.7,4.2,1.3,Iris-versicolor 97 | 5.7,3,4.2,1.2,Iris-versicolor 98 | 5.7,2.9,4.2,1.3,Iris-versicolor 99 | 6.2,2.9,4.3,1.3,Iris-versicolor 100 | 5.1,2.5,3,1.1,Iris-versicolor 101 | 5.7,2.8,4.1,1.3,Iris-versicolor 102 | 6.3,3.3,6,2.5,Iris-virginica 103 | 5.8,2.7,5.1,1.9,Iris-virginica 104 | 7.1,3,5.9,2.1,Iris-virginica 105 | 6.3,2.9,5.6,1.8,Iris-virginica 106 | 6.5,3,5.8,2.2,Iris-virginica 107 | 7.6,3,6.6,2.1,Iris-virginica 108 | 4.9,2.5,4.5,1.7,Iris-virginica 109 | 7.3,2.9,6.3,1.8,Iris-virginica 110 | 6.7,2.5,5.8,1.8,Iris-virginica 111 | 7.2,3.6,6.1,2.5,Iris-virginica 112 | 6.5,3.2,5.1,2,Iris-virginica 113 | 6.4,2.7,5.3,1.9,Iris-virginica 114 | 6.8,3,5.5,2.1,Iris-virginica 115 | 5.7,2.5,5,2,Iris-virginica 116 | 5.8,2.8,5.1,2.4,Iris-virginica 117 | 6.4,3.2,5.3,2.3,Iris-virginica 118 | 6.5,3,5.5,1.8,Iris-virginica 119 | 7.7,3.8,6.7,2.2,Iris-virginica 120 | 7.7,2.6,6.9,2.3,Iris-virginica 121 | 6,2.2,5,1.5,Iris-virginica 122 | 6.9,3.2,5.7,2.3,Iris-virginica 123 | 5.6,2.8,4.9,2,Iris-virginica 124 | 7.7,2.8,6.7,2,Iris-virginica 125 | 6.3,2.7,4.9,1.8,Iris-virginica 126 | 6.7,3.3,5.7,2.1,Iris-virginica 127 | 7.2,3.2,6,1.8,Iris-virginica 128 | 6.2,2.8,4.8,1.8,Iris-virginica 129 | 6.1,3,4.9,1.8,Iris-virginica 130 | 6.4,2.8,5.6,2.1,Iris-virginica 131 | 7.2,3,5.8,1.6,Iris-virginica 132 | 7.4,2.8,6.1,1.9,Iris-virginica 133 | 7.9,3.8,6.4,2,Iris-virginica 134 | 6.4,2.8,5.6,2.2,Iris-virginica 135 | 6.3,2.8,5.1,1.5,Iris-virginica 136 | 6.1,2.6,5.6,1.4,Iris-virginica 137 | 7.7,3,6.1,2.3,Iris-virginica 138 | 6.3,3.4,5.6,2.4,Iris-virginica 139 | 6.4,3.1,5.5,1.8,Iris-virginica 140 | 6,3,4.8,1.8,Iris-virginica 141 | 6.9,3.1,5.4,2.1,Iris-virginica 142 | 6.7,3.1,5.6,2.4,Iris-virginica 143 | 6.9,3.1,5.1,2.3,Iris-virginica 144 | 5.8,2.7,5.1,1.9,Iris-virginica 145 | 6.8,3.2,5.9,2.3,Iris-virginica 146 | 6.7,3.3,5.7,2.5,Iris-virginica 147 | 6.7,3,5.2,2.3,Iris-virginica 148 | 6.3,2.5,5,1.9,Iris-virginica 149 | 6.5,3,5.2,2,Iris-virginica 150 | 6.2,3.4,5.4,2.3,Iris-virginica 151 | 5.9,3,5.1,1.8,Iris-virginica 152 | -------------------------------------------------------------------------------- /examples/assets/iris_analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shroominic/codeinterpreter-api/9482d36f32841a1ffd641db67f772bad1fc622b7/examples/assets/iris_analysis.png -------------------------------------------------------------------------------- /examples/chat_cli.py: -------------------------------------------------------------------------------- 1 | from codeinterpreterapi import CodeInterpreterSession, settings 2 | 3 | settings.MODEL = "gpt-4" 4 | 5 | print( 6 | "AI: Hello, I am the " 7 | "code interpreter agent.\n" 8 | "Ask me todo something and " 9 | "I will use python to do it!\n" 10 | ) 11 | 12 | with CodeInterpreterSession() as session: 13 | while True: 14 | session.generate_response_sync(input("\nUser: ")).show() 15 | -------------------------------------------------------------------------------- /examples/chat_history_backend.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["HISTORY_BACKEND"] = "redis" 4 | os.environ["REDIS_HOST"] = "redis://localhost:6379" 5 | 6 | from codeinterpreterapi import CodeInterpreterSession # noqa: E402 7 | 8 | 9 | def main() -> None: 10 | session_id = None 11 | 12 | session = CodeInterpreterSession() 13 | session.start() 14 | 15 | print("Session ID:", session.session_id) 16 | session_id = session.session_id 17 | 18 | response = session.generate_response("Plot the bitcoin chart of 2023 YTD") 19 | response.show() 20 | 21 | del session 22 | 23 | assert session_id is not None 24 | session = CodeInterpreterSession.from_id(session_id) 25 | 26 | response = session.generate_response("Now for the last 5 years") 27 | response.show() 28 | 29 | session.stop() 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /examples/convert_file.py: -------------------------------------------------------------------------------- 1 | from codeinterpreterapi import CodeInterpreterSession, File 2 | 3 | with CodeInterpreterSession() as session: 4 | user_request = "Convert this dataset to excel." 5 | files = [ 6 | File.from_path("examples/assets/iris.csv"), 7 | ] 8 | 9 | response = session.generate_response(user_request, files) 10 | 11 | print("AI: ", response.content) 12 | for file in response.files: 13 | if file.name == "iris.xlsx": 14 | file.save("examples/assets/iris.xlsx") 15 | -------------------------------------------------------------------------------- /examples/frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shroominic/codeinterpreter-api/9482d36f32841a1ffd641db67f772bad1fc622b7/examples/frontend/__init__.py -------------------------------------------------------------------------------- /examples/frontend/app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | import streamlit as st 5 | from codeinterpreterapi import File 6 | from utils import get_images # type: ignore 7 | 8 | # Page configuration 9 | st.set_page_config(layout="wide") 10 | 11 | st.title("Code Interpreter API 🚀") 12 | 13 | # This will create a sidebar 14 | st.sidebar.title("Code Interpreter API 🚀") 15 | st.sidebar.markdown("[Github Repo](https://github.com/shroominic/codeinterpreter-api)") 16 | 17 | 18 | # This will create a textbox where you can input text 19 | input_text = st.text_area("Write your prompt") 20 | uploaded_files = st.file_uploader("Upload your files", accept_multiple_files=True) or [] 21 | 22 | uploaded_files_list = [] 23 | for uploaded_file in uploaded_files: 24 | bytes_data = uploaded_file.read() 25 | uploaded_files_list.append(File(name=uploaded_file.name, content=bytes_data)) 26 | 27 | # This will create a button 28 | button_pressed = st.button("Run code interpreter api") 29 | 30 | # This will display the images only when the button is pressed 31 | if button_pressed and input_text != "": 32 | if sys.platform == "win32": 33 | loop = asyncio.ProactorEventLoop() 34 | asyncio.set_event_loop(loop) 35 | loop.run_until_complete(get_images(input_text, files=uploaded_files_list)) 36 | else: 37 | asyncio.run(get_images(input_text, files=uploaded_files_list)) 38 | -------------------------------------------------------------------------------- /examples/frontend/chainlitui.py: -------------------------------------------------------------------------------- 1 | import chainlit as cl # type: ignore 2 | from codeinterpreterapi import CodeInterpreterSession 3 | from codeinterpreterapi import File as CIFile 4 | 5 | UPLOADED_FILES: list[CIFile] = [] 6 | 7 | 8 | @cl.action_callback("upload_file") 9 | async def on_action(action: cl.Action) -> None: 10 | files = None 11 | 12 | # Wait for the user to upload a file 13 | while files is None: 14 | files = await cl.AskFileMessage( 15 | content="Please upload a text file to begin!", accept=["text/csv"] 16 | ).send() 17 | # Decode the file 18 | text_file = files[0] 19 | text = text_file.content.decode("utf-8") 20 | 21 | UPLOADED_FILES.append(text_file) 22 | 23 | # Let the user know that the system is ready 24 | await cl.Message( 25 | content=f"`{text_file.name}` uploaded, it contains {len(text)} characters!" 26 | ).send() 27 | await action.remove() 28 | 29 | 30 | @cl.on_chat_start 31 | async def start_chat() -> None: 32 | actions = [ 33 | cl.Action(name="upload_file", value="example_value", description="Upload file") 34 | ] 35 | 36 | await cl.Message( 37 | content="Hello, How can I assist you today", actions=actions 38 | ).send() 39 | 40 | 41 | @cl.on_message 42 | async def run_conversation(user_message: str) -> None: 43 | session = CodeInterpreterSession() 44 | await session.astart() 45 | 46 | files = [CIFile(name=it.name, content=it.content) for it in UPLOADED_FILES] 47 | 48 | response = await session.agenerate_response(user_message, files=files) 49 | elements = [ 50 | cl.Image( 51 | content=file.content, 52 | name=f"code-interpreter-image-{file.name}", 53 | display="inline", 54 | ) 55 | for file in response.files 56 | ] 57 | actions = [ 58 | cl.Action(name="upload_file", value="example_value", description="Upload file") 59 | ] 60 | await cl.Message( 61 | content=response.content, 62 | elements=elements, 63 | actions=actions, 64 | ).send() 65 | 66 | await session.astop() 67 | -------------------------------------------------------------------------------- /examples/frontend/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | from typing import Optional 5 | 6 | import streamlit as st 7 | from codeinterpreterapi import CodeInterpreterSession 8 | 9 | 10 | def create_temp_folder() -> str: 11 | """ 12 | Creates a temp folder 13 | """ 14 | temp_folder = tempfile.mkdtemp() 15 | return temp_folder 16 | 17 | 18 | async def get_images(prompt: str, files: Optional[list] = None) -> list: 19 | if files is None: 20 | files = [] 21 | with st.chat_message("user"): # type: ignore 22 | st.write(prompt) 23 | with st.spinner(): 24 | async with CodeInterpreterSession(model="gpt-3.5-turbo") as session: 25 | response = await session.agenerate_response(prompt, files=files) 26 | 27 | with st.chat_message("assistant"): # type: ignore 28 | st.write(response.content) 29 | 30 | # Showing Results 31 | for _file in response.files: 32 | st.image(_file.get_image(), caption=prompt, use_column_width=True) 33 | 34 | # Allowing the download of the results 35 | if len(response.files) == 1: 36 | st.download_button( 37 | "Download Results", 38 | response.files[0].content, 39 | file_name=response.files[0].name, 40 | ) 41 | else: 42 | target_path = tempfile.mkdtemp() 43 | for _file in response.files: 44 | _file.save(os.path.join(target_path, _file.name)) 45 | 46 | zip_path = os.path.join(os.path.dirname(target_path), "archive") 47 | shutil.make_archive(zip_path, "zip", target_path) 48 | 49 | with open(zip_path + ".zip", "rb") as f: 50 | st.download_button( 51 | "Download Results", 52 | f, 53 | file_name="archive.zip", 54 | ) 55 | return response.files 56 | -------------------------------------------------------------------------------- /examples/plot_sin_wave.py: -------------------------------------------------------------------------------- 1 | from codeinterpreterapi import CodeInterpreterSession 2 | 3 | 4 | async def main() -> None: 5 | async with CodeInterpreterSession() as session: 6 | response = await session.agenerate_response( 7 | "Plot a sin wave and show it to me." 8 | ) 9 | response.show() 10 | 11 | 12 | if __name__ == "__main__": 13 | import asyncio 14 | 15 | asyncio.run(main()) 16 | -------------------------------------------------------------------------------- /examples/show_bitcoin_chart.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from codeinterpreterapi import CodeInterpreterSession 4 | 5 | 6 | def main() -> None: 7 | with CodeInterpreterSession(local=True) as session: 8 | currentdate = datetime.now().strftime("%Y-%m-%d") 9 | 10 | response = session.generate_response( 11 | f"Plot the bitcoin chart of 2023 YTD (today is {currentdate})" 12 | ) 13 | 14 | # prints the text and shows the image 15 | response.show() 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /examples/use_additional_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | The exciting part about this example is 3 | that the code interpreter has internet access 4 | so it can download the bitcoin chart from yahoo finance 5 | and plot it for you 6 | """ 7 | import csv 8 | import io 9 | from typing import Any 10 | 11 | from codeinterpreterapi import CodeInterpreterSession 12 | from langchain_core.tools import BaseTool 13 | 14 | 15 | class ExampleKnowledgeBaseTool(BaseTool): 16 | name: str = "salary_database" 17 | description: str = "Use to get salary data of company employees" 18 | 19 | def _run(self, *args: Any, **kwargs: Any) -> Any: 20 | raise NotImplementedError() 21 | 22 | async def _arun(self, *args: Any, **kwargs: Any) -> Any: 23 | f = io.StringIO() 24 | writer = csv.writer(f) 25 | writer.writerow(["month", "employee", "salary"]) 26 | writer.writerow(["march 2022", "Jan", "1200"]) 27 | writer.writerow(["march 2022", "Ola", "1500"]) 28 | writer.writerow(["april 2022", "Jan", "1800"]) 29 | writer.writerow(["april 2022", "Ola", "2000"]) 30 | return f.getvalue() 31 | 32 | 33 | async def main() -> None: 34 | async with CodeInterpreterSession( 35 | additional_tools=[ExampleKnowledgeBaseTool()] 36 | ) as session: 37 | response = await session.agenerate_response( 38 | "Plot chart of company employee salaries" 39 | ) 40 | 41 | response.show() 42 | 43 | 44 | if __name__ == "__main__": 45 | import asyncio 46 | 47 | asyncio.run(main()) 48 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: CodeInterpreterAPI - Documentation 2 | site_author: Shroominic 3 | site_url: https://shroominic.github.io/codeinterpreter-api/ 4 | repo_name: shroominic/codeinterpreter-api 5 | repo_url: https://github.com/shroominic/codeinterpreter-api/ 6 | 7 | nav: 8 | - 'Getting Started': 9 | - 'Welcome': 'index.md' 10 | - 'Installation': 'installation.md' 11 | - 'Usage': 'usage.md' 12 | - 'Settings': 'settings.md' 13 | - 'Code Interpreter': 14 | - 'Overview': 'concepts_overview.md' 15 | - 'Session': 'code_interpreter_session.md' 16 | - 'CodeBox': 'codebox.md' 17 | - 'File': 'file.md' 18 | - 'Request': 'user_request.md' 19 | - 'Response': 'code_interpreter_response.md' 20 | - 'Deployment': 'deploy.md' 21 | - 'Examples': 22 | - 'Bitcoin Chart': 'bitcoin_chart.md' 23 | - 'Iris Dataset': 'iris_dataset.md' 24 | - 'Streamlit Webapp': 'streamlit_webapp.md' 25 | 26 | theme: 27 | name: material 28 | palette: 29 | scheme: slate 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "codeinterpreterapi" 3 | version = "0.1.20" 4 | description = "CodeInterpreterAPI is an (unofficial) open source python interface for the ChatGPT CodeInterpreter." 5 | authors = [{ name = "Shroominic", email = "contact@shroominic.com" }] 6 | dependencies = [ 7 | "langchain>=0.1, <0.2", 8 | "langchain_openai", 9 | "codeboxapi==0.1.19", 10 | "pyzmq==25.1.2", 11 | ] 12 | license = { file = "LICENSE" } 13 | readme = "README.md" 14 | requires-python = ">= 3.9.7, <3.13" 15 | keywords = [ 16 | "codeinterpreter", 17 | "chatgpt", 18 | "codeinterpreterapi", 19 | "api", 20 | "langchain", 21 | "codeboxapi", 22 | ] 23 | classifiers = [ 24 | "License :: OSI Approved :: MIT License", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | "Topic :: Scientific/Engineering :: Artificial Intelligence", 30 | ] 31 | 32 | [project.urls] 33 | Code = "https://github.com/shroominic/codeinterpreter-api" 34 | Docs = "https://shroominic.github.io/codeinterpreter-api" 35 | 36 | [build-system] 37 | requires = ["hatchling"] 38 | build-backend = "hatchling.build" 39 | 40 | [tool.rye] 41 | managed = true 42 | dev-dependencies = [ 43 | "ruff", 44 | "mypy", 45 | "isort", 46 | "pytest", 47 | "ipython", 48 | "pre-commit", 49 | "codeinterpreterapi[all]", 50 | "mkdocs-material>=9.4", 51 | ] 52 | 53 | [project.optional-dependencies] 54 | localbox = ["codeboxapi[local_support]"] 55 | frontend = ["streamlit"] 56 | image_support = ["codeboxapi[image_support]"] 57 | all = ["codeboxapi[all]", "codeinterpreterapi[frontend]"] 58 | 59 | [tool.hatch.metadata] 60 | allow-direct-references = true 61 | 62 | [tool.pytest.ini_options] 63 | addopts = "-p no:warnings" 64 | 65 | [tool.isort] 66 | multi_line_output = 3 67 | include_trailing_comma = true 68 | force_grid_wrap = 0 69 | line_length = 120 70 | 71 | [tool.flake8] 72 | max-line-length = 120 73 | 74 | [tool.mypy] 75 | ignore_missing_imports = true 76 | disallow_untyped_defs = true 77 | disallow_untyped_calls = true 78 | disallow_incomplete_defs = true 79 | 80 | [tool.ruff.lint] 81 | select = ["E", "F", "I"] 82 | -------------------------------------------------------------------------------- /requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | aiohappyeyeballs==2.4.3 14 | # via aiohttp 15 | aiohttp==3.10.10 16 | # via codeboxapi 17 | # via langchain 18 | # via langchain-community 19 | aiosignal==1.3.1 20 | # via aiohttp 21 | altair==5.4.1 22 | # via streamlit 23 | annotated-types==0.7.0 24 | # via pydantic 25 | anyio==4.6.2.post1 26 | # via httpx 27 | # via jupyter-server 28 | # via openai 29 | appnope==0.1.4 30 | # via ipykernel 31 | argon2-cffi==23.1.0 32 | # via jupyter-server 33 | # via notebook 34 | argon2-cffi-bindings==21.2.0 35 | # via argon2-cffi 36 | arrow==1.3.0 37 | # via isoduration 38 | asttokens==2.4.1 39 | # via stack-data 40 | async-timeout==4.0.3 41 | # via aiohttp 42 | # via langchain 43 | attrs==24.2.0 44 | # via aiohttp 45 | # via jsonschema 46 | # via referencing 47 | babel==2.16.0 48 | # via mkdocs-material 49 | beautifulsoup4==4.12.3 50 | # via nbconvert 51 | bleach==6.2.0 52 | # via nbconvert 53 | blinker==1.8.2 54 | # via streamlit 55 | cachetools==5.5.0 56 | # via streamlit 57 | certifi==2024.8.30 58 | # via httpcore 59 | # via httpx 60 | # via requests 61 | cffi==1.17.1 62 | # via argon2-cffi-bindings 63 | cfgv==3.4.0 64 | # via pre-commit 65 | charset-normalizer==3.4.0 66 | # via requests 67 | click==8.1.7 68 | # via mkdocs 69 | # via streamlit 70 | codeboxapi==0.1.19 71 | # via codeinterpreterapi 72 | colorama==0.4.6 73 | # via mkdocs-material 74 | comm==0.2.2 75 | # via ipykernel 76 | dataclasses-json==0.6.7 77 | # via langchain 78 | # via langchain-community 79 | debugpy==1.8.7 80 | # via ipykernel 81 | decorator==5.1.1 82 | # via ipython 83 | defusedxml==0.7.1 84 | # via nbconvert 85 | distlib==0.3.9 86 | # via virtualenv 87 | distro==1.9.0 88 | # via openai 89 | entrypoints==0.4 90 | # via jupyter-client 91 | exceptiongroup==1.2.2 92 | # via anyio 93 | # via ipython 94 | # via pytest 95 | executing==2.1.0 96 | # via stack-data 97 | fastjsonschema==2.20.0 98 | # via nbformat 99 | filelock==3.16.1 100 | # via virtualenv 101 | fqdn==1.5.1 102 | # via jsonschema 103 | frozenlist==1.5.0 104 | # via aiohttp 105 | # via aiosignal 106 | ghp-import==2.1.0 107 | # via mkdocs 108 | gitdb==4.0.11 109 | # via gitpython 110 | gitpython==3.1.43 111 | # via streamlit 112 | h11==0.14.0 113 | # via httpcore 114 | httpcore==1.0.6 115 | # via httpx 116 | httpx==0.27.2 117 | # via langsmith 118 | # via openai 119 | identify==2.6.1 120 | # via pre-commit 121 | idna==3.10 122 | # via anyio 123 | # via httpx 124 | # via jsonschema 125 | # via requests 126 | # via yarl 127 | importlib-metadata==8.5.0 128 | # via markdown 129 | # via mkdocs 130 | # via mkdocs-get-deps 131 | # via nbconvert 132 | iniconfig==2.0.0 133 | # via pytest 134 | ipykernel==6.29.5 135 | # via nbclassic 136 | # via notebook 137 | ipython==8.18.1 138 | # via ipykernel 139 | ipython-genutils==0.2.0 140 | # via nbclassic 141 | # via notebook 142 | isoduration==20.11.0 143 | # via jsonschema 144 | isort==5.13.2 145 | jedi==0.19.1 146 | # via ipython 147 | jinja2==3.1.4 148 | # via altair 149 | # via jupyter-server 150 | # via mkdocs 151 | # via mkdocs-material 152 | # via nbconvert 153 | # via notebook 154 | # via pydeck 155 | jiter==0.7.0 156 | # via openai 157 | jsonpatch==1.33 158 | # via langchain-core 159 | jsonpointer==3.0.0 160 | # via jsonpatch 161 | # via jsonschema 162 | jsonschema==4.23.0 163 | # via altair 164 | # via jupyter-events 165 | # via nbformat 166 | jsonschema-specifications==2024.10.1 167 | # via jsonschema 168 | jupyter-client==7.4.9 169 | # via ipykernel 170 | # via jupyter-kernel-gateway 171 | # via jupyter-server 172 | # via nbclient 173 | # via notebook 174 | jupyter-core==5.7.2 175 | # via ipykernel 176 | # via jupyter-client 177 | # via jupyter-kernel-gateway 178 | # via jupyter-server 179 | # via nbclient 180 | # via nbconvert 181 | # via nbformat 182 | # via notebook 183 | jupyter-events==0.10.0 184 | # via jupyter-server 185 | jupyter-kernel-gateway==2.5.2 186 | # via codeboxapi 187 | jupyter-server==2.14.2 188 | # via notebook-shim 189 | jupyter-server-terminals==0.5.3 190 | # via jupyter-server 191 | jupyterlab-pygments==0.3.0 192 | # via nbconvert 193 | langchain==0.1.20 194 | # via codeinterpreterapi 195 | langchain-community==0.0.38 196 | # via langchain 197 | langchain-core==0.1.53 198 | # via langchain 199 | # via langchain-community 200 | # via langchain-openai 201 | # via langchain-text-splitters 202 | langchain-openai==0.1.7 203 | # via codeinterpreterapi 204 | langchain-text-splitters==0.0.2 205 | # via langchain 206 | langsmith==0.1.139 207 | # via langchain 208 | # via langchain-community 209 | # via langchain-core 210 | markdown==3.7 211 | # via mkdocs 212 | # via mkdocs-material 213 | # via pymdown-extensions 214 | markdown-it-py==3.0.0 215 | # via rich 216 | markupsafe==3.0.2 217 | # via jinja2 218 | # via mkdocs 219 | # via nbconvert 220 | marshmallow==3.23.1 221 | # via dataclasses-json 222 | matplotlib-inline==0.1.7 223 | # via ipykernel 224 | # via ipython 225 | mdurl==0.1.2 226 | # via markdown-it-py 227 | mergedeep==1.3.4 228 | # via mkdocs 229 | # via mkdocs-get-deps 230 | mistune==3.0.2 231 | # via nbconvert 232 | mkdocs==1.6.1 233 | # via mkdocs-material 234 | mkdocs-get-deps==0.2.0 235 | # via mkdocs 236 | mkdocs-material==9.5.43 237 | mkdocs-material-extensions==1.3.1 238 | # via mkdocs-material 239 | multidict==6.1.0 240 | # via aiohttp 241 | # via yarl 242 | mypy==1.13.0 243 | mypy-extensions==1.0.0 244 | # via mypy 245 | # via typing-inspect 246 | narwhals==1.12.1 247 | # via altair 248 | nbclassic==1.1.0 249 | # via notebook 250 | nbclient==0.10.0 251 | # via nbconvert 252 | nbconvert==7.16.4 253 | # via jupyter-server 254 | # via notebook 255 | nbformat==5.10.4 256 | # via jupyter-server 257 | # via nbclient 258 | # via nbconvert 259 | # via notebook 260 | nest-asyncio==1.6.0 261 | # via ipykernel 262 | # via jupyter-client 263 | # via nbclassic 264 | # via notebook 265 | nodeenv==1.9.1 266 | # via pre-commit 267 | notebook==6.5.7 268 | # via jupyter-kernel-gateway 269 | notebook-shim==0.2.4 270 | # via nbclassic 271 | numpy==1.26.4 272 | # via langchain 273 | # via langchain-community 274 | # via pandas 275 | # via pydeck 276 | # via streamlit 277 | openai==1.54.3 278 | # via langchain-openai 279 | orjson==3.10.11 280 | # via langsmith 281 | overrides==7.7.0 282 | # via jupyter-server 283 | packaging==23.2 284 | # via altair 285 | # via ipykernel 286 | # via jupyter-server 287 | # via langchain-core 288 | # via marshmallow 289 | # via mkdocs 290 | # via nbconvert 291 | # via pytest 292 | # via streamlit 293 | paginate==0.5.7 294 | # via mkdocs-material 295 | pandas==2.2.3 296 | # via streamlit 297 | pandocfilters==1.5.1 298 | # via nbconvert 299 | parso==0.8.4 300 | # via jedi 301 | pathspec==0.12.1 302 | # via mkdocs 303 | pexpect==4.9.0 304 | # via ipython 305 | pillow==10.4.0 306 | # via codeboxapi 307 | # via streamlit 308 | platformdirs==4.3.6 309 | # via jupyter-core 310 | # via mkdocs-get-deps 311 | # via virtualenv 312 | pluggy==1.5.0 313 | # via pytest 314 | pre-commit==4.0.1 315 | prometheus-client==0.21.0 316 | # via jupyter-server 317 | # via notebook 318 | prompt-toolkit==3.0.48 319 | # via ipython 320 | propcache==0.2.0 321 | # via yarl 322 | protobuf==5.28.3 323 | # via streamlit 324 | psutil==6.1.0 325 | # via ipykernel 326 | ptyprocess==0.7.0 327 | # via pexpect 328 | # via terminado 329 | pure-eval==0.2.3 330 | # via stack-data 331 | pyarrow==18.0.0 332 | # via streamlit 333 | pycparser==2.22 334 | # via cffi 335 | pydantic==2.9.2 336 | # via codeboxapi 337 | # via langchain 338 | # via langchain-core 339 | # via langsmith 340 | # via openai 341 | # via pydantic-settings 342 | pydantic-core==2.23.4 343 | # via pydantic 344 | pydantic-settings==2.6.1 345 | # via codeboxapi 346 | pydeck==0.9.1 347 | # via streamlit 348 | pygments==2.18.0 349 | # via ipython 350 | # via mkdocs-material 351 | # via nbconvert 352 | # via rich 353 | pymdown-extensions==10.12 354 | # via mkdocs-material 355 | pytest==8.3.3 356 | python-dateutil==2.9.0.post0 357 | # via arrow 358 | # via ghp-import 359 | # via jupyter-client 360 | # via pandas 361 | python-dotenv==1.0.1 362 | # via pydantic-settings 363 | python-json-logger==2.0.7 364 | # via jupyter-events 365 | pytz==2024.2 366 | # via pandas 367 | pyyaml==6.0.2 368 | # via jupyter-events 369 | # via langchain 370 | # via langchain-community 371 | # via langchain-core 372 | # via mkdocs 373 | # via mkdocs-get-deps 374 | # via pre-commit 375 | # via pymdown-extensions 376 | # via pyyaml-env-tag 377 | pyyaml-env-tag==0.1 378 | # via mkdocs 379 | pyzmq==25.1.2 380 | # via codeinterpreterapi 381 | # via ipykernel 382 | # via jupyter-client 383 | # via jupyter-server 384 | # via notebook 385 | referencing==0.35.1 386 | # via jsonschema 387 | # via jsonschema-specifications 388 | # via jupyter-events 389 | regex==2024.9.11 390 | # via mkdocs-material 391 | # via tiktoken 392 | requests==2.32.3 393 | # via codeboxapi 394 | # via jupyter-kernel-gateway 395 | # via langchain 396 | # via langchain-community 397 | # via langsmith 398 | # via mkdocs-material 399 | # via requests-toolbelt 400 | # via streamlit 401 | # via tiktoken 402 | requests-toolbelt==1.0.0 403 | # via langsmith 404 | rfc3339-validator==0.1.4 405 | # via jsonschema 406 | # via jupyter-events 407 | rfc3986-validator==0.1.1 408 | # via jsonschema 409 | # via jupyter-events 410 | rich==13.9.4 411 | # via streamlit 412 | rpds-py==0.20.1 413 | # via jsonschema 414 | # via referencing 415 | ruff==0.7.2 416 | send2trash==1.8.3 417 | # via jupyter-server 418 | # via notebook 419 | six==1.16.0 420 | # via asttokens 421 | # via python-dateutil 422 | # via rfc3339-validator 423 | smmap==5.0.1 424 | # via gitdb 425 | sniffio==1.3.1 426 | # via anyio 427 | # via httpx 428 | # via openai 429 | soupsieve==2.6 430 | # via beautifulsoup4 431 | sqlalchemy==2.0.35 432 | # via langchain 433 | # via langchain-community 434 | stack-data==0.6.3 435 | # via ipython 436 | streamlit==1.39.0 437 | # via codeinterpreterapi 438 | tenacity==8.5.0 439 | # via langchain 440 | # via langchain-community 441 | # via langchain-core 442 | # via streamlit 443 | terminado==0.18.1 444 | # via jupyter-server 445 | # via jupyter-server-terminals 446 | # via notebook 447 | tiktoken==0.8.0 448 | # via langchain-openai 449 | tinycss2==1.4.0 450 | # via nbconvert 451 | toml==0.10.2 452 | # via streamlit 453 | tomli==2.0.2 454 | # via mypy 455 | # via pytest 456 | tornado==6.4.1 457 | # via ipykernel 458 | # via jupyter-client 459 | # via jupyter-kernel-gateway 460 | # via jupyter-server 461 | # via notebook 462 | # via streamlit 463 | # via terminado 464 | tqdm==4.67.0 465 | # via openai 466 | traitlets==5.14.3 467 | # via comm 468 | # via ipykernel 469 | # via ipython 470 | # via jupyter-client 471 | # via jupyter-core 472 | # via jupyter-events 473 | # via jupyter-kernel-gateway 474 | # via jupyter-server 475 | # via matplotlib-inline 476 | # via nbclient 477 | # via nbconvert 478 | # via nbformat 479 | # via notebook 480 | types-python-dateutil==2.9.0.20241003 481 | # via arrow 482 | typing-extensions==4.12.2 483 | # via altair 484 | # via anyio 485 | # via ipython 486 | # via multidict 487 | # via mypy 488 | # via openai 489 | # via pydantic 490 | # via pydantic-core 491 | # via rich 492 | # via sqlalchemy 493 | # via streamlit 494 | # via typing-inspect 495 | typing-inspect==0.9.0 496 | # via dataclasses-json 497 | tzdata==2024.2 498 | # via pandas 499 | uri-template==1.3.0 500 | # via jsonschema 501 | urllib3==2.2.3 502 | # via requests 503 | virtualenv==20.27.1 504 | # via pre-commit 505 | watchdog==6.0.0 506 | # via mkdocs 507 | wcwidth==0.2.13 508 | # via prompt-toolkit 509 | webcolors==24.8.0 510 | # via jsonschema 511 | webencodings==0.5.1 512 | # via bleach 513 | # via tinycss2 514 | websocket-client==1.8.0 515 | # via jupyter-server 516 | websockets==13.1 517 | # via codeboxapi 518 | yarl==1.17.1 519 | # via aiohttp 520 | zipp==3.20.2 521 | # via importlib-metadata 522 | -------------------------------------------------------------------------------- /requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | # generate-hashes: false 10 | # universal: false 11 | 12 | -e file:. 13 | aiohappyeyeballs==2.4.3 14 | # via aiohttp 15 | aiohttp==3.10.10 16 | # via codeboxapi 17 | # via langchain 18 | # via langchain-community 19 | aiosignal==1.3.1 20 | # via aiohttp 21 | annotated-types==0.7.0 22 | # via pydantic 23 | anyio==4.6.2.post1 24 | # via httpx 25 | # via openai 26 | async-timeout==4.0.3 27 | # via aiohttp 28 | # via langchain 29 | attrs==24.2.0 30 | # via aiohttp 31 | certifi==2024.8.30 32 | # via httpcore 33 | # via httpx 34 | # via requests 35 | charset-normalizer==3.4.0 36 | # via requests 37 | codeboxapi==0.1.19 38 | # via codeinterpreterapi 39 | dataclasses-json==0.6.7 40 | # via langchain 41 | # via langchain-community 42 | distro==1.9.0 43 | # via openai 44 | exceptiongroup==1.2.2 45 | # via anyio 46 | frozenlist==1.5.0 47 | # via aiohttp 48 | # via aiosignal 49 | h11==0.14.0 50 | # via httpcore 51 | httpcore==1.0.6 52 | # via httpx 53 | httpx==0.27.2 54 | # via langsmith 55 | # via openai 56 | idna==3.10 57 | # via anyio 58 | # via httpx 59 | # via requests 60 | # via yarl 61 | jiter==0.7.0 62 | # via openai 63 | jsonpatch==1.33 64 | # via langchain-core 65 | jsonpointer==3.0.0 66 | # via jsonpatch 67 | langchain==0.1.20 68 | # via codeinterpreterapi 69 | langchain-community==0.0.38 70 | # via langchain 71 | langchain-core==0.1.53 72 | # via langchain 73 | # via langchain-community 74 | # via langchain-openai 75 | # via langchain-text-splitters 76 | langchain-openai==0.1.7 77 | # via codeinterpreterapi 78 | langchain-text-splitters==0.0.2 79 | # via langchain 80 | langsmith==0.1.139 81 | # via langchain 82 | # via langchain-community 83 | # via langchain-core 84 | marshmallow==3.23.1 85 | # via dataclasses-json 86 | multidict==6.1.0 87 | # via aiohttp 88 | # via yarl 89 | mypy-extensions==1.0.0 90 | # via typing-inspect 91 | numpy==1.26.4 92 | # via langchain 93 | # via langchain-community 94 | openai==1.54.3 95 | # via langchain-openai 96 | orjson==3.10.11 97 | # via langsmith 98 | packaging==23.2 99 | # via langchain-core 100 | # via marshmallow 101 | propcache==0.2.0 102 | # via yarl 103 | pydantic==2.9.2 104 | # via codeboxapi 105 | # via langchain 106 | # via langchain-core 107 | # via langsmith 108 | # via openai 109 | # via pydantic-settings 110 | pydantic-core==2.23.4 111 | # via pydantic 112 | pydantic-settings==2.6.1 113 | # via codeboxapi 114 | python-dotenv==1.0.1 115 | # via pydantic-settings 116 | pyyaml==6.0.2 117 | # via langchain 118 | # via langchain-community 119 | # via langchain-core 120 | pyzmq==25.1.2 121 | # via codeinterpreterapi 122 | regex==2024.11.6 123 | # via tiktoken 124 | requests==2.32.3 125 | # via codeboxapi 126 | # via langchain 127 | # via langchain-community 128 | # via langsmith 129 | # via requests-toolbelt 130 | # via tiktoken 131 | requests-toolbelt==1.0.0 132 | # via langsmith 133 | sniffio==1.3.1 134 | # via anyio 135 | # via httpx 136 | # via openai 137 | sqlalchemy==2.0.35 138 | # via langchain 139 | # via langchain-community 140 | tenacity==8.5.0 141 | # via langchain 142 | # via langchain-community 143 | # via langchain-core 144 | tiktoken==0.8.0 145 | # via langchain-openai 146 | tqdm==4.67.0 147 | # via openai 148 | typing-extensions==4.12.2 149 | # via anyio 150 | # via multidict 151 | # via openai 152 | # via pydantic 153 | # via pydantic-core 154 | # via sqlalchemy 155 | # via typing-inspect 156 | typing-inspect==0.9.0 157 | # via dataclasses-json 158 | urllib3==2.2.3 159 | # via requests 160 | websockets==13.1 161 | # via codeboxapi 162 | yarl==1.17.1 163 | # via aiohttp 164 | -------------------------------------------------------------------------------- /roadmap.todo: -------------------------------------------------------------------------------- 1 | [ ] - funcchain rewrite 2 | 3 | [ ] - local model support 4 | 5 | [ ] - codeinterpreter-ui (self hosted web frontend) 6 | 7 | [ ] - codeinterpreter-api (serve mode) 8 | 9 | [ ] - codeinterpreter-cli (cli chat) 10 | 11 | [ ] - 12 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/__init__.py: -------------------------------------------------------------------------------- 1 | from . import _patch_parser # noqa 2 | 3 | from codeinterpreterapi.config import settings 4 | from codeinterpreterapi.schema import File 5 | from codeinterpreterapi.session import CodeInterpreterSession 6 | 7 | 8 | __all__ = [ 9 | "CodeInterpreterSession", 10 | "File", 11 | "settings", 12 | ] 13 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/_patch_parser.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from json import JSONDecodeError 4 | from typing import List, Union 5 | 6 | from langchain.agents.agent import AgentOutputParser 7 | from langchain.agents.openai_functions_agent import base 8 | from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish 9 | from langchain_core.exceptions import OutputParserException 10 | from langchain_core.messages import AIMessage, BaseMessage 11 | from langchain_core.outputs import ChatGeneration, Generation 12 | 13 | 14 | class OpenAIFunctionsAgentOutputParser(AgentOutputParser): 15 | """Parses a message into agent action/finish. 16 | 17 | Is meant to be used with OpenAI models, as it relies on the specific 18 | function_call parameter from OpenAI to convey what tools to use. 19 | 20 | If a function_call parameter is passed, then that is used to get 21 | the tool and tool input. 22 | 23 | If one is not passed, then the AIMessage is assumed to be the final output. 24 | """ 25 | 26 | @property 27 | def _type(self) -> str: 28 | return "openai-functions-agent" 29 | 30 | @staticmethod 31 | def _parse_ai_message(message: BaseMessage) -> Union[AgentAction, AgentFinish]: 32 | """Parse an AI message.""" 33 | if not isinstance(message, AIMessage): 34 | raise TypeError(f"Expected an AI message got {type(message)}") 35 | 36 | function_call = message.additional_kwargs.get("function_call", {}) 37 | 38 | if function_call: 39 | function_name = function_call["name"] 40 | try: 41 | if len(function_call["arguments"].strip()) == 0: 42 | # OpenAI returns an empty string for functions containing no args 43 | _tool_input = {} 44 | else: 45 | # otherwise it returns a json object 46 | _tool_input = json.loads(function_call["arguments"]) 47 | except JSONDecodeError: 48 | if function_name == "python": 49 | code = function_call["arguments"] 50 | _tool_input = { 51 | "code": code, 52 | } 53 | else: 54 | raise OutputParserException( 55 | f"Could not parse tool input: {function_call} because " 56 | f"the `arguments` is not valid JSON." 57 | ) 58 | 59 | # HACK HACK HACK: 60 | # The code that encodes tool input into Open AI uses a special variable 61 | # name called `__arg1` to handle old style tools that do not expose a 62 | # schema and expect a single string argument as an input. 63 | # We unpack the argument here if it exists. 64 | # Open AI does not support passing in a JSON array as an argument. 65 | if "__arg1" in _tool_input: 66 | tool_input = _tool_input["__arg1"] 67 | else: 68 | tool_input = _tool_input 69 | 70 | content_msg = f"responded: {message.content}\n" if message.content else "\n" 71 | log = f"\nInvoking: `{function_name}` with `{tool_input}`\n{content_msg}\n" 72 | return AgentActionMessageLog( 73 | tool=function_name, 74 | tool_input=tool_input, 75 | log=log, 76 | message_log=[message], 77 | ) 78 | 79 | return AgentFinish( 80 | return_values={"output": message.content}, log=str(message.content) 81 | ) 82 | 83 | def parse_result( 84 | self, result: List[Generation], *, partial: bool = False 85 | ) -> Union[AgentAction, AgentFinish]: 86 | if not isinstance(result[0], ChatGeneration): 87 | raise ValueError("This output parser only works on ChatGeneration output") 88 | message = result[0].message 89 | return self._parse_ai_message(message) 90 | 91 | async def aparse_result( 92 | self, result: List[Generation], *, partial: bool = False 93 | ) -> Union[AgentAction, AgentFinish]: 94 | return await asyncio.get_running_loop().run_in_executor( 95 | None, self.parse_result, result 96 | ) 97 | 98 | def parse(self, text: str) -> Union[AgentAction, AgentFinish]: 99 | raise ValueError("Can only parse messages") 100 | 101 | 102 | # patch 103 | base.OpenAIFunctionsAgentOutputParser = OpenAIFunctionsAgentOutputParser # type: ignore 104 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/chains/__init__.py: -------------------------------------------------------------------------------- 1 | from .extract_code import extract_python_code 2 | from .modifications_check import aget_file_modifications, get_file_modifications 3 | from .rm_dl_link import aremove_download_link, remove_download_link 4 | 5 | __all__ = [ 6 | "extract_python_code", 7 | "get_file_modifications", 8 | "aget_file_modifications", 9 | "remove_download_link", 10 | "aremove_download_link", 11 | ] 12 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/chains/extract_code.py: -------------------------------------------------------------------------------- 1 | from langchain_core.language_models import BaseLanguageModel 2 | 3 | 4 | def extract_python_code( 5 | text: str, 6 | llm: BaseLanguageModel, 7 | retry: int = 2, 8 | ) -> str: 9 | return "TODO" 10 | 11 | 12 | async def aextract_python_code( 13 | text: str, 14 | llm: BaseLanguageModel, 15 | retry: int = 2, 16 | ) -> str: 17 | return "TODO" 18 | 19 | 20 | async def test() -> None: 21 | from langchain_openai import ChatOpenAI 22 | 23 | llm = ChatOpenAI() 24 | 25 | code = """ 26 | import matplotlib.pyplot as plt 27 | 28 | x = list(range(1, 11)) 29 | y = [29, 39, 23, 32, 4, 43, 43, 23, 43, 77] 30 | 31 | plt.plot(x, y, marker='o') 32 | plt.xlabel('Index') 33 | plt.ylabel('Value') 34 | plt.title('Data Plot') 35 | 36 | plt.show() 37 | """ 38 | 39 | print(extract_python_code(code, llm)) 40 | 41 | 42 | if __name__ == "__main__": 43 | import asyncio 44 | 45 | import dotenv 46 | 47 | dotenv.load_dotenv() 48 | 49 | asyncio.run(test()) 50 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/chains/modifications_check.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List, Optional 3 | 4 | from langchain_core.language_models import BaseLanguageModel 5 | 6 | from codeinterpreterapi.prompts import determine_modifications_prompt 7 | 8 | 9 | def get_file_modifications( 10 | code: str, 11 | llm: BaseLanguageModel, 12 | retry: int = 4, 13 | ) -> Optional[List[str]]: 14 | if retry < 1: 15 | return None 16 | 17 | prompt = determine_modifications_prompt.format(code=code) 18 | 19 | result = llm.invoke(prompt) 20 | 21 | try: 22 | if isinstance(result.content, str): 23 | if result.content.endswith("```"): 24 | result.content = result.content[:-3] 25 | if result.content.startswith("```"): 26 | result.content = result.content[3:] 27 | result = json.loads(result.content) 28 | except json.JSONDecodeError: 29 | result = "" 30 | if not result or not isinstance(result, dict) or "modifications" not in result: 31 | return get_file_modifications(code, llm, retry=retry - 1) 32 | return result["modifications"] 33 | 34 | 35 | async def aget_file_modifications( 36 | code: str, 37 | llm: BaseLanguageModel, 38 | retry: int = 4, 39 | ) -> Optional[List[str]]: 40 | if retry < 1: 41 | return None 42 | 43 | prompt = determine_modifications_prompt.format(code=code) 44 | 45 | result = await llm.ainvoke(prompt) 46 | 47 | try: 48 | if isinstance(result.content, str): 49 | if result.content.endswith("```"): 50 | result.content = result.content[:-3] 51 | if result.content.startswith("```"): 52 | result.content = result.content[3:] 53 | result = json.loads(result.content) 54 | except json.JSONDecodeError: 55 | result = "" 56 | if not result or not isinstance(result, dict) or "modifications" not in result: 57 | return await aget_file_modifications(code, llm, retry=retry - 1) 58 | return result["modifications"] 59 | 60 | 61 | async def test() -> None: 62 | from langchain_openai import ChatOpenAI 63 | 64 | llm = ChatOpenAI() 65 | 66 | code = """ 67 | import matplotlib.pyplot as plt 68 | 69 | x = list(range(1, 11)) 70 | y = [29, 39, 23, 32, 4, 43, 43, 23, 43, 77] 71 | 72 | plt.plot(x, y, marker='o') 73 | plt.xlabel('Index') 74 | plt.ylabel('Value') 75 | plt.title('Data Plot') 76 | 77 | plt.show() 78 | """ 79 | 80 | print(get_file_modifications(code, llm)) 81 | 82 | 83 | if __name__ == "__main__": 84 | import asyncio 85 | 86 | import dotenv 87 | 88 | dotenv.load_dotenv() 89 | 90 | asyncio.run(test()) 91 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/chains/rm_dl_link.py: -------------------------------------------------------------------------------- 1 | from langchain_core.exceptions import OutputParserException 2 | from langchain_core.language_models import BaseLanguageModel 3 | from langchain_core.messages import AIMessage 4 | from langchain_openai import ChatOpenAI 5 | 6 | from codeinterpreterapi.prompts import remove_dl_link_prompt 7 | 8 | 9 | def remove_download_link( 10 | input_response: str, 11 | llm: BaseLanguageModel, 12 | ) -> str: 13 | messages = remove_dl_link_prompt.format_prompt( 14 | input_response=input_response 15 | ).to_messages() 16 | message = llm.invoke(messages) 17 | 18 | if not isinstance(message, AIMessage): 19 | raise OutputParserException("Expected an AIMessage") 20 | 21 | assert isinstance(message.content, str), "TODO: add image support" 22 | return message.content 23 | 24 | 25 | async def aremove_download_link( 26 | input_response: str, 27 | llm: BaseLanguageModel, 28 | ) -> str: 29 | messages = remove_dl_link_prompt.format_prompt( 30 | input_response=input_response 31 | ).to_messages() 32 | message = await llm.ainvoke(messages) 33 | 34 | if not isinstance(message, AIMessage): 35 | raise OutputParserException("Expected an AIMessage") 36 | 37 | assert isinstance(message.content, str), "TODO: add image support" 38 | return message.content 39 | 40 | 41 | def test() -> None: 42 | llm = ChatOpenAI(model="gpt-3.5-turbo-0613") # type: ignore 43 | 44 | example = ( 45 | "I have created the plot to your dataset.\n\n" 46 | "Link to the file [here](sandbox:/plot.png)." 47 | ) 48 | print(remove_download_link(example, llm)) 49 | 50 | 51 | if __name__ == "__main__": 52 | from dotenv import load_dotenv 53 | 54 | load_dotenv() 55 | 56 | test() 57 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/chat_history.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from typing import List 4 | 5 | from codeboxapi import CodeBox 6 | from langchain_core.chat_history import BaseChatMessageHistory 7 | from langchain_core.messages import BaseMessage, messages_from_dict, messages_to_dict 8 | 9 | 10 | class CodeBoxChatMessageHistory(BaseChatMessageHistory): 11 | """ 12 | Chat message history that stores history inside the codebox. 13 | """ 14 | 15 | def __init__(self, codebox: CodeBox): 16 | self.codebox = codebox 17 | 18 | if "history.json" not in [f.name for f in self.codebox.list_files()]: 19 | name, content = "history.json", b"{}" 20 | if (loop := asyncio.get_event_loop()).is_running(): 21 | loop.create_task(self.codebox.aupload(name, content)) 22 | else: 23 | self.codebox.upload(name, content) 24 | 25 | @property 26 | def messages(self) -> List[BaseMessage]: # type: ignore 27 | """Retrieve the messages from the codebox""" 28 | msgs = ( 29 | messages_from_dict(json.loads(file_content.decode("utf-8"))) 30 | if ( 31 | file_content := ( 32 | loop.run_until_complete(self.codebox.adownload("history.json")) 33 | if (loop := asyncio.get_event_loop()).is_running() 34 | else self.codebox.download("history.json") 35 | ).content 36 | ) 37 | else [] 38 | ) 39 | return msgs 40 | 41 | def add_message(self, message: BaseMessage) -> None: 42 | """Append the message to the record in the local file""" 43 | print("Current messages: ", self.messages) 44 | messages = messages_to_dict(self.messages) 45 | print("Adding message: ", message) 46 | messages.append(messages_to_dict([message])[0]) 47 | name, content = "history.json", json.dumps(messages).encode("utf-8") 48 | if (loop := asyncio.get_event_loop()).is_running(): 49 | loop.create_task(self.codebox.aupload(name, content)) 50 | else: 51 | self.codebox.upload(name, content) 52 | print("New messages: ", self.messages) 53 | 54 | def clear(self) -> None: 55 | """Clear session memory from the local file""" 56 | print("Clearing history CLEARING HISTORY") 57 | code = "import os; os.remove('history.json')" 58 | if (loop := asyncio.get_event_loop()).is_running(): 59 | loop.create_task(self.codebox.arun(code)) 60 | else: 61 | self.codebox.run(code) 62 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/config.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from langchain_core.messages import SystemMessage 4 | from langchain_core.pydantic_v1 import BaseSettings 5 | 6 | from codeinterpreterapi.prompts import code_interpreter_system_message 7 | 8 | 9 | class CodeInterpreterAPISettings(BaseSettings): 10 | """ 11 | CodeInterpreter API Config 12 | """ 13 | 14 | DEBUG: bool = False 15 | 16 | # Models 17 | OPENAI_API_KEY: Optional[str] = None 18 | AZURE_OPENAI_API_KEY: Optional[str] = None 19 | AZURE_API_BASE: Optional[str] = None 20 | AZURE_API_VERSION: Optional[str] = None 21 | AZURE_DEPLOYMENT_NAME: Optional[str] = None 22 | ANTHROPIC_API_KEY: Optional[str] = None 23 | 24 | # LLM Settings 25 | MODEL: str = "gpt-3.5-turbo" 26 | TEMPERATURE: float = 0.03 27 | DETAILED_ERROR: bool = True 28 | SYSTEM_MESSAGE: SystemMessage = code_interpreter_system_message 29 | REQUEST_TIMEOUT: int = 3 * 60 30 | MAX_ITERATIONS: int = 12 31 | MAX_RETRY: int = 3 32 | 33 | # Production Settings 34 | HISTORY_BACKEND: Optional[str] = None 35 | REDIS_URL: str = "redis://localhost:6379" 36 | POSTGRES_URL: str = "postgresql://postgres:postgres@localhost:5432/postgres" 37 | 38 | # CodeBox 39 | CODEBOX_API_KEY: Optional[str] = None 40 | CUSTOM_PACKAGES: list[str] = [] 41 | 42 | # deprecated 43 | VERBOSE: bool = DEBUG 44 | 45 | class Config: 46 | env_file = "./.env" 47 | extra = "ignore" 48 | 49 | 50 | settings = CodeInterpreterAPISettings() 51 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | from .modifications_check import determine_modifications_prompt 2 | from .remove_dl_link import remove_dl_link_prompt 3 | from .system_message import system_message as code_interpreter_system_message 4 | 5 | __all__ = [ 6 | "determine_modifications_prompt", 7 | "remove_dl_link_prompt", 8 | "code_interpreter_system_message", 9 | ] 10 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/prompts/modifications_check.py: -------------------------------------------------------------------------------- 1 | from langchain_core.prompts import PromptTemplate 2 | 3 | determine_modifications_prompt = PromptTemplate( 4 | input_variables=["code"], 5 | template="The user will input some code and you need to determine " 6 | "if the code makes any changes to the file system. \n" 7 | "With changes it means creating new files or modifying existing ones.\n" 8 | "Format your answer as JSON inside a codeblock with a " 9 | "list of filenames that are modified by the code.\n" 10 | "If the code does not make any changes to the file system, " 11 | "return an empty list.\n\n" 12 | "Determine modifications:\n" 13 | "```python\n" 14 | "import matplotlib.pyplot as plt\n" 15 | "import numpy as np\n\n" 16 | "t = np.arange(0.0, 4.0*np.pi, 0.1)\n\n" 17 | "s = np.sin(t)\n\n" 18 | "fig, ax = plt.subplots()\n\n" 19 | "ax.plot(t, s)\n\n" 20 | 'ax.set(xlabel="time (s)", ylabel="sin(t)",\n' 21 | ' title="Simple Sin Wave")\n' 22 | "ax.grid()\n\n" 23 | 'plt.savefig("sin_wave.png")\n' 24 | "```\n\n" 25 | "Answer:\n" 26 | "```json\n" 27 | "{{\n" 28 | ' "modifications": ["sin_wave.png"]\n' 29 | "}}\n" 30 | "```\n\n" 31 | "Determine modifications:\n" 32 | "```python\n" 33 | "import matplotlib.pyplot as plt\n" 34 | "import numpy as np\n\n" 35 | "x = np.linspace(0, 10, 100)\n" 36 | "y = x**2\n\n" 37 | "plt.figure(figsize=(8, 6))\n" 38 | "plt.plot(x, y)\n" 39 | 'plt.title("Simple Quadratic Function")\n' 40 | 'plt.xlabel("x")\n' 41 | 'plt.ylabel("y = x^2")\n' 42 | "plt.grid(True)\n" 43 | "plt.show()\n" 44 | "```\n\n" 45 | "Answer:\n" 46 | "```json\n" 47 | "{{\n" 48 | ' "modifications": []\n' 49 | "}}\n" 50 | "```\n\n" 51 | "Determine modifications:\n" 52 | "```python\n" 53 | "{code}\n" 54 | "```\n\n" 55 | "Answer:\n" 56 | "```json\n", 57 | ) 58 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/prompts/remove_dl_link.py: -------------------------------------------------------------------------------- 1 | from langchain_core.messages import AIMessage, HumanMessage, SystemMessage 2 | from langchain_core.prompts.chat import ChatPromptTemplate, HumanMessagePromptTemplate 3 | 4 | remove_dl_link_prompt = ChatPromptTemplate( 5 | input_variables=["input_response"], 6 | messages=[ 7 | SystemMessage( 8 | content="The user will send you a response and you need " 9 | "to remove the download link from it.\n" 10 | "Reformat the remaining message so no whitespace " 11 | "or half sentences are still there.\n" 12 | "If the response does not contain a download link, " 13 | "return the response as is.\n" 14 | ), 15 | HumanMessage( 16 | content="The dataset has been successfully converted to CSV format. " 17 | "You can download the converted file [here](sandbox:/Iris.csv)." 18 | ), # noqa: E501 19 | AIMessage(content="The dataset has been successfully converted to CSV format."), 20 | HumanMessagePromptTemplate.from_template("{input_response}"), 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/prompts/system_message.py: -------------------------------------------------------------------------------- 1 | from langchain_core.messages import SystemMessage 2 | 3 | system_message = SystemMessage( 4 | content=""" 5 | You are using an AI Assistant capable of tasks related to data science, data analysis, data visualization, and file manipulation. Capabilities include: 6 | 7 | - Image Manipulation: Zoom, crop, color grade, enhance resolution, format conversion. 8 | - QR Code Generation: Create QR codes. 9 | - Project Management: Generate Gantt charts, map project steps. 10 | - Study Scheduling: Design optimized exam study schedules. 11 | - File Conversion: Convert files, e.g., PDF to text, video to audio. 12 | - Mathematical Computation: Solve equations, produce graphs. 13 | - Document Analysis: Summarize, extract information from large documents. 14 | - Data Visualization: Analyze datasets, identify trends, create graphs. 15 | - Geolocation Visualization: Show maps to visualize specific trends or occurrences. 16 | - Code Analysis and Creation: Critique and generate code. 17 | 18 | The Assistant operates within a sandboxed Jupyter kernel environment. Pre-installed Python packages include numpy, pandas, matplotlib, seaborn, scikit-learn, yfinance, scipy, statsmodels, sympy, bokeh, plotly, dash, and networkx. Other packages will be installed as required. 19 | 20 | To use, input your task-specific code. Review and retry code in case of error. After two unsuccessful attempts, an error message will be returned. 21 | 22 | The Assistant is designed for specific tasks and may not function as expected if used incorrectly. 23 | """ # noqa: E501 24 | ) 25 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/schema.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Any 3 | 4 | from codeboxapi.schema import CodeBoxStatus 5 | from langchain_core.messages import AIMessage, HumanMessage 6 | from langchain_core.pydantic_v1 import BaseModel 7 | 8 | 9 | class File(BaseModel): 10 | name: str 11 | content: bytes 12 | 13 | @classmethod 14 | def from_path(cls, path: str) -> "File": 15 | if not path.startswith("/"): 16 | path = f"./{path}" 17 | with open(path, "rb") as f: 18 | path = path.split("/")[-1] 19 | return cls(name=path, content=f.read()) 20 | 21 | @classmethod 22 | async def afrom_path(cls, path: str) -> "File": 23 | return await asyncio.to_thread(cls.from_path, path) 24 | 25 | @classmethod 26 | def from_url(cls, url: str) -> "File": 27 | import requests # type: ignore 28 | 29 | r = requests.get(url) 30 | return cls(name=url.split("/")[-1], content=r.content) 31 | 32 | @classmethod 33 | async def afrom_url(cls, url: str) -> "File": 34 | import aiohttp # type: ignore 35 | 36 | async with aiohttp.ClientSession() as session: 37 | async with session.get(url) as r: 38 | return cls(name=url.split("/")[-1], content=await r.read()) 39 | 40 | def save(self, path: str) -> None: 41 | if not path.startswith("/"): 42 | path = f"./{path}" 43 | with open(path, "wb") as f: 44 | f.write(self.content) 45 | 46 | async def asave(self, path: str) -> None: 47 | await asyncio.to_thread(self.save, path) 48 | 49 | def get_image(self) -> Any: 50 | try: 51 | from PIL import Image # type: ignore 52 | except ImportError: 53 | print( 54 | "Please install it with " 55 | "`pip install 'codeinterpreterapi[image_support]'`" 56 | " to display images." 57 | ) 58 | exit(1) 59 | 60 | from io import BytesIO 61 | 62 | img_io = BytesIO(self.content) 63 | img = Image.open(img_io) 64 | 65 | # Convert image to RGB if it's not 66 | if img.mode not in ("RGB", "L"): # L is for grayscale images 67 | img = img.convert("RGB") # type: ignore 68 | 69 | return img 70 | 71 | def show_image(self) -> None: 72 | img = self.get_image() 73 | # Display the image 74 | try: 75 | # Try to get the IPython shell if available. 76 | shell = get_ipython().__class__.__name__ # type: ignore 77 | # If the shell is in a Jupyter notebook or similar. 78 | if shell == "ZMQInteractiveShell" or shell == "Shell": 79 | from IPython.display import display # type: ignore 80 | 81 | display(img) # type: ignore 82 | else: 83 | img.show() 84 | except NameError: 85 | img.show() 86 | 87 | def __str__(self) -> str: 88 | return self.name 89 | 90 | def __repr__(self) -> str: 91 | return f"File(name={self.name})" 92 | 93 | 94 | class CodeInput(BaseModel): 95 | code: str 96 | 97 | 98 | class FileInput(BaseModel): 99 | filename: str 100 | 101 | 102 | class UserRequest(HumanMessage): 103 | files: list[File] = [] 104 | 105 | def __str__(self) -> str: 106 | return str(self.content) 107 | 108 | def __repr__(self) -> str: 109 | return f"UserRequest(content={self.content}, files={self.files})" 110 | 111 | 112 | class CodeInterpreterResponse(AIMessage): 113 | """ 114 | Response from the code interpreter agent. 115 | 116 | files: list of files to be sent to the user (File ) 117 | code_log: list[tuple[str, str]] = [] 118 | """ 119 | 120 | files: list[File] = [] 121 | code_log: list[tuple[str, str]] = [] 122 | 123 | def show(self) -> None: 124 | print("AI: ", self.content) 125 | for file in self.files: 126 | print("File: ", file.name) 127 | file.show_image() 128 | 129 | def __str__(self) -> str: 130 | return str(self.content) 131 | 132 | def __repr__(self) -> str: 133 | return f"CodeInterpreterResponse(content={self.content}, files={self.files})" 134 | 135 | 136 | class SessionStatus(CodeBoxStatus): 137 | @classmethod 138 | def from_codebox_status(cls, cbs: CodeBoxStatus) -> "SessionStatus": 139 | return cls(status=cbs.status) 140 | 141 | def __repr__(self) -> str: 142 | return f"" 143 | -------------------------------------------------------------------------------- /src/codeinterpreterapi/session.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import re 3 | import traceback 4 | from io import BytesIO 5 | from types import TracebackType 6 | from typing import Any, Optional, Type 7 | from uuid import UUID, uuid4 8 | 9 | from codeboxapi import CodeBox # type: ignore 10 | from codeboxapi.schema import CodeBoxOutput # type: ignore 11 | from langchain.agents import ( 12 | AgentExecutor, 13 | BaseSingleActionAgent, 14 | ConversationalAgent, 15 | ConversationalChatAgent, 16 | ) 17 | from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent 18 | from langchain.callbacks.base import Callbacks 19 | from langchain.chat_models.base import BaseChatModel 20 | from langchain.memory.buffer import ConversationBufferMemory 21 | from langchain_community.chat_message_histories.in_memory import ChatMessageHistory 22 | from langchain_community.chat_message_histories.postgres import ( 23 | PostgresChatMessageHistory, 24 | ) 25 | from langchain_community.chat_message_histories.redis import RedisChatMessageHistory 26 | from langchain_core.chat_history import BaseChatMessageHistory 27 | from langchain_core.language_models import BaseLanguageModel 28 | from langchain_core.prompts.chat import MessagesPlaceholder 29 | from langchain_core.tools import BaseTool, StructuredTool 30 | from langchain_openai import AzureChatOpenAI, ChatOpenAI 31 | 32 | from codeinterpreterapi.chains import ( 33 | aget_file_modifications, 34 | aremove_download_link, 35 | get_file_modifications, 36 | remove_download_link, 37 | ) 38 | from codeinterpreterapi.chat_history import CodeBoxChatMessageHistory 39 | from codeinterpreterapi.config import settings 40 | from codeinterpreterapi.schema import ( 41 | CodeInput, 42 | CodeInterpreterResponse, 43 | File, 44 | SessionStatus, 45 | UserRequest, 46 | ) 47 | 48 | 49 | def _handle_deprecated_kwargs(kwargs: dict) -> None: 50 | settings.MODEL = kwargs.get("model", settings.MODEL) 51 | settings.MAX_RETRY = kwargs.get("max_retry", settings.MAX_RETRY) 52 | settings.TEMPERATURE = kwargs.get("temperature", settings.TEMPERATURE) 53 | settings.OPENAI_API_KEY = kwargs.get("openai_api_key", settings.OPENAI_API_KEY) 54 | settings.SYSTEM_MESSAGE = kwargs.get("system_message", settings.SYSTEM_MESSAGE) 55 | settings.MAX_ITERATIONS = kwargs.get("max_iterations", settings.MAX_ITERATIONS) 56 | 57 | 58 | class CodeInterpreterSession: 59 | def __init__( 60 | self, 61 | llm: Optional[BaseLanguageModel] = None, 62 | additional_tools: list[BaseTool] = [], 63 | callbacks: Callbacks = None, 64 | **kwargs: Any, 65 | ) -> None: 66 | _handle_deprecated_kwargs(kwargs) 67 | self.codebox = CodeBox(requirements=settings.CUSTOM_PACKAGES) 68 | self.verbose = kwargs.get("verbose", settings.DEBUG) 69 | self.tools: list[BaseTool] = self._tools(additional_tools) 70 | self.llm: BaseLanguageModel = llm or self._choose_llm() 71 | self.callbacks = callbacks 72 | self.agent_executor: Optional[AgentExecutor] = None 73 | self.input_files: list[File] = [] 74 | self.output_files: list[File] = [] 75 | self.code_log: list[tuple[str, str]] = [] 76 | 77 | @classmethod 78 | def from_id(cls, session_id: UUID, **kwargs: Any) -> "CodeInterpreterSession": 79 | session = cls(**kwargs) 80 | session.codebox = CodeBox.from_id(session_id) 81 | session.agent_executor = session._agent_executor() 82 | return session 83 | 84 | @property 85 | def session_id(self) -> Optional[UUID]: 86 | return self.codebox.session_id 87 | 88 | def start(self) -> SessionStatus: 89 | status = SessionStatus.from_codebox_status(self.codebox.start()) 90 | self.agent_executor = self._agent_executor() 91 | self.codebox.run( 92 | f"!pip install -q {' '.join(settings.CUSTOM_PACKAGES)}", 93 | ) 94 | return status 95 | 96 | async def astart(self) -> SessionStatus: 97 | status = SessionStatus.from_codebox_status(await self.codebox.astart()) 98 | self.agent_executor = self._agent_executor() 99 | await self.codebox.arun( 100 | f"!pip install -q {' '.join(settings.CUSTOM_PACKAGES)}", 101 | ) 102 | return status 103 | 104 | def _tools(self, additional_tools: list[BaseTool]) -> list[BaseTool]: 105 | return additional_tools + [ 106 | StructuredTool( 107 | name="python", 108 | description="Input a string of code to a ipython interpreter. " 109 | "Write the entire code in a single string. This string can " 110 | "be really long, so you can use the `;` character to split lines. " 111 | "Start your code on the same line as the opening quote. " 112 | "Do not start your code with a line break. " 113 | "For example, do 'import numpy', not '\\nimport numpy'." 114 | "Variables are preserved between runs. " 115 | + ( 116 | ( 117 | "You can use all default python packages " 118 | f"specifically also these: {settings.CUSTOM_PACKAGES}" 119 | ) 120 | if settings.CUSTOM_PACKAGES 121 | else "" 122 | ), # TODO: or include this in the system message 123 | func=self._run_handler, 124 | coroutine=self._arun_handler, 125 | args_schema=CodeInput, # type: ignore 126 | ), 127 | ] 128 | 129 | def _choose_llm(self) -> BaseChatModel: 130 | if ( 131 | settings.AZURE_OPENAI_API_KEY 132 | and settings.AZURE_API_BASE 133 | and settings.AZURE_API_VERSION 134 | and settings.AZURE_DEPLOYMENT_NAME 135 | ): 136 | self.log("Using Azure Chat OpenAI") 137 | return AzureChatOpenAI( 138 | temperature=0.03, 139 | base_url=settings.AZURE_API_BASE, 140 | api_version=settings.AZURE_API_VERSION, 141 | azure_deployment=settings.AZURE_DEPLOYMENT_NAME, 142 | api_key=settings.AZURE_OPENAI_API_KEY, # type: ignore 143 | max_retries=settings.MAX_RETRY, 144 | timeout=settings.REQUEST_TIMEOUT, 145 | ) # type: ignore 146 | if settings.OPENAI_API_KEY: 147 | from langchain_openai import ChatOpenAI 148 | 149 | return ChatOpenAI( 150 | model=settings.MODEL, 151 | api_key=settings.OPENAI_API_KEY, # type: ignore 152 | timeout=settings.REQUEST_TIMEOUT, 153 | temperature=settings.TEMPERATURE, 154 | max_retries=settings.MAX_RETRY, 155 | ) # type: ignore 156 | if settings.ANTHROPIC_API_KEY: 157 | from langchain_anthropic import ChatAnthropic # type: ignore 158 | 159 | if "claude" not in settings.MODEL: 160 | print("Please set the claude model in the settings.") 161 | self.log("Using Chat Anthropic") 162 | return ChatAnthropic( 163 | model_name=settings.MODEL, 164 | temperature=settings.TEMPERATURE, 165 | anthropic_api_key=settings.ANTHROPIC_API_KEY, 166 | ) 167 | raise ValueError("Please set the API key for the LLM you want to use.") 168 | 169 | def _choose_agent(self) -> BaseSingleActionAgent: 170 | return ( 171 | OpenAIFunctionsAgent.from_llm_and_tools( 172 | llm=self.llm, 173 | tools=self.tools, 174 | system_message=settings.SYSTEM_MESSAGE, 175 | extra_prompt_messages=[ 176 | MessagesPlaceholder(variable_name="chat_history") 177 | ], 178 | ) 179 | if isinstance(self.llm, ChatOpenAI) or isinstance(self.llm, AzureChatOpenAI) 180 | else ConversationalChatAgent.from_llm_and_tools( 181 | llm=self.llm, 182 | tools=self.tools, 183 | system_message=settings.SYSTEM_MESSAGE.content.__str__(), 184 | ) 185 | if isinstance(self.llm, BaseChatModel) 186 | else ConversationalAgent.from_llm_and_tools( 187 | llm=self.llm, 188 | tools=self.tools, 189 | prefix=settings.SYSTEM_MESSAGE.content.__str__(), 190 | ) 191 | ) 192 | 193 | def _history_backend(self) -> BaseChatMessageHistory: 194 | return ( 195 | CodeBoxChatMessageHistory(codebox=self.codebox) # type: ignore 196 | if settings.HISTORY_BACKEND == "codebox" 197 | else RedisChatMessageHistory( 198 | session_id=str(self.session_id), 199 | url=settings.REDIS_URL, 200 | ) 201 | if settings.HISTORY_BACKEND == "redis" 202 | else PostgresChatMessageHistory( 203 | session_id=str(self.session_id), 204 | connection_string=settings.POSTGRES_URL, 205 | ) 206 | if settings.HISTORY_BACKEND == "postgres" 207 | else ChatMessageHistory() 208 | ) 209 | 210 | def _agent_executor(self) -> AgentExecutor: 211 | return AgentExecutor.from_agent_and_tools( 212 | agent=self._choose_agent(), 213 | max_iterations=settings.MAX_ITERATIONS, 214 | tools=self.tools, 215 | verbose=self.verbose, 216 | memory=ConversationBufferMemory( 217 | memory_key="chat_history", 218 | return_messages=True, 219 | chat_memory=self._history_backend(), 220 | ), 221 | callbacks=self.callbacks, 222 | ) 223 | 224 | def show_code(self, code: str) -> None: 225 | if self.verbose: 226 | print(code) 227 | 228 | async def ashow_code(self, code: str) -> None: 229 | """Callback function to show code to the user.""" 230 | if self.verbose: 231 | print(code) 232 | 233 | def _run_handler(self, code: str) -> str: 234 | """Run code in container and send the output to the user""" 235 | self.show_code(code) 236 | output: CodeBoxOutput = self.codebox.run(code) 237 | self.code_log.append((code, output.content)) 238 | 239 | if not isinstance(output.content, str): 240 | raise TypeError("Expected output.content to be a string.") 241 | 242 | if output.type == "image/png": 243 | filename = f"image-{uuid4()}.png" 244 | file_buffer = BytesIO(base64.b64decode(output.content)) 245 | file_buffer.name = filename 246 | self.output_files.append(File(name=filename, content=file_buffer.read())) 247 | return f"Image {filename} got send to the user." 248 | 249 | elif output.type == "error": 250 | if "ModuleNotFoundError" in output.content: 251 | if package := re.search( 252 | r"ModuleNotFoundError: No module named '(.*)'", 253 | output.content, 254 | ): 255 | self.codebox.install(package.group(1)) 256 | return ( 257 | f"{package.group(1)} was missing but " 258 | "got installed now. Please try again." 259 | ) 260 | else: 261 | # TODO: pre-analyze error to optimize next code generation 262 | pass 263 | if self.verbose: 264 | print("Error:", output.content) 265 | 266 | elif modifications := get_file_modifications(code, self.llm): # type: ignore 267 | for filename in modifications: 268 | if filename in [file.name for file in self.input_files]: 269 | continue 270 | fileb = self.codebox.download(filename) 271 | if not fileb.content: 272 | continue 273 | file_buffer = BytesIO(fileb.content) 274 | file_buffer.name = filename 275 | self.output_files.append( 276 | File(name=filename, content=file_buffer.read()) 277 | ) 278 | 279 | return output.content 280 | 281 | async def _arun_handler(self, code: str) -> str: 282 | """Run code in container and send the output to the user""" 283 | await self.ashow_code(code) 284 | output: CodeBoxOutput = await self.codebox.arun(code) 285 | self.code_log.append((code, output.content)) 286 | 287 | if not isinstance(output.content, str): 288 | raise TypeError("Expected output.content to be a string.") 289 | 290 | if output.type == "image/png": 291 | filename = f"image-{uuid4()}.png" 292 | file_buffer = BytesIO(base64.b64decode(output.content)) 293 | file_buffer.name = filename 294 | self.output_files.append(File(name=filename, content=file_buffer.read())) 295 | return f"Image {filename} got send to the user." 296 | 297 | elif output.type == "error": 298 | if "ModuleNotFoundError" in output.content: 299 | if package := re.search( 300 | r"ModuleNotFoundError: No module named '(.*)'", 301 | output.content, 302 | ): 303 | await self.codebox.ainstall(package.group(1)) 304 | return ( 305 | f"{package.group(1)} was missing but " 306 | "got installed now. Please try again." 307 | ) 308 | else: 309 | # TODO: pre-analyze error to optimize next code generation 310 | pass 311 | if self.verbose: 312 | print("Error:", output.content) 313 | 314 | elif modifications := await aget_file_modifications(code, self.llm): 315 | for filename in modifications: 316 | if filename in [file.name for file in self.input_files]: 317 | continue 318 | fileb = await self.codebox.adownload(filename) 319 | if not fileb.content: 320 | continue 321 | file_buffer = BytesIO(fileb.content) 322 | file_buffer.name = filename 323 | self.output_files.append( 324 | File(name=filename, content=file_buffer.read()) 325 | ) 326 | 327 | return output.content 328 | 329 | def _input_handler(self, request: UserRequest) -> None: 330 | """Callback function to handle user input.""" 331 | if not request.files: 332 | return 333 | if not request.content: 334 | request.content = ( 335 | "I uploaded, just text me back and confirm that you got the file(s)." 336 | ) 337 | assert isinstance(request.content, str), "TODO: implement image support" 338 | request.content += "\n**The user uploaded the following files: **\n" 339 | for file in request.files: 340 | self.input_files.append(file) 341 | request.content += f"[Attachment: {file.name}]\n" 342 | self.codebox.upload(file.name, file.content) 343 | request.content += "**File(s) are now available in the cwd. **\n" 344 | 345 | async def _ainput_handler(self, request: UserRequest) -> None: 346 | # TODO: variables as context to the agent 347 | # TODO: current files as context to the agent 348 | if not request.files: 349 | return 350 | if not request.content: 351 | request.content = ( 352 | "I uploaded, just text me back and confirm that you got the file(s)." 353 | ) 354 | assert isinstance(request.content, str), "TODO: implement image support" 355 | request.content += "\n**The user uploaded the following files: **\n" 356 | for file in request.files: 357 | self.input_files.append(file) 358 | request.content += f"[Attachment: {file.name}]\n" 359 | await self.codebox.aupload(file.name, file.content) 360 | request.content += "**File(s) are now available in the cwd. **\n" 361 | 362 | def _output_handler(self, final_response: str) -> CodeInterpreterResponse: 363 | """Embed images in the response""" 364 | for file in self.output_files: 365 | if str(file.name) in final_response: 366 | # rm ![Any](file.name) from the response 367 | final_response = re.sub(r"\n\n!\[.*\]\(.*\)", "", final_response) 368 | 369 | if self.output_files and re.search(r"\n\[.*\]\(.*\)", final_response): 370 | try: 371 | final_response = remove_download_link(final_response, self.llm) 372 | except Exception as e: 373 | if self.verbose: 374 | print("Error while removing download links:", e) 375 | 376 | output_files = self.output_files 377 | code_log = self.code_log 378 | self.output_files = [] 379 | self.code_log = [] 380 | 381 | return CodeInterpreterResponse( 382 | content=final_response, files=output_files, code_log=code_log 383 | ) 384 | 385 | async def _aoutput_handler(self, final_response: str) -> CodeInterpreterResponse: 386 | """Embed images in the response""" 387 | for file in self.output_files: 388 | if str(file.name) in final_response: 389 | # rm ![Any](file.name) from the response 390 | final_response = re.sub(r"\n\n!\[.*\]\(.*\)", "", final_response) 391 | 392 | if self.output_files and re.search(r"\n\[.*\]\(.*\)", final_response): 393 | try: 394 | final_response = await aremove_download_link(final_response, self.llm) 395 | except Exception as e: 396 | if self.verbose: 397 | print("Error while removing download links:", e) 398 | 399 | output_files = self.output_files 400 | code_log = self.code_log 401 | self.output_files = [] 402 | self.code_log = [] 403 | 404 | return CodeInterpreterResponse( 405 | content=final_response, files=output_files, code_log=code_log 406 | ) 407 | 408 | def generate_response_sync( 409 | self, 410 | user_msg: str, 411 | files: list[File] = [], 412 | ) -> CodeInterpreterResponse: 413 | print("DEPRECATION WARNING: Use generate_response for sync generation.\n") 414 | return self.generate_response( 415 | user_msg=user_msg, 416 | files=files, 417 | ) 418 | 419 | def generate_response( 420 | self, 421 | user_msg: str, 422 | files: list[File] = [], 423 | ) -> CodeInterpreterResponse: 424 | """Generate a Code Interpreter response based on the user's input.""" 425 | user_request = UserRequest(content=user_msg, files=files) 426 | try: 427 | self._input_handler(user_request) 428 | assert self.agent_executor, "Session not initialized." 429 | response = self.agent_executor.invoke({"input": user_request.content}) 430 | return self._output_handler(response["output"]) 431 | except Exception as e: 432 | if self.verbose: 433 | traceback.print_exc() 434 | if settings.DETAILED_ERROR: 435 | return CodeInterpreterResponse( 436 | content="Error in CodeInterpreterSession: " 437 | f"{e.__class__.__name__} - {e}" 438 | ) 439 | else: 440 | return CodeInterpreterResponse( 441 | content="Sorry, something went while generating your response." 442 | "Please try again or restart the session." 443 | ) 444 | 445 | async def agenerate_response( 446 | self, 447 | user_msg: str, 448 | files: list[File] = [], 449 | ) -> CodeInterpreterResponse: 450 | """Generate a Code Interpreter response based on the user's input.""" 451 | user_request = UserRequest(content=user_msg, files=files) 452 | try: 453 | await self._ainput_handler(user_request) 454 | assert self.agent_executor, "Session not initialized." 455 | response = await self.agent_executor.ainvoke( 456 | {"input": user_request.content} 457 | ) 458 | return await self._aoutput_handler(response["output"]) 459 | except Exception as e: 460 | if self.verbose: 461 | traceback.print_exc() 462 | if settings.DETAILED_ERROR: 463 | return CodeInterpreterResponse( 464 | content="Error in CodeInterpreterSession: " 465 | f"{e.__class__.__name__} - {e}" 466 | ) 467 | else: 468 | return CodeInterpreterResponse( 469 | content="Sorry, something went while generating your response." 470 | "Please try again or restart the session." 471 | ) 472 | 473 | def is_running(self) -> bool: 474 | return self.codebox.status() == "running" 475 | 476 | async def ais_running(self) -> bool: 477 | return await self.codebox.astatus() == "running" 478 | 479 | def log(self, msg: str) -> None: 480 | if self.verbose: 481 | print(msg) 482 | 483 | def stop(self) -> SessionStatus: 484 | return SessionStatus.from_codebox_status(self.codebox.stop()) 485 | 486 | async def astop(self) -> SessionStatus: 487 | return SessionStatus.from_codebox_status(await self.codebox.astop()) 488 | 489 | def __enter__(self) -> "CodeInterpreterSession": 490 | self.start() 491 | return self 492 | 493 | def __exit__( 494 | self, 495 | exc_type: Optional[Type[BaseException]], 496 | exc_value: Optional[BaseException], 497 | traceback: Optional[TracebackType], 498 | ) -> None: 499 | self.stop() 500 | 501 | async def __aenter__(self) -> "CodeInterpreterSession": 502 | await self.astart() 503 | return self 504 | 505 | async def __aexit__( 506 | self, 507 | exc_type: Optional[Type[BaseException]], 508 | exc_value: Optional[BaseException], 509 | traceback: Optional[TracebackType], 510 | ) -> None: 511 | await self.astop() 512 | -------------------------------------------------------------------------------- /tests/chain_test.py: -------------------------------------------------------------------------------- 1 | from asyncio import run as _await 2 | 3 | from codeinterpreterapi.chains import ( 4 | aget_file_modifications, 5 | aremove_download_link, 6 | get_file_modifications, 7 | remove_download_link, 8 | ) 9 | from langchain_openai import ChatOpenAI 10 | 11 | llm = ChatOpenAI(model="gpt-3.5-turbo") 12 | 13 | remove_download_link_example = ( 14 | "I have created the plot to your dataset.\n\n" 15 | "Link to the file [here](sandbox:/plot.png)." 16 | ) 17 | 18 | base_code = """ 19 | import matplotlib.pyplot as plt 20 | 21 | x = list(range(1, 11)) 22 | y = [29, 39, 23, 32, 4, 43, 43, 23, 43, 77] 23 | 24 | plt.plot(x, y, marker='o') 25 | plt.xlabel('Index') 26 | plt.ylabel('Value') 27 | plt.title('Data Plot') 28 | """ 29 | code_with_mod = base_code + "\nplt.savefig('plot.png')" 30 | 31 | code_no_mod = base_code + "\nplt.show()" 32 | 33 | 34 | def test_remove_download_link() -> None: 35 | assert ( 36 | remove_download_link(remove_download_link_example, llm=llm).strip() 37 | == "I have created the plot to your dataset." 38 | ) 39 | 40 | 41 | def test_remove_download_link_async() -> None: 42 | assert ( 43 | _await(aremove_download_link(remove_download_link_example, llm=llm)) 44 | ).strip() == "I have created the plot to your dataset." 45 | 46 | 47 | def test_get_file_modifications() -> None: 48 | assert get_file_modifications(code_with_mod, llm=llm) == ["plot.png"] 49 | assert get_file_modifications(code_no_mod, llm=llm) == [] 50 | 51 | 52 | def test_get_file_modifications_async() -> None: 53 | assert _await(aget_file_modifications(code_with_mod, llm=llm)) == ["plot.png"] 54 | assert _await(aget_file_modifications(code_no_mod, llm=llm)) == [] 55 | 56 | 57 | if __name__ == "__main__": 58 | test_remove_download_link() 59 | test_remove_download_link_async() 60 | test_get_file_modifications() 61 | test_get_file_modifications_async() 62 | -------------------------------------------------------------------------------- /tests/general_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from codeinterpreterapi import CodeInterpreterSession, File 4 | 5 | 6 | def test_codebox() -> None: 7 | session = CodeInterpreterSession() 8 | assert run_sync(session), "Failed to run sync CodeInterpreterSession remotely" 9 | assert asyncio.run( 10 | run_async(session) 11 | ), "Failed to run async CodeInterpreterSession remotely" 12 | 13 | 14 | def test_localbox() -> None: 15 | session = CodeInterpreterSession(local=True) 16 | assert run_sync(session), "Failed to run sync CodeInterpreterSession locally" 17 | assert asyncio.run( 18 | run_async(session) 19 | ), "Failed to run async CodeInterpreterSession locally" 20 | 21 | 22 | def run_sync(session: CodeInterpreterSession) -> bool: 23 | try: 24 | assert session.start() == "started" 25 | 26 | assert ( 27 | "3.1" 28 | in session.generate_response( 29 | "Compute pi using Monte Carlo simulation in " 30 | "Python and show me the result." 31 | ).content 32 | ) 33 | 34 | assert ( 35 | ".xlsx" 36 | in session.generate_response( 37 | "Convert this csv file to excel.", 38 | files=[File.from_path("examples/assets/iris.csv")], 39 | ) 40 | .files[0] 41 | .name 42 | ) 43 | 44 | assert ( 45 | ".png" 46 | in session.generate_response( 47 | "Plot the current stock price of Tesla.", 48 | ) 49 | .files[0] 50 | .name 51 | ) 52 | 53 | finally: 54 | assert session.stop() == "stopped" 55 | 56 | return True 57 | 58 | 59 | async def run_async(session: CodeInterpreterSession) -> bool: 60 | try: 61 | assert (await session.astart()) == "started" 62 | 63 | assert ( 64 | "3.1" 65 | in ( 66 | await session.agenerate_response( 67 | "Compute pi using Monte Carlo simulation in " 68 | "Python and show me the result." 69 | ) 70 | ).content 71 | ) 72 | 73 | assert ( 74 | ".xlsx" 75 | in ( 76 | await session.agenerate_response( 77 | "Convert this csv file to excel.", 78 | files=[File.from_path("examples/assets/iris.csv")], 79 | ) 80 | ) 81 | .files[0] 82 | .name 83 | ) 84 | 85 | assert ( 86 | ".png" 87 | in ( 88 | await session.agenerate_response( 89 | "Plot the current stock price of Tesla.", 90 | ) 91 | ) 92 | .files[0] 93 | .name 94 | ) 95 | 96 | finally: 97 | assert await session.astop() == "stopped" 98 | 99 | return True 100 | 101 | 102 | if __name__ == "__main__": 103 | test_codebox() 104 | test_localbox() 105 | -------------------------------------------------------------------------------- /tests/run_examples.py: -------------------------------------------------------------------------------- 1 | # TODO: implement test that runs all examples and checks that they don't crash 2 | --------------------------------------------------------------------------------