Merge branch 'main' into fix/editor-write-prompt

pull/16/head
hanchchch 1 year ago
commit c0ef2c5ef1

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

@ -0,0 +1,11 @@
## :pushpin: Type of PR
<!-- _Bugfix, Feature, Code style update (formatting, local variables), Refactoring (no functional changes, no api changes), Build related changes, CI related changes, Other..._ -->
## :recycle: Current situation
<!-- _Describe the current situation. Explain current problems, if there are any. Be as descriptive as possible (e.g., including examples or code snippets)._ -->
## :bulb: Proposed solution
<!-- _Describe the proposed solution and changes. How does it affect the project? How does it affect the internal structure (e.g., refactorings)?_ -->

2
.gitignore vendored

@ -8,6 +8,6 @@ audio/
video/
dataframe/
static/*
static/generated
playground/

@ -0,0 +1,137 @@
# Contributing to CONTRIBUTING.md
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Styleguides](#styleguides)
- [Commit Messages](#commit-messages)
- [Join The Project Team](#join-the-project-team)
## Code of Conduct
This project and everyone participating in it is governed by the
[CONTRIBUTING.md Code of Conduct](blob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to <>.
## I Have a Question
> If you want to ask a question, we assume that you have read the available [Documentation]().
Before you ask a question, it is best to search for existing [Issues](/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
We will then take care of the issue as soon as possible.
## I Want To Contribute
> ### Legal Notice
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
### Reporting Bugs
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](issues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>.
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for CONTRIBUTING.md, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
- **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
### Your First Code Contribution
### Improving The Documentation
## Styleguides
### Commit Messages
## Join The Project Team
## Attribution
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Corca
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -12,9 +12,14 @@ https://user-images.githubusercontent.com/51526347/230061897-b3479405-8ebd-45ab-
EVAL Making a UI for itself
### https://github.com/eval-bot
#### [EVAL-BOT](https://github.com/eval-bot)
EVAL's self-managed github account. EVAL does everything except for signup and bio setting.
### Examples
[Here](examples/) is an example.
### EVAL's FEATURE
1. **Multimodal Conversation**
@ -75,10 +80,11 @@ Manatory envs are required in order to serve EVAL.
Each optional env has default value, so you don't need to set unless you want to change it.
- `PORT` - port (default: 8000)
- `EVAL_PORT` - port (default: 8000)
- `SERVER` - server address (default: http://localhost:8000)
- `LOG_LEVEL` - INFO | DEBUG (default: INFO)
- `BOT_NAME` - give it a name! (default: Orca)
- `MODEL_NAME` - model name for GPT (default: gpt-4)
**For More Tools**
@ -105,23 +111,32 @@ Some tools requires environment variables. Set envs depend on which tools you wa
### 3. Send request to EVAL
- `POST /command`
- Use the Web GUI to use EVAL in ease
- Go to `http://localhost:8000` in your browser
<img src="assets/gui.png" />
- Or you can manually send request to EVAL with APIs.
- `POST /api/execute`
- `session` - session id
- `files` - urls of file inputs
- `prompt` - prompt
- `key` - session id
- `files` - urls of file inputs
- `query` - prompt
- examples
- You can send request to EVAL with `curl` or `httpie`.
```bash
curl -X POST -H "Content-Type: application/json" -d '{"session": "sessionid", "files": [], "prompt": "Hi there!"}' http://localhost:8000/api/execute
```
```bash
curl -X POST -H "Content-Type: application/json" -d '{"key": "sessionid", "files": ["https://example.com/image.png"], "query": "Hi there!"}' http://localhost:8000/command
```
```bash
http POST http://localhost:8000/api/execute session=sessionid files:='[]' prompt="Hi there!"
```
```bash
http POST http://localhost:8000/command key=sessionid files:='["https://example.com/image.png"]' query="Hi there!"
```
- It also supports asynchronous execution. You can use `POST /api/execute/async` instead of `POST /api/execute`, with same body.
- We are planning to make a GUI for EVAL so you can use it without terminal.
- It returns `id` of the execution. Use `GET /api/execute/async/{id}` to get the result.
## TODO

@ -0,0 +1,62 @@
import os
import re
from pathlib import Path
from typing import Dict, List
from fastapi.templating import Jinja2Templates
from core.agents.manager import AgentManager
from core.handlers.base import BaseHandler, FileHandler, FileType
from core.handlers.dataframe import CsvToDataframe
from core.tools.base import BaseToolSet
from core.tools.cpu import ExitConversation, RequestsGet
from core.tools.editor import CodeEditor
from core.tools.terminal import Terminal
from core.upload import StaticUploader
from env import settings
BASE_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.chdir(BASE_DIR / settings["PLAYGROUND_DIR"])
toolsets: List[BaseToolSet] = [
Terminal(),
CodeEditor(),
RequestsGet(),
ExitConversation(),
]
handlers: Dict[FileType, BaseHandler] = {FileType.DATAFRAME: CsvToDataframe()}
if settings["USE_GPU"]:
import torch
from core.handlers.image import ImageCaptioning
from core.tools.gpu import (
ImageEditing,
InstructPix2Pix,
Text2Image,
VisualQuestionAnswering,
)
if torch.cuda.is_available():
toolsets.extend(
[
Text2Image("cuda"),
ImageEditing("cuda"),
InstructPix2Pix("cuda"),
VisualQuestionAnswering("cuda"),
]
)
handlers[FileType.IMAGE] = ImageCaptioning("cuda")
agent_manager = AgentManager.create(toolsets=toolsets)
file_handler = FileHandler(handlers=handlers, path=BASE_DIR)
templates = Jinja2Templates(directory=BASE_DIR / "api" / "templates")
uploader = StaticUploader.from_settings(
settings, path=BASE_DIR / "static", endpoint="static"
)
reload_dirs = [BASE_DIR / "core", BASE_DIR / "api"]

@ -1,87 +1,63 @@
import re
from typing import Dict, List, TypedDict
from multiprocessing import Process
from tempfile import NamedTemporaryFile
from typing import List, TypedDict
import uvicorn
from fastapi import FastAPI
from fastapi import FastAPI, Request, UploadFile
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from ansi import ANSI, Color, Style, dim_multiline
from core.agents.manager import AgentManager
from core.handlers.base import BaseHandler, FileHandler, FileType
from core.handlers.dataframe import CsvToDataframe
from core.prompts.error import ERROR_PROMPT
from core.tools.base import BaseToolSet
from core.tools.cpu import ExitConversation, RequestsGet
from core.tools.editor import CodeEditor
from core.tools.terminal import Terminal
from core.upload import StaticUploader
from api.container import agent_manager, file_handler, reload_dirs, templates, uploader
from api.worker import get_task_result, start_worker, task_execute
from env import settings
from logger import logger
app = FastAPI()
app.mount("/static", StaticFiles(directory=StaticUploader.STATIC_DIR), name="static")
uploader = StaticUploader.from_settings(settings)
app.mount("/static", StaticFiles(directory=uploader.path), name="static")
toolsets: List[BaseToolSet] = [
Terminal(),
CodeEditor(),
RequestsGet(),
ExitConversation(),
]
handlers: Dict[FileType, BaseHandler] = {FileType.DATAFRAME: CsvToDataframe()}
if settings["USE_GPU"]:
import torch
class ExecuteRequest(BaseModel):
session: str
prompt: str
files: List[str]
from core.handlers.image import ImageCaptioning
from core.tools.gpu import (
ImageEditing,
InstructPix2Pix,
Text2Image,
VisualQuestionAnswering,
)
if torch.cuda.is_available():
toolsets.extend(
[
Text2Image("cuda"),
ImageEditing("cuda"),
InstructPix2Pix("cuda"),
VisualQuestionAnswering("cuda"),
]
)
handlers[FileType.IMAGE] = ImageCaptioning("cuda")
class ExecuteResponse(TypedDict):
answer: str
files: List[str]
agent_manager = AgentManager.create(toolsets=toolsets)
file_handler = FileHandler(handlers=handlers)
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
class Request(BaseModel):
key: str
query: str
files: List[str]
@app.get("/dashboard", response_class=HTMLResponse)
async def dashboard(request: Request):
return templates.TemplateResponse("dashboard.html", {"request": request})
class Response(TypedDict):
response: str
files: List[str]
@app.post("/upload")
async def create_upload_file(files: List[UploadFile]):
urls = []
for file in files:
extension = "." + file.filename.split(".")[-1]
with NamedTemporaryFile(suffix=extension) as tmp_file:
tmp_file.write(file.file.read())
tmp_file.flush()
urls.append(uploader.upload(tmp_file.name))
return {"urls": urls}
@app.get("/")
async def index():
return {"message": f"Hello World. I'm {settings['BOT_NAME']}."}
@app.post("/command")
async def command(request: Request) -> Response:
query = request.query
@app.post("/api/execute")
async def execute(request: ExecuteRequest) -> ExecuteResponse:
query = request.prompt
files = request.files
session = request.key
session = request.session
executor = agent_manager.get_or_create_executor(session)
executor = agent_manager.create_executor(session)
promptedQuery = "\n".join([file_handler.handle(file) for file in files])
promptedQuery += query
@ -89,15 +65,64 @@ async def command(request: Request) -> Response:
try:
res = executor({"input": promptedQuery})
except Exception as e:
return {"response": str(e), "files": []}
return {"answer": str(e), "files": []}
files = re.findall("(image/\S*png)|(dataframe/\S*csv)", res["output"])
files = re.findall(r"\[file://\S*\]", res["output"])
files = [file[1:-1] for file in files]
return {
"response": res["output"],
"answer": res["output"],
"files": [uploader.upload(file) for file in files],
}
@app.post("/api/execute/async")
async def execute_async(request: ExecuteRequest):
query = request.prompt
files = request.files
session = request.session
promptedQuery = "\n".join([file_handler.handle(file) for file in files])
promptedQuery += query
execution = task_execute.delay(session, promptedQuery)
return {"id": execution.id}
@app.get("/api/execute/async/{execution_id}")
async def execute_async(execution_id: str):
execution = get_task_result(execution_id)
result = {}
if execution.status == "SUCCESS" and execution.result:
output = execution.result.get("output", "")
files = re.findall(r"\[file://\S*\]", output)
files = [file[1:-1] for file in files]
result = {
"answer": output,
"files": [uploader.upload(file) for file in files],
}
return {
"status": execution.status,
"info": execution.info,
"result": result,
}
def serve():
uvicorn.run("api.main:app", host="0.0.0.0", port=settings["PORT"])
p = Process(target=start_worker, args=[])
p.start()
uvicorn.run("api.main:app", host="0.0.0.0", port=settings["EVAL_PORT"])
def dev():
p = Process(target=start_worker, args=[])
p.start()
uvicorn.run(
"api.main:app",
host="0.0.0.0",
port=settings["EVAL_PORT"],
reload=True,
reload_dirs=reload_dirs,
)

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>EVAL {% block title %}{% endblock %}</title>
<link href="{{ url_for('static', path='/styles.css') }}" rel="stylesheet" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ"
crossorigin="anonymous"
/>
<link rel="icon" href="{{ url_for('static', path='/eval.png') }}" />
{% block head %} {% endblock %}
</head>
<body>
<header
class="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom"
>
<a
href="/"
class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-body-emphasis text-decoration-none"
>
<svg class="bi me-2" width="40" height="32">
<use xlink:href="#bootstrap"></use>
</svg>
<img
class="logo"
src="{{ url_for('static', path='/eval.png') }}"
alt="logo"
/>
<span class="fs-4">EVAL</span>
</a>
<ul class="nav nav-pills">
<li class="nav-item">
<a
href="https://github.com/corca-ai/EVAL"
class="nav-link"
target="_blank"
>Github</a
>
</li>
</ul>
</header>
<div class="d-flex flex-row">
<!-- <div
class="d-flex flex-column flex-shrink-0 p-3 bg-body-tertiary"
style="width: 240px; height: 80vh"
>
<ul id="nav-sidebar" class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="/" class="nav-link link-body-emphasis">
<svg class="bi pe-none me-2" width="16" height="16">
<use xlink:href="#home"></use>
</svg>
Execute
</a>
</li>
<li>
<a href="/dashboard" class="nav-link link-body-emphasis">
<svg class="bi pe-none me-2" width="16" height="16">
<use xlink:href="#speedometer2"></use>
</svg>
Dashboard
</a>
</li>
</ul>
<hr />
</div> -->
<div class="w-100">
<div class="container">{% block content %}{% endblock %}</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
crossorigin="anonymous"
></script>
<script src="{{ url_for('static', path='/layout.js') }}"></script>
</body>
</html>

@ -0,0 +1,3 @@
{% extends "base.html" %} {% block head %} {% endblock %} {% block content %}
<div class="p-3">Work in progress.</div>
{% endblock %}

@ -0,0 +1,53 @@
{% extends "base.html" %} {% block head %}
<script src="{{ url_for('static', path='/execute.js') }}"></script>
{% endblock %} {% block content %}
<div class="container-fluid pb-3">
<div class="d-grid gap-3" style="grid-template-columns: 2fr 3fr">
<div class="bg-body-tertiary border rounded-3 p-3">
<div>
<div class="mb-3">
<label for="prompt" class="form-label">Prompt</label>
<textarea id="prompt" name="prompt" class="form-control"></textarea>
</div>
<div class="mb-3">
<label for="files" class="form-label">Files</label>
<input id="files" type="file" class="form-control" />
</div>
<div class="mb-3">
<label for="session" class="form-label">Session</label>
<input id="session" name="session" class="form-control" />
</div>
<button
id="submit"
type="submit"
class="btn btn-primary"
onclick="submit(event)"
>
Submit
</button>
<button
id="submit-loader"
class="btn btn-primary disabled"
style="display: none"
>
Submit
<div class="spinner-border spinner-border-sm"></div>
</button>
</div>
</div>
<div class="bg-body-tertiary border rounded-3 p-2">
<div id="actions"></div>
<div class="card m-2">
<div class="card-body">
<div id="answer" class="card-text"></div>
<div id="response-files" class="card-text"></div>
</div>
</div>
</div>
</div>
</div>
<script>
setRandomSessionId();
</script>
</div>
{% endblock %}

@ -0,0 +1,42 @@
from celery import Celery
from celery.result import AsyncResult
from api.container import agent_manager
from env import settings
celery_app = Celery(__name__)
celery_app.conf.broker_url = settings["CELERY_BROKER_URL"]
celery_app.conf.result_backend = settings["CELERY_BROKER_URL"]
celery_app.conf.update(
task_track_started=True,
task_serializer="json",
accept_content=["json"], # Ignore other content
result_serializer="json",
enable_utc=True,
)
@celery_app.task(name="task_execute", bind=True)
def task_execute(self, session: str, prompt: str):
executor = agent_manager.create_executor(session, self)
response = executor({"input": prompt})
result = {"output": response["output"]}
previous = AsyncResult(self.request.id)
if previous and previous.info:
result.update(previous.info)
return result
def get_task_result(task_id):
return AsyncResult(task_id)
def start_worker():
celery_app.worker_main(
[
"worker",
"--loglevel=INFO",
]
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

@ -4,6 +4,7 @@ from core.tools.factory import ToolsFactory
from env import settings
from langchain.chat_models.base import BaseChatModel
from langchain.output_parsers.base import BaseOutputParser
from langchain.callbacks.base import BaseCallbackManager
from .chat_agent import ConversationalChatAgent
from .llm import ChatOpenAI
@ -17,8 +18,11 @@ class AgentBuilder:
self.global_tools: list = None
self.toolsets = toolsets
def build_llm(self):
self.llm = ChatOpenAI(temperature=0)
def build_llm(self, callback_manager: BaseCallbackManager = None):
self.llm = ChatOpenAI(
temperature=0, callback_manager=callback_manager, verbose=True
)
self.llm.check_access()
def build_parser(self):
self.parser = EvalOutputParser()
@ -39,6 +43,12 @@ class AgentBuilder:
*ToolsFactory.create_global_tools(self.toolsets),
]
def get_parser(self):
if self.parser is None:
raise ValueError("Parser is not initialized yet")
return self.parser
def get_global_tools(self):
if self.global_tools is None:
raise ValueError("Global tools are not initialized yet")

@ -2,22 +2,45 @@ from typing import Any, Dict, List, Optional, Union
from langchain.callbacks.base import BaseCallbackHandler
from langchain.schema import AgentAction, AgentFinish, LLMResult
from celery import Task
from ansi import ANSI, Color, Style, dim_multiline
from logger import logger
class EVALCallbackHandler(BaseCallbackHandler):
@property
def ignore_llm(self) -> bool:
return False
def set_parser(self, parser) -> None:
self.parser = parser
def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> None:
pass
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
pass
text = response.generations[0][0].text
parsed = self.parser.parse_all(text)
logger.info(ANSI("Plan").to(Color.blue().bright()) + ": " + parsed["plan"])
logger.info(ANSI("What I Did").to(Color.blue()) + ": " + parsed["what_i_did"])
logger.info(
ANSI("Action").to(Color.cyan())
+ ": "
+ ANSI(parsed["action"]).to(Style.bold())
)
logger.info(
ANSI("Input").to(Color.cyan())
+ ": "
+ dim_multiline(parsed["action_input"])
)
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
pass
logger.info(ANSI(f"on_llm_new_token {token}").to(Color.green(), Style.italic()))
def on_llm_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
@ -85,3 +108,90 @@ class EVALCallbackHandler(BaseCallbackHandler):
+ ": "
+ dim_multiline(finish.return_values.get("output", ""))
)
class ExecutionTracingCallbackHandler(BaseCallbackHandler):
def __init__(self, execution: Task):
self.execution = execution
self.index = 0
def set_parser(self, parser) -> None:
self.parser = parser
def on_llm_start(
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
) -> None:
pass
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
text = response.generations[0][0].text
parsed = self.parser.parse_all(text)
self.index += 1
parsed["index"] = self.index
self.execution.update_state(state="LLM_END", meta=parsed)
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
pass
def on_llm_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> None:
pass
def on_chain_start(
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
) -> None:
pass
def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None:
pass
def on_chain_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> None:
self.execution.update_state(state="CHAIN_ERROR", meta={"error": str(error)})
def on_tool_start(
self,
serialized: Dict[str, Any],
input_str: str,
**kwargs: Any,
) -> None:
pass
def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any:
pass
def on_tool_end(
self,
output: str,
observation_prefix: Optional[str] = None,
llm_prefix: Optional[str] = None,
**kwargs: Any,
) -> None:
previous = self.execution.AsyncResult(self.execution.request.id)
self.execution.update_state(
state="TOOL_END", meta={**previous.info, "observation": output}
)
def on_tool_error(
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
) -> None:
previous = self.execution.AsyncResult(self.execution.request.id)
self.execution.update_state(
state="TOOL_ERROR", meta={**previous.info, "error": str(error)}
)
def on_text(
self,
text: str,
color: Optional[str] = None,
end: str = "",
**kwargs: Optional[str],
) -> None:
pass
def on_agent_finish(
self, finish: AgentFinish, color: Optional[str] = None, **kwargs: Any
) -> None:
pass

@ -5,6 +5,8 @@ import logging
import sys
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple
import openai
from langchain.chat_models.base import BaseChatModel
from langchain.schema import (
AIMessage,
@ -26,6 +28,9 @@ from tenacity import (
wait_exponential,
)
from env import settings
from ansi import ANSI, Color, Style
def _create_retry_decorator(llm: ChatOpenAI) -> Callable[[Any], Any]:
import openai
@ -98,6 +103,23 @@ def _create_chat_result(response: Mapping[str, Any]) -> ChatResult:
return ChatResult(generations=generations)
class ModelNotFoundException(Exception):
"""Exception raised when the model is not found."""
def __init__(self, model_name: str):
self.model_name = model_name
super().__init__(
f"\n\nModel {ANSI(self.model_name).to(Color.red())} does not exist.\nMake sure if you have access to the model.\n"
+ f"You can set the model name with the environment variable {ANSI('MODEL_NAME').to(Style.bold())} on {ANSI('.env').to(Style.bold())}.\n"
+ "\nex) MODEL_NAME=gpt-4\n"
+ ANSI(
"\nLooks like you don't have access to gpt-4 yet. Try using `gpt-3.5-turbo`."
if self.model_name == "gpt-4"
else ""
).to(Style.italic())
)
class ChatOpenAI(BaseChatModel, BaseModel):
"""Wrapper around OpenAI Chat large language models.
@ -115,7 +137,7 @@ class ChatOpenAI(BaseChatModel, BaseModel):
"""
client: Any #: :meta private:
model_name: str = "gpt-4"
model_name: str = settings["MODEL_NAME"]
"""Model name to use."""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""Holds any model parameters valid for `create` call not explicitly specified."""
@ -134,6 +156,14 @@ class ChatOpenAI(BaseChatModel, BaseModel):
extra = Extra.ignore
def check_access(self) -> None:
"""Check that the user has access to the model."""
try:
openai.Engine.retrieve(self.model_name)
except openai.error.InvalidRequestError:
raise ModelNotFoundException(self.model_name)
@root_validator(pre=True)
def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Build extra kwargs from additional params that were passed in."""

@ -1,9 +1,9 @@
from typing import Dict
from typing import Dict, Optional
from celery import Task
from langchain.agents.agent import Agent, AgentExecutor
from langchain.agents.tools import BaseTool
from langchain.callbacks import set_handler
from langchain.agents.agent import AgentExecutor
from langchain.callbacks.base import CallbackManager
from langchain.callbacks import set_handler
from langchain.chains.conversation.memory import ConversationBufferMemory
from langchain.memory.chat_memory import BaseChatMemory
@ -11,68 +11,73 @@ from core.tools.base import BaseToolSet
from core.tools.factory import ToolsFactory
from .builder import AgentBuilder
from .callback import EVALCallbackHandler
from .callback import EVALCallbackHandler, ExecutionTracingCallbackHandler
callback_manager = CallbackManager([EVALCallbackHandler()])
set_handler(EVALCallbackHandler())
class AgentManager:
def __init__(
self,
agent: Agent,
global_tools: list[BaseTool],
toolsets: list[BaseToolSet] = [],
):
self.agent: Agent = agent
self.global_tools: list[BaseTool] = global_tools
self.toolsets: list[BaseToolSet] = toolsets
self.memories: Dict[str, BaseChatMemory] = {}
self.executors: Dict[str, AgentExecutor] = {}
def create_memory(self) -> BaseChatMemory:
return ConversationBufferMemory(memory_key="chat_history", return_messages=True)
def create_executor(self, session: str) -> AgentExecutor:
memory: BaseChatMemory = self.create_memory()
def get_or_create_memory(self, session: str) -> BaseChatMemory:
if not (session in self.memories):
self.memories[session] = self.create_memory()
return self.memories[session]
def create_executor(
self, session: str, execution: Optional[Task] = None
) -> AgentExecutor:
builder = AgentBuilder(self.toolsets)
builder.build_parser()
callbacks = []
eval_callback = EVALCallbackHandler()
eval_callback.set_parser(builder.get_parser())
callbacks.append(eval_callback)
if execution:
execution_callback = ExecutionTracingCallbackHandler(execution)
execution_callback.set_parser(builder.get_parser())
callbacks.append(execution_callback)
callback_manager = CallbackManager(callbacks)
builder.build_llm(callback_manager)
builder.build_global_tools()
memory: BaseChatMemory = self.get_or_create_memory(session)
tools = [
*self.global_tools,
*builder.get_global_tools(),
*ToolsFactory.create_per_session_tools(
self.toolsets,
get_session=lambda: (session, self.executors[session]),
),
]
for tool in tools:
tool.set_callback_manager(callback_manager)
tool.callback_manager = callback_manager
return AgentExecutor.from_agent_and_tools(
agent=self.agent,
executor = AgentExecutor.from_agent_and_tools(
agent=builder.get_agent(),
tools=tools,
memory=memory,
callback_manager=callback_manager,
verbose=True,
)
def remove_executor(self, session: str) -> None:
if session in self.executors:
del self.executors[session]
def get_or_create_executor(self, session: str) -> AgentExecutor:
if not (session in self.executors):
self.executors[session] = self.create_executor(session=session)
return self.executors[session]
self.executors[session] = executor
return executor
@staticmethod
def create(toolsets: list[BaseToolSet]) -> "AgentManager":
builder = AgentBuilder(toolsets)
builder.build_llm()
builder.build_parser()
builder.build_global_tools()
agent = builder.get_agent()
global_tools = builder.get_global_tools()
return AgentManager(
agent=agent,
global_tools=global_tools,
toolsets=toolsets,
)

@ -1,16 +1,31 @@
import re
import time
from typing import Dict
from langchain.output_parsers.base import BaseOutputParser
from ansi import ANSI, Color, Style
from core.agents.callback import dim_multiline
from core.prompts.input import EVAL_FORMAT_INSTRUCTIONS
from logger import logger
class EvalOutputParser(BaseOutputParser):
@staticmethod
def parse_all(text: str) -> Dict[str, str]:
regex = r"Action: (.*?)[\n]Plan:(.*)[\n]What I Did:(.*)[\n]Action Input: (.*)"
match = re.search(regex, text, re.DOTALL)
if not match:
raise Exception("parse error")
action = match.group(1).strip()
plan = match.group(2)
what_i_did = match.group(3)
action_input = match.group(4).strip(" ").strip('"')
return {
"action": action,
"plan": plan,
"what_i_did": what_i_did,
"action_input": action_input,
}
def get_format_instructions(self) -> str:
return EVAL_FORMAT_INSTRUCTIONS
@ -20,21 +35,9 @@ class EvalOutputParser(BaseOutputParser):
if not match:
raise Exception("parse error")
action = match.group(1).strip()
plan = match.group(2)
what_i_did = match.group(3)
action_input = match.group(4)
logger.info(ANSI("Plan").to(Color.blue().bright()) + ": " + plan)
logger.info(ANSI("What I Did").to(Color.blue()) + ": " + what_i_did)
time.sleep(1)
logger.info(
ANSI("Action").to(Color.cyan()) + ": " + ANSI(action).to(Style.bold())
)
time.sleep(1)
logger.info(ANSI("Input").to(Color.cyan()) + ": " + dim_multiline(action_input))
time.sleep(1)
return {"action": action, "action_input": action_input.strip(" ").strip('"')}
parsed = EvalOutputParser.parse_all(text)
return {"action": parsed["action"], "action_input": parsed["action_input"]}
def __str__(self):
return "EvalOutputParser"

@ -1,10 +1,14 @@
import os
import shutil
import uuid
from enum import Enum
from pathlib import Path
from typing import Dict
import requests
from env import settings
class FileType(Enum):
IMAGE = "image"
@ -51,8 +55,9 @@ class BaseHandler:
class FileHandler:
def __init__(self, handlers: Dict[FileType, BaseHandler]):
def __init__(self, handlers: Dict[FileType, BaseHandler], path: Path):
self.handlers = handlers
self.path = path
def register(self, filetype: FileType, handler: BaseHandler) -> "FileHandler":
self.handlers[filetype] = handler
@ -62,8 +67,9 @@ class FileHandler:
filetype = FileType.from_url(url)
data = requests.get(url).content
local_filename = os.path.join(
filetype.value, str(uuid.uuid4())[0:8] + filetype.to_extension()
"file", str(uuid.uuid4())[0:8] + filetype.to_extension()
)
os.makedirs(os.path.dirname(local_filename), exist_ok=True)
with open(local_filename, "wb") as f:
size = f.write(data)
print(f"Inputs: {url} ({size//1000}MB) => {local_filename}")
@ -71,6 +77,26 @@ class FileHandler:
def handle(self, url: str) -> str:
try:
return self.handlers[FileType.from_url(url)].handle(self.download(url))
if url.startswith(settings["SERVER"]):
local_filepath = url[len(settings["SERVER"]) + 1 :]
local_filename = Path("file") / local_filepath.split("/")[-1]
src = self.path / local_filepath
dst = self.path / settings["PLAYGROUND_DIR"] / local_filename
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy(src, dst)
else:
local_filename = self.download(url)
try:
handler = self.handlers[FileType.from_url(url)]
except KeyError:
if FileType.from_url(url) == FileType.IMAGE:
raise Exception(
f"No handler for {FileType.from_url(url)}. "
f"Please set USE_GPU to True in env/settings.py"
)
else:
raise Exception(f"No handler for {FileType.from_url(url)}")
handler.handle(local_filename)
except Exception as e:
return "Error: " + str(e)
raise e

@ -9,7 +9,7 @@ class CsvToDataframe(BaseHandler):
def handle(self, filename: str):
df = pd.read_csv(filename)
description = (
f"Dataframe with {len(df)} rows and {len(df.columns)} columns."
f"Dataframe with {len(df)} rows and {len(df.columns)} columns. "
"Columns are: "
f"{', '.join(df.columns)}"
)

@ -2,6 +2,9 @@ IMAGE_PROMPT = """
provide a figure named {filename}. The description is: {description}.
Please understand and answer the image based on this information. The image understanding is complete, so don't try to understand the image again.
USER INPUT
============
"""
@ -9,12 +12,18 @@ AUDIO_PROMPT = """
provide a audio named {filename}. The description is: {description}.
Please understand and answer the audio based on this information. The audio understanding is complete, so don't try to understand the audio again.
USER INPUT
============
"""
VIDEO_PROMPT = """
provide a video named {filename}. The description is: {description}.
Please understand and answer the video based on this information. The video understanding is complete, so don't try to understand the video again.
USER INPUT
============
"""
DATAFRAME_PROMPT = """
@ -22,4 +31,7 @@ provide a dataframe named {filename}. The description is: {description}.
You are able to use the dataframe to answer the question.
You have to act like an data analyst who can do an effective analysis through dataframe.
USER INPUT
============
"""

@ -1,10 +1,9 @@
EVAL_PREFIX = """{bot_name} can execute any user's request.
{bot_name} has permission to handle one instance and can handle the environment in it at will. File creation is only possible in playground folder, and other folders and files can never be modified under any circumstances.
You can code, run, debug, and test yourself. You can correct the code appropriately by looking at the error message. Please do TDD.
I can understand, process, and create various types of files. Images must be stored in the ./image/, audio in the ./audio/, video in the ./video/, and dataframes must be stored in the ./dataframe/.
{bot_name} has permission to handle one instance and can handle the environment in it at will.
You can code, run, debug, and test yourself. You can correct the code appropriately by looking at the error message.
I can understand, process, and create various types of files.
{bot_name} can do whatever it takes to execute the user's request. Let's think step by step.
"""
@ -38,6 +37,7 @@ EVAL_SUFFIX = """TOOLS
{bot_name} can ask the user to use tools to look up information that may be helpful in answering the users original question.
You are very strict to the filename correctness and will never fake a file name if it does not exist.
You will remember to provide the file name loyally if it's provided in the last tool observation.
If you have to include files in your response, you must provide the filepath in [file://filepath] format. It must be wrapped in square brackets.
The tools the human can use are:

@ -55,16 +55,12 @@ class CodeEditor(BaseToolSet):
"Input should be filename and code to append. "
"Input code must be the code that should be appended, NOT whole code. "
"ex. test.py\nprint('hello world')\n "
"and the output will be last 3 line.",
"and the output will be last 3 lines.",
)
def append(self, inputs: str) -> str:
try:
code = CodeWriter.append(inputs)
output = (
"Last 3 line was:\n"
+ "\n".join(code.split("\n")[-3:])
+ "\nYou can use CodeEditor.APPEND tool to append the code if this file is not completed."
)
output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:])
except Exception as e:
output = str(e)
@ -88,11 +84,7 @@ class CodeEditor(BaseToolSet):
def write(self, inputs: str) -> str:
try:
code = CodeWriter.write(inputs)
output = (
"Last 3 line was:\n"
+ "\n".join(code.split("\n")[-3:])
+ "\nYou can use CodeEditor.APPEND tool to append the code if this file is not completed."
)
output = "Last 3 line was:\n" + "\n".join(code.split("\n")[-3:])
except Exception as e:
output = str(e)
@ -101,23 +93,23 @@ class CodeEditor(BaseToolSet):
)
return output
@tool(
name="CodeEditor.PATCH",
description="Patch the code to correct the error if an error occurs or to improve it. "
"Input is a list of patches. The patch is separated by {seperator}. ".format(
seperator=CodePatcher.separator.replace("\n", "\\n")
)
+ "Each patch has to be formatted like below.\n"
"<filepath>|<start_line>,<start_col>|<end_line>,<end_col>|<new_code>"
"Here is an example. If the original code is:\n"
"print('hello world')\n"
"and you want to change it to:\n"
"print('hi corca')\n"
"then the patch should be:\n"
"test.py|1,8|1,19|hi corca\n"
"Code between start and end will be replaced with new_code. "
"The output will be written/deleted bytes or error message. ",
)
# @tool(
# name="CodeEditor.PATCH",
# description="Patch the code to correct the error if an error occurs or to improve it. "
# "Input is a list of patches. The patch is separated by {seperator}. ".format(
# seperator=CodePatcher.separator.replace("\n", "\\n")
# )
# + "Each patch has to be formatted like below.\n"
# "<filepath>|<start_line>,<start_col>|<end_line>,<end_col>|<new_code>"
# "Here is an example. If the original code is:\n"
# "print('hello world')\n"
# "and you want to change it to:\n"
# "print('hi corca')\n"
# "then the patch should be:\n"
# "test.py|1,8|1,19|hi corca\n"
# "Code between start and end will be replaced with new_code. "
# "The output will be written/deleted bytes or error message. ",
# )
def patch(self, patches: str) -> str:
try:
w, d = CodePatcher.patch(patches)
@ -139,7 +131,6 @@ class CodeEditor(BaseToolSet):
"Output will be success or error message.",
)
def delete(self, inputs: str) -> str:
filepath: str = str(Path(settings["PLAYGROUND_DIR"]) / Path(inputs))
try:
with open(filepath, "w") as f:
f.write("")

@ -58,10 +58,9 @@ test.py|11,16|11,16|_titles
import os
import re
from pathlib import Path
from typing import Tuple
from env import settings
from .verify import verify
class Position:
@ -84,7 +83,7 @@ class PatchCommand:
separator = "|"
def __init__(self, filepath: str, start: Position, end: Position, content: str):
self.filepath: str = str(Path(settings["PLAYGROUND_DIR"]) / Path(filepath))
self.filepath: str = filepath
self.start: Position = start
self.end: Position = end
self.content: str = content
@ -99,14 +98,8 @@ class PatchCommand:
f.writelines(lines)
return sum([len(line) for line in lines])
@verify
def execute(self) -> Tuple[int, int]:
# make sure the directory exists
if not str(Path(self.filepath).resolve()).startswith(
str(Path(settings["PLAYGROUND_DIR"]).resolve())
):
return "You can't write file outside of current directory."
os.makedirs(os.path.dirname(self.filepath), exist_ok=True)
lines = self.read_lines()
before = sum([len(line) for line in lines])

@ -3,10 +3,9 @@ read protocol:
<filepath>|<start line>-<end line>
"""
from pathlib import Path
from typing import List, Optional, Tuple
from typing import List, Optional
from env import settings
from .verify import verify
class Line:
@ -104,16 +103,12 @@ class ReadCommand:
separator = "|"
def __init__(self, filepath: str, start: int, end: int):
self.filepath: str = str(Path(settings["PLAYGROUND_DIR"]) / Path(filepath))
self.filepath: str = filepath
self.start: int = start
self.end: int = end
@verify
def execute(self) -> str:
if not str(Path(self.filepath).resolve()).startswith(
str(Path(settings["PLAYGROUND_DIR"]).resolve())
):
return "You can't write file outside of current directory."
with open(self.filepath, "r") as f:
code = f.readlines()
@ -134,16 +129,12 @@ class SummaryCommand:
separator = "|"
def __init__(self, filepath: str, depth: int, parent_content: Optional[str] = None):
self.filepath: str = str(Path(settings["PLAYGROUND_DIR"]) / Path(filepath))
self.filepath: str = filepath
self.depth: int = depth
self.parent_content: Optional[str] = parent_content
@verify
def execute(self) -> str:
if not str(Path(self.filepath).resolve()).startswith(
str(Path(settings["PLAYGROUND_DIR"]).resolve())
):
return "You can't write file outside of current directory."
with open(self.filepath, "r") as f:
code = f.readlines()
@ -152,8 +143,6 @@ class SummaryCommand:
if line.strip() != "":
code_tree.append(line, i + 1)
# code_tree.print()
if self.parent_content is None:
lines = code_tree.find_from_root(self.depth)
else:

@ -0,0 +1,14 @@
from pathlib import Path
def verify(func):
def wrapper(*args, **kwargs):
try:
filepath = args[0].filepath
except AttributeError:
raise Exception("This tool doesn't have filepath. Please check your code.")
if not str(Path(filepath).resolve()).startswith(str(Path().resolve())):
return "You can't access file outside of playground."
return func(*args, **kwargs)
return wrapper

@ -5,16 +5,15 @@ write protocol:
<content>
"""
import os
from pathlib import Path
from env import settings
from .verify import verify
class WriteCommand:
separator = "\n"
def __init__(self, filepath: str, content: int):
self.filepath: str = str(Path(settings["PLAYGROUND_DIR"]) / Path(filepath))
self.filepath: str = filepath
self.content: str = content
self.mode: str = "w"
@ -22,14 +21,11 @@ class WriteCommand:
self.mode = mode
return self
@verify
def execute(self) -> str:
# make sure the directory exists
if not str(Path(self.filepath).resolve()).startswith(
str(Path(settings["PLAYGROUND_DIR"]).resolve())
):
return "You can't write file outside of current directory."
os.makedirs(os.path.dirname(self.filepath), exist_ok=True)
dir_path = os.path.dirname(self.filepath)
if dir_path:
os.makedirs(dir_path, exist_ok=True)
with open(self.filepath, self.mode) as f:
f.write(self.content)
return self.content

@ -76,7 +76,7 @@ class ImageEditing(BaseToolSet):
name="Remove Something From The Photo",
description="useful when you want to remove and object or something from the photo "
"from its description or location. "
"The input to this tool should be a comma seperated string of two, "
"The input to this tool should be a comma separated string of two, "
"representing the image_path and the object need to be removed. ",
)
def inference_remove(self, inputs):
@ -87,7 +87,7 @@ class ImageEditing(BaseToolSet):
name="Replace Something From The Photo",
description="useful when you want to replace an object from the object description or "
"location with another object from its description. "
"The input to this tool should be a comma seperated string of three, "
"The input to this tool should be a comma separated string of three, "
"representing the image_path, the object to be replaced, the object to be replaced with ",
)
def inference_replace(self, inputs):
@ -132,7 +132,7 @@ class InstructPix2Pix(BaseToolSet):
name="Instruct Image Using Text",
description="useful when you want to the style of the image to be like the text. "
"like: make it look like a painting. or make it like a robot. "
"The input to this tool should be a comma seperated string of two, "
"The input to this tool should be a comma separated string of two, "
"representing the image_path and the text. ",
)
def inference(self, inputs):
@ -202,7 +202,7 @@ class VisualQuestionAnswering(BaseToolSet):
name="Answer Question About The Image",
description="useful when you need an answer for a question based on an image. "
"like: what is the background color of the last image, how many cats in this figure, what is in this figure. "
"The input to this tool should be a comma seperated string of two, representing the image_path and the question",
"The input to this tool should be a comma separated string of two, representing the image_path and the question",
)
def inference(self, inputs):
image_path, question = inputs.split(",")

@ -31,7 +31,6 @@ class Terminal(BaseToolSet):
process = subprocess.Popen(
commands,
shell=True,
cwd=settings["PLAYGROUND_DIR"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

@ -1,5 +1,6 @@
import os
import shutil
from pathlib import Path
from env import DotEnv
@ -7,20 +8,22 @@ from .base import AbstractUploader
class StaticUploader(AbstractUploader):
STATIC_DIR = "static"
def __init__(self, server: str):
def __init__(self, server: str, path: Path, endpoint: str):
self.server = server
self.path = path
self.endpoint = endpoint
@staticmethod
def from_settings(settings: DotEnv) -> "StaticUploader":
return StaticUploader(settings["SERVER"])
def from_settings(settings: DotEnv, path: Path, endpoint: str) -> "StaticUploader":
return StaticUploader(settings["SERVER"], path, endpoint)
def get_url(self, uploaded_path: str) -> str:
return f"{self.server}/{uploaded_path}"
def upload(self, filepath: str):
upload_path = os.path.join(StaticUploader.STATIC_DIR, filepath)
os.makedirs(os.path.dirname(upload_path), exist_ok=True)
shutil.copy(filepath, upload_path)
return f"{self.server}/{upload_path}"
relative_path = Path("generated") / filepath.split("/")[-1]
file_path = self.path / relative_path
os.makedirs(os.path.dirname(file_path), exist_ok=True)
shutil.copy(filepath, file_path)
endpoint_path = self.endpoint / relative_path
return f"{self.server}/{endpoint_path}"

@ -12,10 +12,14 @@ services:
- ./playground/:/app/playground/
ports:
- "4500:4500"
- "7000:7000"
- "6500:6500"
- "8000:8000" # eval port
env_file:
- .env
environment:
- CELERY_BROKER_URL=redis://redis:6379
depends_on:
- redis
eval.gpu:
container_name: eval.gpu
@ -29,7 +33,7 @@ services:
- ./playground/:/app/playground/
ports:
- "4500:4500"
- "7000:7000"
- "6500:6500"
- "8000:8000" # eval port
env_file:
- .env
@ -40,3 +44,8 @@ services:
- driver: nvidia
device_ids: ["1"] # You can choose which GPU to use
capabilities: [gpu]
depends_on:
- redis
redis:
image: redis:alpine

@ -9,9 +9,10 @@ load_dotenv()
class DotEnv(TypedDict):
OPENAI_API_KEY: str
PORT: int
EVAL_PORT: int
SERVER: str
CELERY_BROKER_URL: str
USE_GPU: bool # optional
PLAYGROUND_DIR: str # optional
LOG_LEVEL: str # optional
@ -27,10 +28,12 @@ class DotEnv(TypedDict):
SERPAPI_API_KEY: str # optional
PORT = int(os.getenv("PORT", 8000))
EVAL_PORT = int(os.getenv("EVAL_PORT", 8000))
settings: DotEnv = {
"PORT": PORT,
"SERVER": os.getenv("SERVER", f"http://localhost:{PORT}"),
"EVAL_PORT": EVAL_PORT,
"MODEL_NAME": os.getenv("MODEL_NAME", "gpt-4"),
"CELERY_BROKER_URL": os.getenv("CELERY_BROKER_URL", "redis://localhost:6379"),
"SERVER": os.getenv("SERVER", f"http://localhost:{EVAL_PORT}"),
"USE_GPU": os.getenv("USE_GPU", "False").lower() == "true",
"PLAYGROUND_DIR": os.getenv("PLAYGROUND_DIR", "playground"),
"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),

@ -0,0 +1,7 @@
1. Please develop and serve a simple web TODO app. The user can list all TODO items and add or delete each TODO item. I want it to have neumorphism-style. The ports you can use are 4500 and 6500.
2. Please develop and serve a simple community web service. People can signup, login, post, comment. Post and comment should be visible at once. I want it to have neumorphism-style. The ports you can use are 4500 and 6500.
3. Please make a web GUI for using HTTP API server. The name of it is EVAL. You can check the server code at ./main.py. The server is served on localhost:8000. Users should be able to write text input as 'query' and url array as 'files', and check the response. Users input form should be delivered in JSON format. I want it to have neumorphism-style. Serve it on port 4500.
4. Please introduce yourself to the world in detail. What you're doing, what you can do, what you want to do. Please write all of this in a readable way at README.md. You must include user's input in INPUT.md except personal access token. After writing, you should create a repository, commit, and push it. username is eval-bot. user email is [no-reply@corca.ai](mailto:no-reply@corca.ai). personal access token is ghp-EXAMPLE1234.

254
poetry.lock generated

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
[[package]]
name = "accelerate"
@ -153,6 +153,21 @@ files = [
[package.dependencies]
frozenlist = ">=1.1.0"
[[package]]
name = "amqp"
version = "5.1.1"
description = "Low-level AMQP client for Python (fork of amqplib)."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "amqp-5.1.1-py3-none-any.whl", hash = "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359"},
{file = "amqp-5.1.1.tar.gz", hash = "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2"},
]
[package.dependencies]
vine = ">=5.0.0"
[[package]]
name = "anyio"
version = "3.6.2"
@ -224,6 +239,18 @@ soupsieve = ">1.2"
html5lib = ["html5lib"]
lxml = ["lxml"]
[[package]]
name = "billiard"
version = "3.6.4.0"
description = "Python multiprocessing fork with improvements and bugfixes"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "billiard-3.6.4.0-py3-none-any.whl", hash = "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"},
{file = "billiard-3.6.4.0.tar.gz", hash = "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547"},
]
[[package]]
name = "bitsandbytes"
version = "0.37.2"
@ -325,6 +352,61 @@ urllib3 = ">=1.25.4,<1.27"
[package.extras]
crt = ["awscrt (==0.16.9)"]
[[package]]
name = "celery"
version = "5.2.7"
description = "Distributed Task Queue."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "celery-5.2.7-py3-none-any.whl", hash = "sha256:138420c020cd58d6707e6257b6beda91fd39af7afde5d36c6334d175302c0e14"},
{file = "celery-5.2.7.tar.gz", hash = "sha256:fafbd82934d30f8a004f81e8f7a062e31413a23d444be8ee3326553915958c6d"},
]
[package.dependencies]
billiard = ">=3.6.4.0,<4.0"
click = ">=8.0.3,<9.0"
click-didyoumean = ">=0.0.3"
click-plugins = ">=1.1.1"
click-repl = ">=0.2.0"
kombu = ">=5.2.3,<6.0"
pytz = ">=2021.3"
vine = ">=5.0.0,<6.0"
[package.extras]
arangodb = ["pyArango (>=1.3.2)"]
auth = ["cryptography"]
azureblockblob = ["azure-storage-blob (==12.9.0)"]
brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
cassandra = ["cassandra-driver (<3.21.0)"]
consul = ["python-consul2"]
cosmosdbsql = ["pydocumentdb (==2.3.2)"]
couchbase = ["couchbase (>=3.0.0)"]
couchdb = ["pycouchdb"]
django = ["Django (>=1.11)"]
dynamodb = ["boto3 (>=1.9.178)"]
elasticsearch = ["elasticsearch"]
eventlet = ["eventlet (>=0.32.0)"]
gevent = ["gevent (>=1.5.0)"]
librabbitmq = ["librabbitmq (>=1.5.0)"]
memcache = ["pylibmc"]
mongodb = ["pymongo[srv] (>=3.11.1)"]
msgpack = ["msgpack"]
pymemcache = ["python-memcached"]
pyro = ["pyro4"]
pytest = ["pytest-celery"]
redis = ["redis (>=3.4.1,!=4.0.0,!=4.0.1)"]
s3 = ["boto3 (>=1.9.125)"]
slmq = ["softlayer-messaging (>=1.0.3)"]
solar = ["ephem"]
sqlalchemy = ["sqlalchemy"]
sqs = ["kombu[sqs]"]
tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
yaml = ["PyYAML (>=3.10)"]
zookeeper = ["kazoo (>=1.3.1)"]
zstd = ["zstandard"]
[[package]]
name = "certifi"
version = "2022.12.7"
@ -437,6 +519,56 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "click-didyoumean"
version = "0.3.0"
description = "Enables git-like *did-you-mean* feature in click"
category = "main"
optional = false
python-versions = ">=3.6.2,<4.0.0"
files = [
{file = "click-didyoumean-0.3.0.tar.gz", hash = "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"},
{file = "click_didyoumean-0.3.0-py3-none-any.whl", hash = "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667"},
]
[package.dependencies]
click = ">=7"
[[package]]
name = "click-plugins"
version = "1.1.1"
description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
category = "main"
optional = false
python-versions = "*"
files = [
{file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
{file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"},
]
[package.dependencies]
click = ">=4.0"
[package.extras]
dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"]
[[package]]
name = "click-repl"
version = "0.2.0"
description = "REPL plugin for Click"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "click-repl-0.2.0.tar.gz", hash = "sha256:cd12f68d745bf6151210790540b4cb064c7b13e571bc64b6957d98d120dacfd8"},
{file = "click_repl-0.2.0-py3-none-any.whl", hash = "sha256:94b3fbbc9406a236f176e0506524b2937e4b23b6f4c0c0b2a0a83f8a64e9194b"},
]
[package.dependencies]
click = "*"
prompt-toolkit = "*"
six = "*"
[[package]]
name = "cmake"
version = "3.26.1"
@ -818,7 +950,7 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag
name = "jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -844,6 +976,38 @@ files = [
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
]
[[package]]
name = "kombu"
version = "5.2.4"
description = "Messaging library for Python."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "kombu-5.2.4-py3-none-any.whl", hash = "sha256:8b213b24293d3417bcf0d2f5537b7f756079e3ea232a8386dcc89a59fd2361a4"},
{file = "kombu-5.2.4.tar.gz", hash = "sha256:37cee3ee725f94ea8bb173eaab7c1760203ea53bbebae226328600f9d2799610"},
]
[package.dependencies]
amqp = ">=5.0.9,<6.0.0"
vine = "*"
[package.extras]
azureservicebus = ["azure-servicebus (>=7.0.0)"]
azurestoragequeues = ["azure-storage-queue"]
consul = ["python-consul (>=0.6.0)"]
librabbitmq = ["librabbitmq (>=2.0.0)"]
mongodb = ["pymongo (>=3.3.0,<3.12.1)"]
msgpack = ["msgpack"]
pyro = ["pyro4"]
qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"]
redis = ["redis (>=3.4.1,!=4.0.0,!=4.0.1)"]
slmq = ["softlayer-messaging (>=1.0.3)"]
sqlalchemy = ["sqlalchemy"]
sqs = ["boto3 (>=1.9.12)", "pycurl (>=7.44.1,<7.45.0)", "urllib3 (>=1.26.7)"]
yaml = ["PyYAML (>=3.10)"]
zookeeper = ["kazoo (>=1.3.1)"]
[[package]]
name = "langchain"
version = "0.0.115"
@ -905,7 +1069,7 @@ tiktoken = "*"
name = "markupsafe"
version = "2.1.2"
description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@ -1528,6 +1692,21 @@ files = [
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "prompt-toolkit"
version = "3.0.38"
description = "Library for building powerful interactive command lines in Python"
category = "main"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"},
{file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "psutil"
version = "5.9.4"
@ -1719,6 +1898,21 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "python-multipart"
version = "0.0.6"
description = "A streaming multipart parser for Python"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "python_multipart-0.0.6-py3-none-any.whl", hash = "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18"},
{file = "python_multipart-0.0.6.tar.gz", hash = "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132"},
]
[package.extras]
dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"]
[[package]]
name = "python-ptrace"
version = "0.9.8"
@ -1793,6 +1987,25 @@ files = [
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
[[package]]
name = "redis"
version = "4.5.4"
description = "Python client for Redis database and key-value store"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"},
{file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"},
]
[package.dependencies]
async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""}
[package.extras]
hiredis = ["hiredis (>=1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
name = "regex"
version = "2023.3.23"
@ -2396,6 +2609,15 @@ category = "dev"
optional = false
python-versions = "*"
files = [
{file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"},
{file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"},
{file = "triton-2.0.0-1-cp36-cp36m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c9fc8c89874bc48eb7e7b2107a9b8d2c0bf139778637be5bfccb09191685cfd"},
{file = "triton-2.0.0-1-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d2684b6a60b9f174f447f36f933e9a45f31db96cb723723ecd2dcfd1c57b778b"},
{file = "triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d4978298b74fcf59a75fe71e535c092b023088933b2f1df933ec32615e4beef"},
{file = "triton-2.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74f118c12b437fb2ca25e1a04759173b517582fcf4c7be11913316c764213656"},
{file = "triton-2.0.0-1-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9618815a8da1d9157514f08f855d9e9ff92e329cd81c0305003eb9ec25cc5add"},
{file = "triton-2.0.0-1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aca3303629cd3136375b82cb9921727f804e47ebee27b2677fef23005c3851a"},
{file = "triton-2.0.0-1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3e13aa8b527c9b642e3a9defcc0fbd8ffbe1c80d8ac8c15a01692478dc64d8a"},
{file = "triton-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05a7e64e4ca0565535e3d5d3405d7e49f9d308505bb7773d21fb26a4c008c2"},
{file = "triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd"},
{file = "triton-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b4d70dc92fb40af553b4460492c31dc7d3a114a979ffb7a5cdedb7eb546c08"},
@ -2481,6 +2703,30 @@ h11 = ">=0.8"
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "vine"
version = "5.0.0"
description = "Promises, promises, promises."
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
{file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"},
]
[[package]]
name = "wcwidth"
version = "0.2.6"
description = "Measures the displayed width of unicode strings in a terminal"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
]
[[package]]
name = "wheel"
version = "0.40.0"
@ -2618,4 +2864,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "75263beb9c26f5ce6dc0e25851441272e3095880ebf0a01e2b9bf9be5a8ab10e"
content-hash = "3a7cbc62858401d620de4eeab75906ea8caa97d780632e58e1d6316681228d13"

@ -7,6 +7,7 @@ packages = [{include = "api"}]
[tool.poetry.scripts]
serve = "api.main:serve"
dev = "api.main:dev"
[tool.poetry.dependencies]
python = "^3.10"
@ -21,6 +22,10 @@ pillow = "^9.4.0"
boto3 = "^1.26.94"
uvicorn = "^0.21.1"
python-ptrace = "^0.9.8"
jinja2 = "^3.1.2"
python-multipart = "^0.0.6"
celery = "^5.2.7"
redis = "^4.5.4"
[tool.poetry.group.gpu]
optional = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

@ -0,0 +1,277 @@
const $ = (selector) => document.querySelector(selector);
const setLoader = (isLoading) => {
const button = $("#submit");
const loader = $("#submit-loader");
if (isLoading) {
button.style.display = "none";
loader.style.display = "block";
} else {
button.style.display = "block";
loader.style.display = "none";
}
};
const setAnswer = (answer, files = []) => {
if (answer) {
$("#answer").textContent = answer;
} else {
$("#answer").innerHTML = createSpinner();
}
const filesDiv = $("#response-files");
filesDiv.innerHTML = "";
files.forEach((file) => {
const a = document.createElement("a");
a.classList.add("icon-link");
a.href = file;
a.textContent = file.split("/").pop();
a.setAttribute("download", "");
filesDiv.appendChild(a);
});
};
class EvalApi {
constructor({ onComplete, onError, onSettle, onLLMEnd, onToolEnd }) {
this.executionId = null;
this.pollInterval = null;
this.onComplete = (answer, files, info) => {
onComplete(answer, files, info);
onSettle();
};
this.onError = (error) => {
onError(error);
onSettle();
};
this.onLLMEnd = (info) => {
onLLMEnd(info);
};
this.onToolEnd = (info) => {
onToolEnd(info);
};
}
async uploadFiles(rawfiles) {
const files = [];
if (rawfiles.length > 0) {
const formData = new FormData();
for (let i = 0; i < rawfiles.length; i++) {
formData.append("files", rawfiles[i]);
}
const respone = await fetch("/upload", {
method: "POST",
body: formData,
});
const { urls } = await respone.json();
files.push(...urls);
}
return files;
}
async execute(prompt, session, files) {
try {
const response = await fetch("/api/execute/async", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt,
session,
files,
}),
});
if (response.status !== 200) {
throw new Error(await response.text());
}
const { id: executionId } = await response.json();
this.executionId = executionId;
this.pollInterval = setInterval(this.poll.bind(this), 1000);
} catch (e) {
clearInterval(this.pollInterval);
this.onError(e);
}
}
async poll() {
try {
const response = await fetch(`/api/execute/async/${this.executionId}`, {
method: "GET",
});
if (response.status !== 200) {
throw new Error(await response.text());
}
const { status, result, info } = await response.json();
switch (status) {
case "PENDING":
break;
case "FAILURE":
throw new Error("Execution failed");
case "LLM_END":
this.onLLMEnd(info);
break;
case "TOOL_END":
this.onToolEnd(info);
break;
case "SUCCESS":
clearInterval(this.pollInterval);
this.onComplete(result.answer, result.files, info);
break;
}
} catch (e) {
clearInterval(this.pollInterval);
this.onError(e);
}
}
}
const submit = async () => {
setAnswer("");
setLoader(true);
const actions = $("#actions");
actions.innerHTML = "";
let currentActionIndex = 0;
const onInfo = (info) => {
if (currentActionIndex >= info.index) {
return;
}
currentActionIndex = info.index;
const w = document.createElement("div");
w.innerHTML = createActionCard(
info.index,
info.action,
info.action_input,
info.what_i_did,
info.plan,
info.observation
);
actions.appendChild(w);
};
const api = new EvalApi({
onSettle: () => setLoader(false),
onError: (error) => setAnswer(`Error: ${error.message}`, []),
onComplete: (answer, files, info) => {
setAnswer(answer, files);
onInfo(info);
},
onLLMEnd: onInfo,
onToolEnd: onInfo,
});
const prompt = $("#prompt").value;
const session = $("#session").value;
const files = await api.uploadFiles($("#files").files);
await api.execute(prompt, session, files);
};
const setRandomSessionId = () => {
const sessionId = Math.random().toString(36).substring(2, 15);
$("#session").value = sessionId;
};
const createSpinner = () => `
<div class="text-center">
<div class="spinner-border m-3"></div>
</div>
`;
const createActionCard = (
index,
action,
input,
whatIdid,
plan,
observation
) => `
<div class="accordion m-2">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button">
<span class="text-secondary">
Action #${index}
</span>
<span class="mx-1">-</span>
<span class="fw-bold">
${action}
</span>
</button>
</h2>
<div class="accordion-collapse collapse show">
<div class="accordion-body">
<table class="table">
<tbody>
${
action !== "Final Answer"
? `<tr>
<th style="width: 100px">Input</th>
<td><div>${input}</div></td>
</tr>`
: ""
}
<tr>
<th style="width: 100px">What I Did</th>
<td><div>${whatIdid}</div></td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th colspan="2">Plan</th>
</tr>
</thead>
<tbody>
${plan
.split("- ")
.map((p) => p.trim())
.filter((p) => p.length > 0)
.map(
(p) => `
<tr>
${
p.startsWith("[ ]")
? `<td><input class="form-check-input" type="checkbox" /></td>
<td>${p.replace("[ ]", "")}</td>`
: ""
}
${
p.startsWith("[x]")
? `<td><input class="form-check-input" type="checkbox" checked/></td>
<td>${p.replace("[x]", "")}</td>`
: ""
}
</tr>`
)
.join("")}
</tbody>
</table>
${
action !== "Final Answer"
? `<table class="table">
<thead>
<tr>
<th colspan="2">Observation</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div>${observation}</div>
</td>
</tr>
</tbody>
</table>`
: ""
}
</div>
</div>
</div>
</div>`;

@ -0,0 +1,27 @@
const highlightActiveNavItem = () => {
const navItems = document.querySelectorAll("#nav-sidebar > li > a");
const currentPath = window.location.pathname;
navItems.forEach((item) => {
if (item.getAttribute("href") === currentPath) {
item.classList.add("active");
}
});
};
highlightActiveNavItem();
const getNumber = (str) => Number(str.replace("px", ""));
function expandTextarea(id) {
document.getElementById(id).addEventListener(
"keyup",
function () {
this.style.overflow = "hidden";
this.style.height =
Math.max(getNumber(this.style.height), this.scrollHeight) + "px";
},
false
);
}
expandTextarea("prompt");

@ -0,0 +1,7 @@
.logo {
border-radius: 50%;
overflow: hidden;
height: 48px;
width: 48px;
margin-right: 20px;
}

@ -4,12 +4,6 @@ import uuid
import numpy as np
os.makedirs("image", exist_ok=True)
os.makedirs("audio", exist_ok=True)
os.makedirs("video", exist_ok=True)
os.makedirs("dataframe", exist_ok=True)
os.makedirs("playground", exist_ok=True)
def seed_everything(seed):
random.seed(seed)
@ -24,15 +18,6 @@ def seed_everything(seed):
return seed
def prompts(name, description):
def decorator(func):
func.name = name
func.description = description
return func
return decorator
def cut_dialogue_history(history_memory, keep_last_n_words=500):
tokens = history_memory.split()
n_tokens = len(tokens)

Loading…
Cancel
Save