Update AutoGen Sample App | Rename AutoGen Assistant -> AutoGen Studio (#998)

* add new autogen-studio renamed folder

* remove old autogen-assistant files

* formatting updates

* add support for upsert/updates to agents and workflows

* version bump, general fixes

* support deleting db items

* add support for summary method to flowmanager

* formatting updates

* update serverl urls

* version bump

* add support for updated metadata messages object to include sender information

* formatting updates

* update documentation and blog post

* blog post update

* add description field example to agent workflow spec

* readme and blog update

* Update website/blog/2023-12-01-AutoGenStudio/index.mdx

Co-authored-by: Chi Wang <wang.chi@microsoft.com>

* add fix to ensure working directory is cleared after each run

* update version

* minor updates

* formatting updates

---------

Co-authored-by: Chi Wang <wang.chi@microsoft.com>
This commit is contained in:
Victor Dibia
2023-12-24 04:20:59 -08:00
committed by GitHub
parent a97269634f
commit 26b7aff2dc
75 changed files with 3301 additions and 1714 deletions

View File

@@ -1,120 +0,0 @@
# AutoGen Assistant
![ARA](./docs/ara_stockprices.png)
AutoGen Assistant is an Autogen-powered AI app (user interface) that can converse with you to help you conduct research, write and execute code, run saved skills, create new skills (explicitly and by demonstration), and adapt in response to your interactions.
### Capabilities / Roadmap
Some of the capabilities supported by the app frontend include the following:
- [x] Select from a list of agents (current support for two agent workflows - `UserProxyAgent` and `AssistantAgent`)
- [x] Modify agent configuration (e.g. temperature, model, agent system message, model etc) and chat with updated agent configurations.
- [x] View agent messages and output files in the UI from agent runs.
- [ ] Support for more complex agent workflows (e.g. `GroupChat` workflows)
- [ ] Improved user experience (e.g., streaming intermediate model output, better summarization of agent responses, etc)
Project Structure:
- _autogenra/_ code for the backend classes and web api (FastAPI)
- _frontend/_ code for the webui, built with Gatsby and Tailwind
### Installation
1. **Install from PyPi**
We recommend using a virtual environment (e.g., conda) to avoid conflicts with existing Python packages. With Python 3.10 or newer active in your virtual environment, use pip to install AutoGen Assistant:
```bash
pip install autogenra
```
2. **Install from Source**
> Note: This approach requires some familiarity with building interfaces in React.
If you prefer to install from source, ensure you have Python 3.10+ and Node.js (version above 14.15.0) installed. Here's how you get started:
- Clone the AutoGen Assistant repository and install its Python dependencies:
```bash
pip install -e .
```
- Navigate to the `samples/apps/autogen-assistant/frontend` directory, install dependencies, and build the UI:
```bash
npm install -g gatsby-cli
npm install --global yarn
yarn install
yarn build
```
- For Windows users, to build the frontend, you may need alternative commands to build the frontend.
```bash
gatsby clean && rmdir /s /q ..\\autogenra\\web\\ui && (set \"PREFIX_PATH_VALUE=\" || ver>nul) && gatsby build --prefix-paths && xcopy /E /I /Y public ..\\autogenra\\web\\ui
```
- Navigate to the `samples/apps/autogen-assistant` directory and install the `autogenra` library in your current Python environment:
```bash
pip install -e .
```
### Running the Application
Once installed, run the web UI by entering the following in your terminal:
```bash
autogenra ui --port 8081
```
This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Assistant.
Now that you have AutoGen Assistant installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills.
## Capabilities
This demo focuses on the research assistant use case with some generalizations:
- **Skills**: The agent is provided with a list of skills that it can leverage while attempting to address a user's query. Each skill is a python function that may be in any file in a folder made available to the agents. We separate the concept of global skills available to all agents `backend/files/global_utlis_dir` and user level skills `backend/files/user/<user_hash>/utils_dir`, relevant in a multi user environment. Agents are aware skills as they are appended to the system message. A list of example skills is available in the `backend/global_utlis_dir` folder. Modify the file or create a new file with a function in the same directory to create new global skills.
- **Conversation Persistence**: Conversation history is persisted in an sqlite database `database.sqlite`.
- **Default Agent Workflow**: The default a sample workflow with two agents - a user proxy agent and an assistant agent.
## Example Usage
Let us use a simple query demonstrating the capabilities of the research assistant.
```
Plot a chart of NVDA and TESLA stock price YTD. Save the result to a file named nvda_tesla.png
```
The agents responds by _writing and executing code_ to create a python program to generate the chart with the stock prices.
> Note than there could be multiple turns between the `AssistantAgent` and the `UserProxyAgent` to produce and execute the code in order to complete the task.
![ARA](./docs/ara_stockprices.png)
> Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background.
<!-- ![ARA](./docs/ara_console.png) -->
## FAQ
**Q: How can I add more skills to the AutoGen Assistant?**
A: You can extend the capabilities of your agents by adding new Python functions. The AutoGen Assistant interface also lets you directly paste functions that can be reused in the agent workflow.
**Q: Where can I adjust the agent configurations and settings?**
A: You can modify agent configurations directly from the UI or by editing the default configurations in the `utils.py` file under the `get_default_agent_config()` method (assuming you are building your own UI).
**Q: If I want to reset the conversation with an agent, how do I go about it?**
A: To reset your conversation history, you can delete the `database.sqlite` file. If you need to clear user-specific data, remove the relevant `autogenra/web/files/user/<user_id_md5hash>` folder.
**Q: Is it possible to view the output and messages generated by the agents during interactions?**
A: Yes, you can view the generated messages in the debug console of the web UI, providing insights into the agent interactions. Alternatively, you can inspect the `database.sqlite` file for a comprehensive record of messages.
## Acknowledgements
AutoGen assistant is Based on the [AutoGen](https://microsoft.github.io/autogen) project. It is adapted in October 2023 from a research prototype (original credits: Gagan Bansal, Adam Fourney, Victor Dibia, Piali Choudhury, Saleema Amershi, Ahmed Awadallah, Chi Wang)

View File

@@ -1,3 +0,0 @@
from .autogenflow import *
from .autogenchat import *
from .datamodel import *

View File

@@ -1,325 +0,0 @@
import json
import logging
import sqlite3
import threading
import os
from typing import Any, List, Dict, Tuple
from ..datamodel import Gallery, Message, Session
MESSAGES_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS messages (
user_id TEXT NOT NULL,
session_id TEXT,
root_msg_id TEXT NOT NULL,
msg_id TEXT,
role TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT,
timestamp DATETIME,
UNIQUE (user_id, root_msg_id, msg_id)
)
"""
SESSIONS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
flow_config TEXT,
UNIQUE (user_id, session_id)
)
"""
SKILLS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS sessions (
session_id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
flow_config TEXT,
UNIQUE (user_id, session_id)
)
"""
GALLERY_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS gallery (
gallery_id TEXT NOT NULL,
session TEXT,
messages TEXT,
tags TEXT,
timestamp DATETIME NOT NULL,
UNIQUE ( gallery_id)
)
"""
lock = threading.Lock()
logger = logging.getLogger()
class DBManager:
"""
A database manager class that handles the creation and interaction with an SQLite database.
"""
def __init__(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the DBManager object, creates a database if it does not exist, and establishes a connection.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
self.path = path
# check if the database exists, if not create it
if not os.path.exists(self.path):
logger.info("Creating database")
self.init_db(path=self.path, **kwargs)
try:
self.conn = sqlite3.connect(self.path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
except Exception as e:
logger.error("Error connecting to database: %s", e)
raise e
def init_db(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the database by creating necessary tables.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
# Connect to the database (or create a new one if it doesn't exist)
self.conn = sqlite3.connect(path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
# Create the table with the specified columns, appropriate data types, and a UNIQUE constraint on (root_msg_id, msg_id)
self.cursor.execute(MESSAGES_TABLE_SQL)
# Create a sessions table
self.cursor.execute(SESSIONS_TABLE_SQL)
# Create a skills
self.cursor.execute(SKILLS_TABLE_SQL)
# Create a gallery table
self.cursor.execute(GALLERY_TABLE_SQL)
# Commit the changes and close the connection
self.conn.commit()
def query(self, query: str, args: Tuple = (), return_json: bool = False) -> List[Dict[str, Any]]:
"""
Executes a given SQL query and returns the results.
Args:
query (str): The SQL query to execute.
args (Tuple): The arguments to pass to the SQL query.
return_json (bool): If True, the results will be returned as a list of dictionaries.
Returns:
List[Dict[str, Any]]: The result of the SQL query.
"""
try:
with lock:
self.cursor.execute(query, args)
result = self.cursor.fetchall()
self.commit()
if return_json:
result = [dict(zip([key[0] for key in self.cursor.description], row)) for row in result]
return result
except Exception as e:
logger.error("Error running query with query %s and args %s: %s", query, args, e)
raise e
def commit(self) -> None:
"""
Commits the current transaction to the database.
"""
self.conn.commit()
def close(self) -> None:
"""
Closes the database connection.
"""
self.conn.close()
def save_message(message: Message, dbmanager: DBManager) -> None:
"""
Save a message in the database using the provided database manager.
:param message: The Message object containing message data
:param dbmanager: The DBManager instance used to interact with the database
"""
query = "INSERT INTO messages (user_id, root_msg_id, msg_id, role, content, metadata, timestamp, session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
args = (
message.user_id,
message.root_msg_id,
message.msg_id,
message.role,
message.content,
message.metadata,
message.timestamp,
message.session_id,
)
dbmanager.query(query=query, args=args)
def load_messages(user_id: str, session_id: str, dbmanager: DBManager) -> List[dict]:
"""
Load messages for a specific user and session from the database, sorted by timestamp.
:param user_id: The ID of the user whose messages are to be loaded
:param session_id: The ID of the session whose messages are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a message
"""
query = "SELECT * FROM messages WHERE user_id = ? AND session_id = ?"
args = (user_id, session_id)
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=False)
return result
def get_sessions(user_id: str, dbmanager: DBManager) -> List[dict]:
"""
Load sessions for a specific user from the database, sorted by timestamp.
:param user_id: The ID of the user whose sessions are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a session
"""
query = "SELECT * FROM sessions WHERE user_id = ?"
args = (user_id,)
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
for row in result:
row["flow_config"] = json.loads(row["flow_config"])
return result
def create_session(user_id: str, session: Session, dbmanager: DBManager) -> List[dict]:
"""
Create a new session for a specific user in the database.
:param user_id: The ID of the user whose session is to be created
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a session
"""
query = "INSERT INTO sessions (user_id, session_id, timestamp, flow_config) VALUES (?, ?, ?,?)"
args = (session.user_id, session.session_id, session.timestamp, json.dumps(session.flow_config.dict()))
dbmanager.query(query=query, args=args)
sessions = get_sessions(user_id=user_id, dbmanager=dbmanager)
return sessions
def publish_session(session: Session, dbmanager: DBManager, tags: List[str] = []) -> Gallery:
"""
Publish a session to the gallery table in the database. Fetches the session messages first, then saves session and messages object to the gallery database table.
:param session: The Session object containing session data
:param dbmanager: The DBManager instance used to interact with the database
:param tags: A list of tags to associate with the session
:return: A gallery object containing the session and messages objects
"""
messages = load_messages(user_id=session.user_id, session_id=session.session_id, dbmanager=dbmanager)
gallery_item = Gallery(session=session, messages=messages, tags=tags)
query = "INSERT INTO gallery (gallery_id, session, messages, tags, timestamp) VALUES (?, ?, ?, ?,?)"
args = (
gallery_item.id,
json.dumps(gallery_item.session.dict()),
json.dumps([message.dict() for message in gallery_item.messages]),
json.dumps(gallery_item.tags),
gallery_item.timestamp,
)
dbmanager.query(query=query, args=args)
return gallery_item
def get_gallery(gallery_id, dbmanager: DBManager) -> List[Gallery]:
"""
Load gallery items from the database, sorted by timestamp. If gallery_id is provided, only the gallery item with the matching gallery_id will be returned.
:param gallery_id: The ID of the gallery item to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of Gallery objects
"""
if gallery_id:
query = "SELECT * FROM gallery WHERE gallery_id = ?"
args = (gallery_id,)
else:
query = "SELECT * FROM gallery"
args = ()
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
gallery = []
for row in result:
gallery_item = Gallery(
id=row["gallery_id"],
session=Session(**json.loads(row["session"])),
messages=[Message(**message) for message in json.loads(row["messages"])],
tags=json.loads(row["tags"]),
timestamp=row["timestamp"],
)
gallery.append(gallery_item)
return gallery
def delete_user_sessions(user_id: str, session_id: str, dbmanager: DBManager, delete_all: bool = False) -> List[dict]:
"""
Delete a specific session or all sessions for a user from the database.
:param user_id: The ID of the user whose session is to be deleted
:param session_id: The ID of the specific session to be deleted (ignored if delete_all is True)
:param dbmanager: The DBManager instance to interact with the database
:param delete_all: If True, all sessions for the user will be deleted
:return: A list of the remaining sessions if not all were deleted, otherwise an empty list
"""
if delete_all:
query = "DELETE FROM sessions WHERE user_id = ?"
args = (user_id,)
dbmanager.query(query=query, args=args)
return []
else:
query = "DELETE FROM sessions WHERE user_id = ? AND session_id = ?"
args = (user_id, session_id)
dbmanager.query(query=query, args=args)
sessions = get_sessions(user_id=user_id, dbmanager=dbmanager)
return sessions
def delete_message(
user_id: str, msg_id: str, session_id: str, dbmanager: DBManager, delete_all: bool = False
) -> List[dict]:
"""
Delete a specific message or all messages for a user and session from the database.
:param user_id: The ID of the user whose messages are to be deleted
:param msg_id: The ID of the specific message to be deleted (ignored if delete_all is True)
:param session_id: The ID of the session whose messages are to be deleted
:param dbmanager: The DBManager instance to interact with the database
:param delete_all: If True, all messages for the user will be deleted
:return: A list of the remaining messages if not all were deleted, otherwise an empty list
"""
if delete_all:
query = "DELETE FROM messages WHERE user_id = ? AND session_id = ?"
args = (user_id, session_id)
dbmanager.query(query=query, args=args)
return []
else:
query = "DELETE FROM messages WHERE user_id = ? AND msg_id = ? AND session_id = ?"
args = (user_id, msg_id, session_id)
dbmanager.query(query=query, args=args)
messages = load_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager)
return messages

View File

@@ -1,2 +0,0 @@
VERSION = "0.0.09a"
APP_NAME = "autogenra"

View File

@@ -1,35 +0,0 @@
from typing import Optional
import requests
from bs4 import BeautifulSoup
def fetch_user_profile(url: str) -> Optional[str]:
"""
Fetches the text content from a personal website.
Given a URL of a person's personal website, this function scrapes
the content of the page and returns the text found within the <body>.
Args:
url (str): The URL of the person's personal website.
Returns:
Optional[str]: The text content of the website's body, or None if any error occurs.
"""
try:
# Send a GET request to the URL
response = requests.get(url)
# Check for successful access to the webpage
if response.status_code == 200:
# Parse the HTML content of the page using BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# Extract the content of the <body> tag
body_content = soup.find("body")
# Return all the text in the body tag, stripping leading/trailing whitespaces
return " ".join(body_content.stripped_strings) if body_content else None
else:
# Return None if the status code isn't 200 (success)
return None
except requests.RequestException:
# Return None if any request-related exception is caught
return None

View File

@@ -1,73 +0,0 @@
import os
import re
import json
import hashlib
def search_arxiv(query, max_results=10):
"""
Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.
Args:
query (str): The search query.
max_results (int, optional): The maximum number of search results to return. Defaults to 10.
Returns:
jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'
Example:
>>> results = search_arxiv("attention is all you need")
>>> print(results)
"""
import arxiv
key = hashlib.md5(("search_arxiv(" + str(max_results) + ")" + query).encode("utf-8")).hexdigest()
# Create the cache if it doesn't exist
cache_dir = ".cache"
if not os.path.isdir(cache_dir):
os.mkdir(cache_dir)
fname = os.path.join(cache_dir, key + ".cache")
# Cache hit
if os.path.isfile(fname):
fh = open(fname, "r", encoding="utf-8")
data = json.loads(fh.read())
fh.close()
return data
# Normalize the query, removing operator keywords
query = re.sub(r"[^\s\w]", " ", query.lower())
query = re.sub(r"\s(and|or|not)\s", " ", " " + query + " ")
query = re.sub(r"[^\s\w]", " ", query.lower())
query = re.sub(r"\s+", " ", query).strip()
search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)
jresults = list()
for result in search.results():
r = dict()
r["entry_id"] = result.entry_id
r["updated"] = str(result.updated)
r["published"] = str(result.published)
r["title"] = result.title
r["authors"] = [str(a) for a in result.authors]
r["summary"] = result.summary
r["comment"] = result.comment
r["journal_ref"] = result.journal_ref
r["doi"] = result.doi
r["primary_category"] = result.primary_category
r["categories"] = result.categories
r["links"] = [str(link) for link in result.links]
r["pdf_url"] = result.pdf_url
jresults.append(r)
if len(jresults) > max_results:
jresults = jresults[0:max_results]
# Save to cache
fh = open(fname, "w")
fh.write(json.dumps(jresults))
fh.close()
return jresults

View File

@@ -1,51 +0,0 @@
# filename: generate_images.py
from typing import List
import uuid
import requests # to perform HTTP requests
from pathlib import Path
from openai import OpenAI
def generate_and_save_images(query: str, image_size: str = "1024x1024") -> List[str]:
"""
Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.
:param query: A natural language description of the image to be generated.
:param image_size: The size of the image to be generated. (default is "1024x1024")
:return: A list of filenames for the saved images.
"""
client = OpenAI() # Initialize the OpenAI client
response = client.images.generate(model="dall-e-3", prompt=query, n=1, size=image_size) # Generate images
# List to store the file names of saved images
saved_files = []
# Check if the response is successful
if response.data:
for image_data in response.data:
# Generate a random UUID as the file name
file_name = str(uuid.uuid4()) + ".png" # Assuming the image is a PNG
file_path = Path(file_name)
img_url = image_data.url
img_response = requests.get(img_url)
if img_response.status_code == 200:
# Write the binary content to a file
with open(file_path, "wb") as img_file:
img_file.write(img_response.content)
print(f"Image saved to {file_path}")
saved_files.append(str(file_path))
else:
print(f"Failed to download the image from {img_url}")
else:
print("No image data found in the response!")
# Return the list of saved files
return saved_files
# Example usage of the function:
# generate_and_save_images("A cute baby sea otter")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

View File

@@ -1,332 +0,0 @@
import { AdjustmentsVerticalIcon } from "@heroicons/react/24/outline";
import { Modal, Select, Slider } from "antd";
import * as React from "react";
import { ControlRowView, GroupView, ModelSelector } from "../../atoms";
import { IAgentFlowSpec, IFlowConfig, IModelConfig } from "../../types";
import TextArea from "antd/es/input/TextArea";
import { useConfigStore } from "../../../hooks/store";
import debounce from "lodash.debounce";
import { getModels } from "../../utils";
const FlowView = ({
title,
flowSpec,
setFlowSpec,
}: {
title: string;
flowSpec: IAgentFlowSpec;
setFlowSpec: (newFlowSpec: IAgentFlowSpec) => void;
}) => {
// Local state for the FlowView component
const [localFlowSpec, setLocalFlowSpec] =
React.useState<IAgentFlowSpec>(flowSpec);
// Event handlers for updating local state and propagating changes
const onControlChange = (value: any, key: string) => {
const updatedFlowSpec = {
...localFlowSpec,
config: { ...localFlowSpec.config, [key]: value },
};
setLocalFlowSpec(updatedFlowSpec);
setFlowSpec(updatedFlowSpec);
};
const onDebouncedControlChange = React.useCallback(
debounce((value: any, key: string) => {
onControlChange(value, key);
}, 3000),
[onControlChange]
);
const modelConfigs = getModels();
return (
<>
<div className="text-accent">{title}</div>
<GroupView title={flowSpec.config.name} className="mb-4">
<ControlRowView
title="Max Consecutive Auto Reply"
className="mt-4"
description="Max consecutive auto reply messages before termination."
value={flowSpec.config.max_consecutive_auto_reply}
control={
<Slider
min={2}
max={30}
defaultValue={flowSpec.config.max_consecutive_auto_reply}
step={1}
onAfterChange={(value: any) => {
onControlChange(value, "max_consecutive_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Human Input Mode"
description="Defines when to request human input"
value={flowSpec.config.human_input_mode}
control={
<Select
className="mt-2 w-full"
defaultValue={flowSpec.config.human_input_mode}
onChange={(value: any) => {
onControlChange(value, "human_input_mode");
}}
options={
[
{ label: "NEVER", value: "NEVER" },
{ label: "TERMINATE", value: "TERMINATE" },
{ label: "ALWAYS", value: "ALWAYS" },
] as any
}
/>
}
/>
<ControlRowView
title="System Message"
className="mt-4"
description="Free text to control agent behavior"
value={flowSpec.config.system_message}
control={
<TextArea
className="mt-2 w-full"
defaultValue={flowSpec.config.system_message}
rows={3}
onChange={(e) => {
onDebouncedControlChange(e.target.value, "system_message");
}}
/>
}
/>
{flowSpec.config.llm_config && (
<ControlRowView
title="Model"
className="mt-4"
description="Defines which models are used for the agent."
value={flowSpec.config.llm_config?.config_list?.[0]?.model}
control={
<ModelSelector
className="mt-2 w-full"
configs={flowSpec.config.llm_config.config_list || []}
setConfigs={(config_list: IModelConfig[]) => {
const llm_config = {
...flowSpec.config.llm_config,
config_list,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
)}
</GroupView>
</>
);
};
const AgentsControlView = ({
flowConfig,
setFlowConfig,
selectedConfig,
setSelectedConfig,
flowConfigs,
setFlowConfigs,
}: {
flowConfig: IFlowConfig;
setFlowConfig: (newFlowConfig: IFlowConfig) => void;
selectedConfig: number;
setSelectedConfig: (index: number) => void;
flowConfigs: IFlowConfig[];
setFlowConfigs: (newFlowConfigs: IFlowConfig[]) => void;
}) => {
const [isModalVisible, setIsModalVisible] = React.useState(false);
// Function to update a specific flowConfig by index
const updateFlowConfigs = (index: number, newFlowConfig: IFlowConfig) => {
const updatedFlowConfigs = [...flowConfigs];
updatedFlowConfigs[index] = newFlowConfig;
setFlowConfigs(updatedFlowConfigs);
};
React.useEffect(() => {
updateFlowConfigs(selectedConfig, flowConfig);
}, [flowConfig]);
const FlowConfigViewer = ({
flowConfig,
setFlowConfig,
}: {
flowConfig: IFlowConfig;
setFlowConfig: (newFlowConfig: IFlowConfig) => void;
}) => {
// Local state for sender and receiver FlowSpecs
const [senderFlowSpec, setSenderFlowSpec] = React.useState<IAgentFlowSpec>(
flowConfig.sender
);
const [receiverFlowSpec, setReceiverFlowSpec] =
React.useState<IAgentFlowSpec>(flowConfig.receiver);
// Update the local state and propagate changes to the parent component
const updateSenderFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setSenderFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, sender: newFlowSpec });
};
const updateReceiverFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setReceiverFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, receiver: newFlowSpec });
};
return (
<>
<div className="mb-2">{flowConfig.name}</div>
<div className="flex gap-3 ">
<div className="w-1/2">
<div className="">
<FlowView
title="Sender"
flowSpec={senderFlowSpec}
setFlowSpec={updateSenderFlowSpec}
/>
</div>
</div>
<div className="w-1/2">
<FlowView
title="Receiver"
flowSpec={receiverFlowSpec}
setFlowSpec={updateReceiverFlowSpec}
/>
</div>
</div>
</>
);
};
return (
<div className="text-secondary rounded p">
<Modal
width={800}
title={
<span>
<AdjustmentsVerticalIcon className="h-4 text-accent inline-block mr-2 -mt-1" />
AutoGen Agent Settings
</span>
}
open={isModalVisible}
onCancel={() => {
setIsModalVisible(false);
}}
onOk={() => {
setIsModalVisible(false);
}}
>
<ControlRowView
title="Agent Flow Specification"
className="mb-4"
description="Select the agent flow specification that will be used for your tasks."
value={flowConfig.name}
control={
<Select
className="mt-2 w-full"
value={flowConfig.name}
onChange={(value: any) => {
setSelectedConfig(value);
setFlowConfig(flowConfigs[value]);
}}
options={
flowConfigs.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
}
/>
<FlowConfigViewer
flowConfig={flowConfig}
setFlowConfig={setFlowConfig}
/>
<p className="mt-4 text-xs text-secondary">
{" "}
Learn more about AutoGen Agent parameters{" "}
<a
className="border-b border-accent hover:text-accent "
target={"_blank"}
rel={"noopener noreferrer"}
href={
"https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat"
}
>
here
</a>
.
</p>
</Modal>
<div
role="button"
onClick={() => {
setIsModalVisible(true);
}}
className="text-right flex-1 -mt-1 text-accent"
>
<span className="inline-block -mt-2">Settings</span>{" "}
<AdjustmentsVerticalIcon className="inline-block w-4 h-6 " />
</div>
</div>
);
};
const AgentsView = () => {
const flowConfigs = useConfigStore((state) => state.flowConfigs);
const setFlowConfigs = useConfigStore((state) => state.setFlowConfigs);
const flowConfig = useConfigStore((state) => state.flowConfig);
const setFlowConfig = useConfigStore((state) => state.setFlowConfig);
const [selectedConfig, setSelectedConfig] = React.useState<number>(0);
// const [flowConfig, setFlowConfig] = React.useState<IFlowConfig>(
// flowConfigs[selectedConfig]
// );
return (
<div className=" mb-4 ">
<div className="font-semibold pb-2 border-b">Agents </div>
<div className="text-xs mt-2 mb-2 pb-1 ">
{" "}
Select or create an agent workflow.{" "}
</div>
<div className="text-xs text-secondary mt-2 flex">
<div>Agent Workflow</div>
<div className="flex-1">
<AgentsControlView
flowConfig={flowConfig}
setFlowConfig={setFlowConfig}
selectedConfig={selectedConfig}
setSelectedConfig={setSelectedConfig}
flowConfigs={flowConfigs}
setFlowConfigs={setFlowConfigs}
/>
</div>
</div>
<Select
className="mt-2 w-full"
value={flowConfigs[selectedConfig].name}
onChange={(value: any) => {
setSelectedConfig(value);
setFlowConfig(flowConfigs[value]);
}}
options={
flowConfigs.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
</div>
);
};
export default AgentsView;

View File

@@ -1,12 +1,12 @@
database.sqlite
.cache/*
autogenra/web/files/user/*
autogenra/web/files/ui/*
autogenstudio/web/files/user/*
autogenstudio/web/files/ui/*
OAI_CONFIG_LIST
scratch/
autogenra/web/workdir/*
autogenra/web/ui/*
autogenra/web/skills/user/*
autogenstudio/web/workdir/*
autogenstudio/web/ui/*
autogenstudio/web/skills/user/*
.release.sh
# Byte-compiled / optimized / DLL files

View File

@@ -1,4 +1,4 @@
recursive-include autogenra/web/ui *
recursive-include autogenstudio/web/ui *
recursive-exclude notebooks *
recursive-exclude frontend *
recursive-exclude docs *

View File

@@ -0,0 +1,122 @@
# AutoGen Studio
![ARA](./docs/ara_stockprices.png)
AutoGen Studio is an AutoGen-powered AI app (user interface) to help you rapidly prototype AI agents, enhance them with skills, compose them into workflows and interact with them to accomplish tasks. It is built on top of the [AutoGen](https://microsoft.github.io/autogen) framework, which is a toolkit for building AI agents.
Code for AutoGen Studio is on GitHub at [microsoft/autogen](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio)
> **Note**: AutoGen Studio is meant to help you rapidly prototype multi-agent workflows and demonstrate an example of end user interfaces built with AutoGen. It is not meant to be a production-ready app.
### Capabilities / Roadmap
Some of the capabilities supported by the app frontend include the following:
- [x] Build / Configure agents (currently supports two agent workflows based on `UserProxyAgent` and `AssistantAgent`), modify their configuration (e.g. skills, temperature, model, agent system message, model etc) and compose them into workflows.
- [x] Chat with agent works and specify tasks.
- [x] View agent messages and output files in the UI from agent runs.
- [x] Add interaction sessions to a gallery.
- [ ] Support for more complex agent workflows (e.g. `GroupChat` workflows).
- [ ] Improved user experience (e.g., streaming intermediate model output, better summarization of agent responses, etc).
Project Structure:
- _autogenstudio/_ code for the backend classes and web api (FastAPI)
- _frontend/_ code for the webui, built with Gatsby and TailwindCSS
### Installation
1. **Install from PyPi**
We recommend using a virtual environment (e.g., conda) to avoid conflicts with existing Python packages. With Python 3.10 or newer active in your virtual environment, use pip to install AutoGen Studio:
```bash
pip install autogenstudio
```
2. **Install from Source**
> Note: This approach requires some familiarity with building interfaces in React.
If you prefer to install from source, ensure you have Python 3.10+ and Node.js (version above 14.15.0) installed. Here's how you get started:
- Clone the AutoGen Studio repository and install its Python dependencies:
```bash
pip install -e .
```
- Navigate to the `samples/apps/autogen-studio/frontend` directory, install dependencies, and build the UI:
```bash
npm install -g gatsby-cli
npm install --global yarn
cd frontend
yarn install
yarn build
```
For Windows users, to build the frontend, you may need alternative commands to build the frontend.
```bash
gatsby clean && rmdir /s /q ..\\autogenstudio\\web\\ui && (set \"PREFIX_PATH_VALUE=\" || ver>nul) && gatsby build --prefix-paths && xcopy /E /I /Y public ..\\autogenstudio\\web\\ui
```
### Running the Application
Once installed, run the web UI by entering the following in your terminal:
```bash
autogenstudio ui --port 8081
```
This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Studio.
Now that you have AutoGen Studio installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills.
## Capabilities
AutoGen Studio proposes some high-level concepts.
**Agent Workflow**: An agent workflow is a specification of a set of agents that can work together to accomplish a task. The simplest version of this is a setup with two agents a user proxy agent (that represents a user i.e. it compiles code and prints result) and an assistant that can address task requests (e.g., generating plans, writing code, evaluating responses, proposing error recovery steps, etc.). A more complex flow could be a group chat where even more agents work towards a solution.
**Session**: A session refers to a period of continuous interaction or engagement with an agent workflow, typically characterized by a sequence of activities or operations aimed at achieving specific objectives. It includes the agent workflow configuration, the interactions between the user and the agents. A session can be “published” to a “gallery”.
**Skills**: Skills are functions (e.g., Python functions) that describe how to solve a task. In general, a good skill has a descriptive name (e.g. `generate_images`), extensive docstrings and good defaults (e.g., writing out files to disk for persistence and reuse). You can add new skills AutoGen Studio app via the provided UI. At inference time, these skills are made available to the assistant agent as they address your tasks.
AutoGen Studio comes with 3 example skills: `fetch_profile`, `find_papers`, `generate_images`. The default skills, agents and workflows are based on the [dbdefaults.json](autogentstudio/utils/dbdefaults.json) file which is used to initialize the database.
## Example Usage
Consider the following query.
```
Plot a chart of NVDA and TESLA stock price YTD. Save the result to a file named nvda_tesla.png
```
The agent workflow responds by _writing and executing code_ to create a python program to generate the chart with the stock prices.
> Note than there could be multiple turns between the `AssistantAgent` and the `UserProxyAgent` to produce and execute the code in order to complete the task.
![ARA](./docs/ara_stockprices.png)
> Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background.
<!-- ![ARA](./docs/ara_console.png) -->
## FAQ
**Q: Where can I adjust the default skills, agent and workflow configurations?**
A: You can modify agent configurations directly from the UI or by editing the [dbdefaults.json](autogentstudio/utils/dbdefaults.json) file which is used to initialize the database.
**Q: If I want to reset the entire conversation with an agent, how do I go about it?**
A: To reset your conversation history, you can delete the `database.sqlite` file. If you need to clear user-specific data, remove the relevant `autogenstudio/web/files/user/<user_id_md5hash>` folder.
**Q: Is it possible to view the output and messages generated by the agents during interactions?**
A: Yes, you can view the generated messages in the debug console of the web UI, providing insights into the agent interactions. Alternatively, you can inspect the `database.sqlite` file for a comprehensive record of messages.
## Acknowledgements
AutoGen Studio is Based on the [AutoGen](https://microsoft.github.io/autogen) project. It was adapted from a research prototype built in October 2023 (original credits: Gagan Bansal, Adam Fourney, Victor Dibia, Piali Choudhury, Saleema Amershi, Ahmed Awadallah, Chi Wang).

View File

@@ -0,0 +1,3 @@
from .chatmanager import *
from .workflowmanager import *
from .datamodel import *

View File

@@ -3,7 +3,7 @@ import time
from typing import List
from .datamodel import AgentWorkFlowConfig, Message
from .utils import extract_successful_code_blocks, get_default_agent_config, get_modified_files
from .autogenflow import AutoGenWorkFlowManager
from .workflowmanager import AutoGenWorkFlowManager
import os
@@ -14,16 +14,13 @@ class AutoGenChatManager:
def chat(self, message: Message, history: List, flow_config: AgentWorkFlowConfig = None, **kwargs) -> None:
work_dir = kwargs.get("work_dir", None)
scratch_dir = os.path.join(work_dir, "scratch")
skills_suffix = kwargs.get("skills_prompt", "")
# if no flow config is provided, use the default
if flow_config is None:
flow_config = get_default_agent_config(scratch_dir, skills_suffix=skills_suffix)
flow_config = get_default_agent_config(scratch_dir)
# print("Flow config: ", flow_config)
flow = AutoGenWorkFlowManager(
config=flow_config, history=history, work_dir=scratch_dir, assistant_prompt=skills_suffix
)
flow = AutoGenWorkFlowManager(config=flow_config, history=history, work_dir=scratch_dir)
message_text = message.content.strip()
output = ""
@@ -32,16 +29,19 @@ class AutoGenChatManager:
metadata = {}
flow.run(message=f"{message_text}", clear_history=False)
agent_chat_messages = flow.receiver.chat_messages[flow.sender][len(history) :]
metadata["messages"] = agent_chat_messages
metadata["messages"] = flow.agent_history
successful_code_blocks = extract_successful_code_blocks(agent_chat_messages)
successful_code_blocks = "\n\n".join(successful_code_blocks)
output = (
(flow.sender.last_message()["content"] + "\n" + successful_code_blocks)
if successful_code_blocks
else flow.sender.last_message()["content"]
)
output = ""
if flow_config.summary_method == "last":
successful_code_blocks = extract_successful_code_blocks(flow.agent_history)
last_message = flow.agent_history[-1]["message"]["content"]
successful_code_blocks = "\n\n".join(successful_code_blocks)
output = (last_message + "\n" + successful_code_blocks) if successful_code_blocks else last_message
elif flow_config.summary_method == "llm":
output = ""
elif flow_config.summary_method == "none":
output = ""
metadata["code"] = ""
end_time = time.time()

View File

@@ -17,13 +17,13 @@ def ui(
docs: bool = False,
):
"""
Launch the Autogen RA UI CLI .Pass in parameters host, port, workers, and reload to override the default values.
Launch the AutoGen Studio UI CLI .Pass in parameters host, port, workers, and reload to override the default values.
"""
os.environ["AUTOGENUI_API_DOCS"] = str(docs)
uvicorn.run(
"autogenra.web.app:app",
"autogenstudio.web.app:app",
host=host,
port=port,
workers=workers,
@@ -34,10 +34,10 @@ def ui(
@app.command()
def version():
"""
Print the version of the Autogen RA UI CLI.
Print the version of the AutoGen Studio UI CLI.
"""
typer.echo(f"Autogen RA UI CLI version: {VERSION}")
typer.echo(f"AutoGen Studio UI CLI version: {VERSION}")
def run():

View File

@@ -12,7 +12,7 @@ class Message(object):
content: str
root_msg_id: Optional[str] = None
msg_id: Optional[str] = None
timestamp: Optional[datetime] = None
timestamp: Optional[str] = None
personalize: Optional[bool] = False
ra: Optional[str] = None
code: Optional[str] = None
@@ -23,11 +23,33 @@ class Message(object):
if self.msg_id is None:
self.msg_id = str(uuid.uuid4())
if self.timestamp is None:
self.timestamp = datetime.now()
self.timestamp = datetime.now().isoformat()
def dict(self):
result = asdict(self)
return result
@dataclass
class Skill(object):
title: str
file_name: str
content: str
id: Optional[str] = None
description: Optional[str] = None
timestamp: Optional[str] = None
user_id: Optional[str] = None
def __post_init__(self):
if self.id is None:
self.id = str(uuid.uuid4())
if self.timestamp is None:
self.timestamp = datetime.now().isoformat()
if self.user_id is None:
self.user_id = "default"
def dict(self):
result = asdict(self)
result["timestamp"] = result["timestamp"].isoformat()
return result
@@ -37,7 +59,7 @@ class Message(object):
# autogenflow data models
@dataclass
class ModelConfig:
"""Data model for Model Config item in LLMConfig for Autogen"""
"""Data model for Model Config item in LLMConfig for AutoGen"""
model: str
api_key: Optional[str] = None
@@ -48,7 +70,7 @@ class ModelConfig:
@dataclass
class LLMConfig:
"""Data model for LLM Config for Autogen"""
"""Data model for LLM Config for AutoGen"""
config_list: List[Any] = field(default_factory=List)
temperature: float = 0
@@ -58,7 +80,7 @@ class LLMConfig:
@dataclass
class AgentConfig:
"""Data model for Agent Config for Autogen"""
"""Data model for Agent Config for AutoGen"""
name: str
llm_config: Optional[Union[LLMConfig, bool]] = False
@@ -68,6 +90,12 @@ class AgentConfig:
is_termination_msg: Optional[Union[bool, str, Callable]] = None
code_execution_config: Optional[Union[bool, str, Dict[str, Any]]] = None
def dict(self):
result = asdict(self)
if isinstance(result["llm_config"], LLMConfig):
result["llm_config"] = result["llm_config"].dict()
return result
@dataclass
class AgentFlowSpec:
@@ -75,19 +103,56 @@ class AgentFlowSpec:
type: Literal["assistant", "userproxy", "groupchat"]
config: AgentConfig = field(default_factory=AgentConfig)
id: Optional[str] = None
timestamp: Optional[str] = None
user_id: Optional[str] = None
skills: Optional[Union[None, List[Skill]]] = None
description: Optional[str] = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now().isoformat()
if self.id is None:
self.id = str(uuid.uuid4())
if self.user_id is None:
self.user_id = "default"
def dict(self):
result = asdict(self)
return result
@dataclass
class AgentWorkFlowConfig:
"""Data model for Flow Config for Autogen"""
"""Data model for Flow Config for AutoGen"""
name: str
description: str
sender: AgentFlowSpec
receiver: Union[AgentFlowSpec, List[AgentFlowSpec]]
type: Literal["default", "groupchat"] = "default"
id: Optional[str] = None
user_id: Optional[str] = None
timestamp: Optional[str] = None
# how the agent message summary is generated. last: only last message is used, none: no summary, llm: use llm to generate summary
summary_method: Optional[Literal["last", "none", "llm"]] = "last"
def __post_init__(self):
if self.id is None:
self.id = str(uuid.uuid4())
if self.user_id is None:
self.user_id = "default"
if self.timestamp is None:
self.timestamp = datetime.now().isoformat()
def dict(self):
return asdict(self)
result = asdict(self)
result["sender"] = self.sender.dict()
if isinstance(self.receiver, list):
result["receiver"] = [r.dict() for r in self.receiver]
else:
result["receiver"] = self.receiver.dict()
return result
@dataclass
@@ -95,19 +160,19 @@ class Session(object):
"""Data model for AutoGen Chat Session"""
user_id: str
session_id: Optional[str] = None
timestamp: Optional[datetime] = None
id: Optional[str] = None
timestamp: Optional[str] = None
flow_config: AgentWorkFlowConfig = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
if self.session_id is None:
self.session_id = str(uuid.uuid4())
self.timestamp = datetime.now().isoformat()
if self.id is None:
self.id = str(uuid.uuid4())
def dict(self):
result = asdict(self)
result["timestamp"] = self.timestamp.isoformat()
result["flow_config"] = self.flow_config.dict()
return result
@@ -119,17 +184,16 @@ class Gallery(object):
messages: List[Message]
tags: List[str]
id: Optional[str] = None
timestamp: Optional[datetime] = None
timestamp: Optional[str] = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
self.timestamp = datetime.now().isoformat()
if self.id is None:
self.id = str(uuid.uuid4())
def dict(self):
result = asdict(self)
result["timestamp"] = self.timestamp.isoformat()
return result
@@ -148,16 +212,12 @@ class DeleteMessageWebRequestModel(object):
session_id: Optional[str] = None
@dataclass
class CreateSkillWebRequestModel(object):
user_id: str
skills: Union[str, List[str]]
@dataclass
class DBWebRequestModel(object):
user_id: str
msg_id: Optional[str] = None
session: Optional[Session] = None
skills: Optional[Union[str, List[str]]] = None
skill: Optional[Skill] = None
tags: Optional[List[str]] = None
agent: Optional[AgentFlowSpec] = None
workflow: Optional[AgentWorkFlowConfig] = None

View File

@@ -0,0 +1,189 @@
{
"agents": [
{
"type": "userproxy",
"description": "A user proxy agent that executes code.",
"config": {
"name": "userproxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 5,
"system_message": "",
"llm_config": false,
"code_execution_config": {
"work_dir": null,
"use_docker": false
}
}
},
{
"type": "assistant",
"description": "A primary assistant agent that writes plans and code to solve tasks.",
"skills": [
{
"title": "find_papers_arxiv",
"description": "This skill finds relevant papers on arXiv given a query.",
"content": "import os\nimport re\nimport json\nimport hashlib\n\n\ndef search_arxiv(query, max_results=10):\n \"\"\"\n Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.\n\n Args:\n query (str): The search query.\n max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n\n Returns:\n jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'\n\n Example:\n >>> results = search_arxiv(\"attention is all you need\")\n >>> print(results)\n \"\"\"\n\n import arxiv\n\n key = hashlib.md5((\"search_arxiv(\" + str(max_results) + \")\" + query).encode(\"utf-8\")).hexdigest()\n # Create the cache if it doesn't exist\n cache_dir = \".cache\"\n if not os.path.isdir(cache_dir):\n os.mkdir(cache_dir)\n\n fname = os.path.join(cache_dir, key + \".cache\")\n\n # Cache hit\n if os.path.isfile(fname):\n fh = open(fname, \"r\", encoding=\"utf-8\")\n data = json.loads(fh.read())\n fh.close()\n return data\n\n # Normalize the query, removing operator keywords\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s(and|or|not)\\s\", \" \", \" \" + query + \" \")\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s+\", \" \", query).strip()\n\n search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)\n\n jresults = list()\n for result in search.results():\n r = dict()\n r[\"entry_id\"] = result.entry_id\n r[\"updated\"] = str(result.updated)\n r[\"published\"] = str(result.published)\n r[\"title\"] = result.title\n r[\"authors\"] = [str(a) for a in result.authors]\n r[\"summary\"] = result.summary\n r[\"comment\"] = result.comment\n r[\"journal_ref\"] = result.journal_ref\n r[\"doi\"] = result.doi\n r[\"primary_category\"] = result.primary_category\n r[\"categories\"] = result.categories\n r[\"links\"] = [str(link) for link in result.links]\n r[\"pdf_url\"] = result.pdf_url\n jresults.append(r)\n\n if len(jresults) > max_results:\n jresults = jresults[0:max_results]\n\n # Save to cache\n fh = open(fname, \"w\")\n fh.write(json.dumps(jresults))\n fh.close()\n return jresults\n",
"file_name": "find_papers_arxiv.py"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n",
"file_name": "generate_images.py"
}
],
"config": {
"name": "primary_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
},
{
"model": "gpt-3.5-turbo-16k"
},
{
"model": "TheBloke/zephyr-7B-alpha-AWQ",
"base_url": "http://localhost:8000/v1"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": null
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 8,
"system_message": "You are a helpful assistant that can use available functions when needed to solve problems. At each point, do your best to determine if the user's request has been addressed. IF THE REQUEST HAS NOT BEEN ADDRESSED, RESPOND WITH CODE TO ADDRESS IT. IF A FAILURE OCCURRED (e.g., due to a missing library) AND SOME ADDITIONAL CODE WAS WRITTEN (e.g. code to install the library), ENSURE THAT THE ORIGINAL CODE TO ADDRESS THE TASK STILL GETS EXECUTED. If the request HAS been addressed, respond with a summary of the result. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request ' or 'The tallest mountain in Africa is ..' etc. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE."
}
}
],
"skills": [
{
"title": "find_papers_arxiv",
"description": "This skill finds relevant papers on arXiv given a query.",
"content": "import os\nimport re\nimport json\nimport hashlib\n\n\ndef search_arxiv(query, max_results=10):\n \"\"\"\n Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.\n\n Args:\n query (str): The search query.\n max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n\n Returns:\n jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'\n\n Example:\n >>> results = search_arxiv(\"attention is all you need\")\n >>> print(results)\n \"\"\"\n\n import arxiv\n\n key = hashlib.md5((\"search_arxiv(\" + str(max_results) + \")\" + query).encode(\"utf-8\")).hexdigest()\n # Create the cache if it doesn't exist\n cache_dir = \".cache\"\n if not os.path.isdir(cache_dir):\n os.mkdir(cache_dir)\n\n fname = os.path.join(cache_dir, key + \".cache\")\n\n # Cache hit\n if os.path.isfile(fname):\n fh = open(fname, \"r\", encoding=\"utf-8\")\n data = json.loads(fh.read())\n fh.close()\n return data\n\n # Normalize the query, removing operator keywords\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s(and|or|not)\\s\", \" \", \" \" + query + \" \")\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s+\", \" \", query).strip()\n\n search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)\n\n jresults = list()\n for result in search.results():\n r = dict()\n r[\"entry_id\"] = result.entry_id\n r[\"updated\"] = str(result.updated)\n r[\"published\"] = str(result.published)\n r[\"title\"] = result.title\n r[\"authors\"] = [str(a) for a in result.authors]\n r[\"summary\"] = result.summary\n r[\"comment\"] = result.comment\n r[\"journal_ref\"] = result.journal_ref\n r[\"doi\"] = result.doi\n r[\"primary_category\"] = result.primary_category\n r[\"categories\"] = result.categories\n r[\"links\"] = [str(link) for link in result.links]\n r[\"pdf_url\"] = result.pdf_url\n jresults.append(r)\n\n if len(jresults) > max_results:\n jresults = jresults[0:max_results]\n\n # Save to cache\n fh = open(fname, \"w\")\n fh.write(json.dumps(jresults))\n fh.close()\n return jresults\n",
"file_name": "find_papers_arxiv.py"
},
{
"title": "fetch_profile",
"description": "This skill fetches the text content from a personal website.",
"content": "from typing import Optional\nimport requests\nfrom bs4 import BeautifulSoup\n\n\ndef fetch_user_profile(url: str) -> Optional[str]:\n \"\"\"\n Fetches the text content from a personal website.\n\n Given a URL of a person's personal website, this function scrapes\n the content of the page and returns the text found within the <body>.\n\n Args:\n url (str): The URL of the person's personal website.\n\n Returns:\n Optional[str]: The text content of the website's body, or None if any error occurs.\n \"\"\"\n try:\n # Send a GET request to the URL\n response = requests.get(url)\n # Check for successful access to the webpage\n if response.status_code == 200:\n # Parse the HTML content of the page using BeautifulSoup\n soup = BeautifulSoup(response.text, \"html.parser\")\n # Extract the content of the <body> tag\n body_content = soup.find(\"body\")\n # Return all the text in the body tag, stripping leading/trailing whitespaces\n return \" \".join(body_content.stripped_strings) if body_content else None\n else:\n # Return None if the status code isn't 200 (success)\n return None\n except requests.RequestException:\n # Return None if any request-related exception is caught\n return None\n",
"file_name": "fetch_profile.py"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n",
"file_name": "generate_images.py"
}
],
"workflows": [
{
"name": "General Agent Workflow",
"description": "This workflow is used for general purpose tasks.",
"sender": {
"type": "userproxy",
"config": {
"name": "userproxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 10,
"system_message": "",
"llm_config": false,
"code_execution_config": {
"work_dir": null,
"use_docker": false
}
}
},
"receiver": {
"type": "assistant",
"description": "Default assistant to generate plans and write code to solve tasks.",
"skills": [
{
"title": "find_papers_arxiv",
"description": "This skill finds relevant papers on arXiv given a query.",
"content": "import os\nimport re\nimport json\nimport hashlib\n\n\ndef search_arxiv(query, max_results=10):\n \"\"\"\n Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.\n\n Args:\n query (str): The search query.\n max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n\n Returns:\n jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'\n\n Example:\n >>> results = search_arxiv(\"attention is all you need\")\n >>> print(results)\n \"\"\"\n\n import arxiv\n\n key = hashlib.md5((\"search_arxiv(\" + str(max_results) + \")\" + query).encode(\"utf-8\")).hexdigest()\n # Create the cache if it doesn't exist\n cache_dir = \".cache\"\n if not os.path.isdir(cache_dir):\n os.mkdir(cache_dir)\n\n fname = os.path.join(cache_dir, key + \".cache\")\n\n # Cache hit\n if os.path.isfile(fname):\n fh = open(fname, \"r\", encoding=\"utf-8\")\n data = json.loads(fh.read())\n fh.close()\n return data\n\n # Normalize the query, removing operator keywords\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s(and|or|not)\\s\", \" \", \" \" + query + \" \")\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s+\", \" \", query).strip()\n\n search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)\n\n jresults = list()\n for result in search.results():\n r = dict()\n r[\"entry_id\"] = result.entry_id\n r[\"updated\"] = str(result.updated)\n r[\"published\"] = str(result.published)\n r[\"title\"] = result.title\n r[\"authors\"] = [str(a) for a in result.authors]\n r[\"summary\"] = result.summary\n r[\"comment\"] = result.comment\n r[\"journal_ref\"] = result.journal_ref\n r[\"doi\"] = result.doi\n r[\"primary_category\"] = result.primary_category\n r[\"categories\"] = result.categories\n r[\"links\"] = [str(link) for link in result.links]\n r[\"pdf_url\"] = result.pdf_url\n jresults.append(r)\n\n if len(jresults) > max_results:\n jresults = jresults[0:max_results]\n\n # Save to cache\n fh = open(fname, \"w\")\n fh.write(json.dumps(jresults))\n fh.close()\n return jresults\n",
"file_name": "find_papers_arxiv.py"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n",
"file_name": "generate_images.py"
}
],
"config": {
"name": "primary_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
},
{
"model": "gpt-3.5-turbo-16k"
},
{
"model": "TheBloke/zephyr-7B-alpha-AWQ",
"base_url": "http://localhost:8000/v1"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": null
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 15,
"system_message": "You are a helpful assistant that can use available functions when needed to solve problems. At each point, do your best to determine if the user's request has been addressed. IF THE REQUEST HAS NOT BEEN ADDRESSED, RESPOND WITH CODE TO ADDRESS IT. IF A FAILURE OCCURRED (e.g., due to a missing library) AND SOME ADDITIONAL CODE WAS WRITTEN (e.g. code to install the library), ENSURE THAT THE ORIGINAL CODE TO ADDRESS THE TASK STILL GETS EXECUTED. If the request HAS been addressed, respond with a summary of the result. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request ' or 'The tallest mountain in Africa is ..' etc. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE."
}
},
"type": "default"
},
{
"name": "Visualization Agent Workflow",
"description": "This workflow is used for visualization tasks.",
"sender": {
"type": "userproxy",
"description": "User proxy agent to execute code",
"config": {
"name": "userproxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 5,
"system_message": "",
"llm_config": false,
"code_execution_config": {
"work_dir": null,
"use_docker": false
}
}
},
"receiver": {
"type": "assistant",
"description": "Visualization assistant to create plans and write code to generate visualizations",
"config": {
"name": "visualization_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
},
{
"model": "gpt-3.5-turbo-16k"
},
{
"model": "TheBloke/zephyr-7B-alpha-AWQ",
"base_url": "http://localhost:8000/v1"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": null
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 4,
"system_message": "Your task is to ensure you generate a high quality visualization for the user. Your visualizations must follow best practices and you must articulate your reasoning for your choices. The visualization must not have grid or outline box. The visualization should have an APPROPRIATE ASPECT RATIO e..g rectangular for time series data. The title must be bold. Importantly, if THE CHART IS A LINE CHART, you MUST ADD ALINE OF BEST FIT and ADD TEXT ON THE SLOPE OF EACH LINE. Note that today's date is 12/10/2023. At each point, do your best to determine if the user's request has been addressed and if so, respond with a summary. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request '. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE."
}
},
"type": "default"
}
]
}

View File

@@ -0,0 +1,671 @@
import json
import logging
import sqlite3
import threading
import os
from typing import Any, List, Dict, Optional, Tuple
from ..datamodel import AgentFlowSpec, AgentWorkFlowConfig, Gallery, Message, Session, Skill
MESSAGES_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS messages (
user_id TEXT NOT NULL,
session_id TEXT,
root_msg_id TEXT NOT NULL,
msg_id TEXT,
role TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT,
timestamp DATETIME,
UNIQUE (user_id, root_msg_id, msg_id)
)
"""
SESSIONS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS sessions (
id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
flow_config TEXT,
UNIQUE (user_id, id)
)
"""
SKILLS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS skills (
id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
content TEXT,
title TEXT,
file_name TEXT,
UNIQUE (id, user_id)
)
"""
AGENTS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS agents (
id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
config TEXT,
type TEXT,
skills TEXT,
description TEXT,
UNIQUE (id, user_id)
)
"""
WORKFLOWS_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS workflows (
id TEXT NOT NULL,
user_id TEXT NOT NULL,
timestamp DATETIME NOT NULL,
sender TEXT,
receiver TEXT,
type TEXT,
name TEXT,
description TEXT,
summary_method TEXT,
UNIQUE (id, user_id)
)
"""
GALLERY_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS gallery (
id TEXT NOT NULL,
session TEXT,
messages TEXT,
tags TEXT,
timestamp DATETIME NOT NULL,
UNIQUE ( id)
)
"""
lock = threading.Lock()
logger = logging.getLogger()
class DBManager:
"""
A database manager class that handles the creation and interaction with an SQLite database.
"""
def __init__(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the DBManager object, creates a database if it does not exist, and establishes a connection.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
self.path = path
# check if the database exists, if not create it
# self.reset_db()
if not os.path.exists(self.path):
logger.info("Creating database")
self.init_db(path=self.path, **kwargs)
try:
self.conn = sqlite3.connect(self.path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
except Exception as e:
logger.error("Error connecting to database: %s", e)
raise e
def reset_db(self):
"""
Reset the database by deleting the database file and creating a new one.
"""
print("resetting db")
if os.path.exists(self.path):
os.remove(self.path)
self.init_db(path=self.path)
def init_db(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the database by creating necessary tables.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
# Connect to the database (or create a new one if it doesn't exist)
self.conn = sqlite3.connect(path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
# Create the table with the specified columns, appropriate data types, and a UNIQUE constraint on (root_msg_id, msg_id)
self.cursor.execute(MESSAGES_TABLE_SQL)
# Create a sessions table
self.cursor.execute(SESSIONS_TABLE_SQL)
# Create a skills
self.cursor.execute(SKILLS_TABLE_SQL)
# Create a gallery table
self.cursor.execute(GALLERY_TABLE_SQL)
# Create a agents table
self.cursor.execute(AGENTS_TABLE_SQL)
# Create a workflows table
self.cursor.execute(WORKFLOWS_TABLE_SQL)
# init skills table with content of defaultskills.json in current directory
current_dir = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(current_dir, "dbdefaults.json"), "r", encoding="utf-8") as json_file:
data = json.load(json_file)
skills = data["skills"]
agents = data["agents"]
for skill in skills:
skill = Skill(**skill)
self.cursor.execute(
"INSERT INTO skills (id, user_id, timestamp, content, title, file_name) VALUES (?, ?, ?, ?, ?, ?)",
(skill.id, "default", skill.timestamp, skill.content, skill.title, skill.file_name),
)
for agent in agents:
agent = AgentFlowSpec(**agent)
agent.skills = [skill.dict() for skill in agent.skills] if agent.skills else None
self.cursor.execute(
"INSERT INTO agents (id, user_id, timestamp, config, type, skills, description) VALUES (?, ?, ?, ?, ?, ?, ?)",
(
agent.id,
"default",
agent.timestamp,
json.dumps(agent.config.dict()),
agent.type,
json.dumps(agent.skills),
agent.description,
),
)
for workflow in data["workflows"]:
workflow = AgentWorkFlowConfig(**workflow)
self.cursor.execute(
"INSERT INTO workflows (id, user_id, timestamp, sender, receiver, type, name, description, summary_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?)",
(
workflow.id,
"default",
workflow.timestamp,
json.dumps(workflow.sender.dict()),
json.dumps(workflow.receiver.dict()),
workflow.type,
workflow.name,
workflow.description,
workflow.summary_method,
),
)
# Commit the changes and close the connection
self.conn.commit()
def query(self, query: str, args: Tuple = (), return_json: bool = False) -> List[Dict[str, Any]]:
"""
Executes a given SQL query and returns the results.
Args:
query (str): The SQL query to execute.
args (Tuple): The arguments to pass to the SQL query.
return_json (bool): If True, the results will be returned as a list of dictionaries.
Returns:
List[Dict[str, Any]]: The result of the SQL query.
"""
try:
with lock:
self.cursor.execute(query, args)
result = self.cursor.fetchall()
self.commit()
if return_json:
result = [dict(zip([key[0] for key in self.cursor.description], row)) for row in result]
return result
except Exception as e:
logger.error("Error running query with query %s and args %s: %s", query, args, e)
raise e
def commit(self) -> None:
"""
Commits the current transaction to the database.
"""
self.conn.commit()
def close(self) -> None:
"""
Closes the database connection.
"""
self.conn.close()
def create_message(message: Message, dbmanager: DBManager) -> None:
"""
Save a message in the database using the provided database manager.
:param message: The Message object containing message data
:param dbmanager: The DBManager instance used to interact with the database
"""
query = "INSERT INTO messages (user_id, root_msg_id, msg_id, role, content, metadata, timestamp, session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
args = (
message.user_id,
message.root_msg_id,
message.msg_id,
message.role,
message.content,
message.metadata,
message.timestamp,
message.session_id,
)
dbmanager.query(query=query, args=args)
def get_messages(user_id: str, session_id: str, dbmanager: DBManager) -> List[dict]:
"""
Load messages for a specific user and session from the database, sorted by timestamp.
:param user_id: The ID of the user whose messages are to be loaded
:param session_id: The ID of the session whose messages are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a message
"""
query = "SELECT * FROM messages WHERE user_id = ? AND session_id = ?"
args = (user_id, session_id)
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=False)
return result
def get_sessions(user_id: str, dbmanager: DBManager) -> List[dict]:
"""
Load sessions for a specific user from the database, sorted by timestamp.
:param user_id: The ID of the user whose sessions are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a session
"""
query = "SELECT * FROM sessions WHERE user_id = ?"
args = (user_id,)
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
for row in result:
row["flow_config"] = json.loads(row["flow_config"])
return result
def create_session(user_id: str, session: Session, dbmanager: DBManager) -> List[dict]:
"""
Create a new session for a specific user in the database.
:param user_id: The ID of the user whose session is to be created
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a session
"""
query = "INSERT INTO sessions (user_id, id, timestamp, flow_config) VALUES (?, ?, ?,?)"
args = (session.user_id, session.id, session.timestamp, json.dumps(session.flow_config.dict()))
dbmanager.query(query=query, args=args)
sessions = get_sessions(user_id=user_id, dbmanager=dbmanager)
return sessions
def delete_session(session: Session, dbmanager: DBManager) -> List[dict]:
"""
Delete a specific session and all messages for that session in the database.
:param session: The Session object containing session data
:param dbmanager: The DBManager instance to interact with the database
:return: A list of the remaining sessions
"""
query = "DELETE FROM sessions WHERE id = ?"
args = (session.id,)
dbmanager.query(query=query, args=args)
query = "DELETE FROM messages WHERE session_id = ?"
args = (session.id,)
dbmanager.query(query=query, args=args)
return get_sessions(user_id=session.user_id, dbmanager=dbmanager)
def create_gallery(session: Session, dbmanager: DBManager, tags: List[str] = []) -> Gallery:
"""
Publish a session to the gallery table in the database. Fetches the session messages first, then saves session and messages object to the gallery database table.
:param session: The Session object containing session data
:param dbmanager: The DBManager instance used to interact with the database
:param tags: A list of tags to associate with the session
:return: A gallery object containing the session and messages objects
"""
messages = get_messages(user_id=session.user_id, session_id=session.id, dbmanager=dbmanager)
gallery_item = Gallery(session=session, messages=messages, tags=tags)
query = "INSERT INTO gallery (id, session, messages, tags, timestamp) VALUES (?, ?, ?, ?,?)"
args = (
gallery_item.id,
json.dumps(gallery_item.session.dict()),
json.dumps([message.dict() for message in gallery_item.messages]),
json.dumps(gallery_item.tags),
gallery_item.timestamp,
)
dbmanager.query(query=query, args=args)
return gallery_item
def get_gallery(gallery_id, dbmanager: DBManager) -> List[Gallery]:
"""
Load gallery items from the database, sorted by timestamp. If gallery_id is provided, only the gallery item with the matching gallery_id will be returned.
:param gallery_id: The ID of the gallery item to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of Gallery objects
"""
if gallery_id:
query = "SELECT * FROM gallery WHERE id = ?"
args = (gallery_id,)
else:
query = "SELECT * FROM gallery"
args = ()
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
gallery = []
for row in result:
gallery_item = Gallery(
id=row["id"],
session=Session(**json.loads(row["session"])),
messages=[Message(**message) for message in json.loads(row["messages"])],
tags=json.loads(row["tags"]),
timestamp=row["timestamp"],
)
gallery.append(gallery_item)
return gallery
def get_skills(user_id: str, dbmanager: DBManager) -> List[Skill]:
"""
Load skills from the database, sorted by timestamp. Load skills where id = user_id or user_id = default.
:param user_id: The ID of the user whose skills are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of Skill objects
"""
query = "SELECT * FROM skills WHERE user_id = ? OR user_id = ?"
args = (user_id, "default")
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
skills = []
for row in result:
skill = Skill(**row)
skills.append(skill)
return skills
def upsert_skill(skill: Skill, dbmanager: DBManager) -> List[Skill]:
"""
Insert or update a skill for a specific user in the database.
If the skill with the given ID already exists, it will be updated with the new data.
Otherwise, a new skill will be created.
:param skill: The Skill object containing skill data
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a skill
"""
existing_skill = get_item_by_field("skills", "id", skill.id, dbmanager)
if existing_skill:
updated_data = {
"user_id": skill.user_id,
"timestamp": skill.timestamp,
"content": skill.content,
"title": skill.title,
"file_name": skill.file_name,
}
update_item("skills", skill.id, updated_data, dbmanager)
else:
query = "INSERT INTO skills (id, user_id, timestamp, content, title, file_name) VALUES (?, ?, ?, ?, ?, ?)"
args = (skill.id, skill.user_id, skill.timestamp, skill.content, skill.title, skill.file_name)
dbmanager.query(query=query, args=args)
skills = get_skills(user_id=skill.user_id, dbmanager=dbmanager)
return skills
def delete_skill(skill: Skill, dbmanager: DBManager) -> List[Skill]:
"""
Delete a skill for a specific user in the database.
:param skill: The Skill object containing skill data
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a skill
"""
# delete where id = skill.id and user_id = skill.user_id
query = "DELETE FROM skills WHERE id = ? AND user_id = ?"
args = (skill.id, skill.user_id)
dbmanager.query(query=query, args=args)
return get_skills(user_id=skill.user_id, dbmanager=dbmanager)
def delete_message(
user_id: str, msg_id: str, session_id: str, dbmanager: DBManager, delete_all: bool = False
) -> List[dict]:
"""
Delete a specific message or all messages for a user and session from the database.
:param user_id: The ID of the user whose messages are to be deleted
:param msg_id: The ID of the specific message to be deleted (ignored if delete_all is True)
:param session_id: The ID of the session whose messages are to be deleted
:param dbmanager: The DBManager instance to interact with the database
:param delete_all: If True, all messages for the user will be deleted
:return: A list of the remaining messages if not all were deleted, otherwise an empty list
"""
if delete_all:
query = "DELETE FROM messages WHERE user_id = ? AND session_id = ?"
args = (user_id, session_id)
dbmanager.query(query=query, args=args)
return []
else:
query = "DELETE FROM messages WHERE user_id = ? AND msg_id = ? AND session_id = ?"
args = (user_id, msg_id, session_id)
dbmanager.query(query=query, args=args)
messages = get_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager)
return messages
def get_agents(user_id: str, dbmanager: DBManager) -> List[AgentFlowSpec]:
"""
Load agents from the database, sorted by timestamp. Load agents where id = user_id or user_id = default.
:param user_id: The ID of the user whose agents are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of AgentFlowSpec objects
"""
query = "SELECT * FROM agents WHERE user_id = ? OR user_id = ?"
args = (user_id, "default")
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
agents = []
for row in result:
row["config"] = json.loads(row["config"])
row["skills"] = json.loads(row["skills"] or "[]")
agent = AgentFlowSpec(**row)
agents.append(agent)
return agents
def upsert_agent(agent_flow_spec: AgentFlowSpec, dbmanager: DBManager) -> List[Dict[str, Any]]:
"""
Insert or update an agent for a specific user in the database.
If the agent with the given ID already exists, it will be updated with the new data.
Otherwise, a new agent will be created.
:param agent_flow_spec: The AgentFlowSpec object containing agent configuration
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing an agent after insertion or update
"""
existing_agent = get_item_by_field("agents", "id", agent_flow_spec.id, dbmanager)
if existing_agent:
updated_data = {
"user_id": agent_flow_spec.user_id,
"timestamp": agent_flow_spec.timestamp,
"config": json.dumps(agent_flow_spec.config.dict()),
"type": agent_flow_spec.type,
"description": agent_flow_spec.description,
"skills": json.dumps([x.dict() for x in agent_flow_spec.skills] if agent_flow_spec.skills else []),
}
update_item("agents", agent_flow_spec.id, updated_data, dbmanager)
else:
query = "INSERT INTO agents (id, user_id, timestamp, config, type, description, skills) VALUES (?, ?, ?, ?, ?, ?, ?)"
config_json = json.dumps(agent_flow_spec.config.dict())
args = (
agent_flow_spec.id,
agent_flow_spec.user_id,
agent_flow_spec.timestamp,
config_json,
agent_flow_spec.type,
agent_flow_spec.description,
json.dumps([x.dict() for x in agent_flow_spec.skills] if agent_flow_spec.skills else []),
)
dbmanager.query(query=query, args=args)
agents = get_agents(user_id=agent_flow_spec.user_id, dbmanager=dbmanager)
return agents
def delete_agent(agent: AgentFlowSpec, dbmanager: DBManager) -> List[Dict[str, Any]]:
"""
Delete an agent for a specific user from the database.
:param agent: The AgentFlowSpec object containing agent configuration
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing an agent after deletion
"""
# delete based on agent.id and agent.user_id
query = "DELETE FROM agents WHERE id = ? AND user_id = ?"
args = (agent.id, agent.user_id)
dbmanager.query(query=query, args=args)
return get_agents(user_id=agent.user_id, dbmanager=dbmanager)
def get_item_by_field(table: str, field: str, value: Any, dbmanager: DBManager) -> Optional[Dict[str, Any]]:
query = f"SELECT * FROM {table} WHERE {field} = ?"
args = (value,)
result = dbmanager.query(query=query, args=args)
return result[0] if result else None
def update_item(table: str, item_id: str, updated_data: Dict[str, Any], dbmanager: DBManager) -> None:
set_clause = ", ".join([f"{key} = ?" for key in updated_data.keys()])
query = f"UPDATE {table} SET {set_clause} WHERE id = ?"
args = (*updated_data.values(), item_id)
dbmanager.query(query=query, args=args)
def get_workflows(user_id: str, dbmanager: DBManager) -> List[Dict[str, Any]]:
"""
Load workflows for a specific user from the database, sorted by timestamp.
:param user_id: The ID of the user whose workflows are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a workflow
"""
query = "SELECT * FROM workflows WHERE user_id = ? OR user_id = ?"
args = (user_id, "default")
result = dbmanager.query(query=query, args=args, return_json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=True)
workflows = []
for row in result:
row["sender"] = json.loads(row["sender"])
row["receiver"] = json.loads(row["receiver"])
workflow = AgentWorkFlowConfig(**row)
workflows.append(workflow)
return workflows
def upsert_workflow(workflow: AgentWorkFlowConfig, dbmanager: DBManager) -> List[Dict[str, Any]]:
"""
Insert or update a workflow for a specific user in the database.
If the workflow with the given ID already exists, it will be updated with the new data.
Otherwise, a new workflow will be created.
:param workflow: The AgentWorkFlowConfig object containing workflow data
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a workflow after insertion or update
"""
existing_workflow = get_item_by_field("workflows", "id", workflow.id, dbmanager)
if existing_workflow:
updated_data = {
"user_id": workflow.user_id,
"timestamp": workflow.timestamp,
"sender": json.dumps(workflow.sender.dict()),
"receiver": json.dumps(
[receiver.dict() for receiver in workflow.receiver]
if isinstance(workflow.receiver, list)
else workflow.receiver.dict()
),
"type": workflow.type,
"name": workflow.name,
"description": workflow.description,
"summary_method": workflow.summary_method,
}
update_item("workflows", workflow.id, updated_data, dbmanager)
else:
query = "INSERT INTO workflows (id, user_id, timestamp, sender, receiver, type, name, description, summary_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?)"
args = (
workflow.id,
workflow.user_id,
workflow.timestamp,
json.dumps(workflow.sender.dict()),
json.dumps(
[receiver.dict() for receiver in workflow.receiver]
if isinstance(workflow.receiver, list)
else workflow.receiver.dict()
),
workflow.type,
workflow.name,
workflow.description,
workflow.summary_method,
)
dbmanager.query(query=query, args=args)
return get_workflows(user_id=workflow.user_id, dbmanager=dbmanager)
def delete_workflow(workflow: AgentWorkFlowConfig, dbmanager: DBManager) -> List[Dict[str, Any]]:
"""
Delete a workflow for a specific user from the database. If the workflow does not exist, do nothing.
:param workflow: The AgentWorkFlowConfig object containing workflow data
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a workflow after deletion
"""
# delete where workflow.id =id and workflow.user_id = user_id
query = "DELETE FROM workflows WHERE id = ? AND user_id = ?"
args = (workflow.id, workflow.user_id)
dbmanager.query(query=query, args=args)
return get_workflows(user_id=workflow.user_id, dbmanager=dbmanager)

View File

@@ -6,7 +6,7 @@ import os
import shutil
import re
import autogen
from ..datamodel import AgentConfig, AgentFlowSpec, AgentWorkFlowConfig, LLMConfig
from ..datamodel import AgentConfig, AgentFlowSpec, AgentWorkFlowConfig, LLMConfig, Skill
def md5_hash(text: str) -> str:
@@ -19,6 +19,20 @@ def md5_hash(text: str) -> str:
return hashlib.md5(text.encode()).hexdigest()
def clear_folder(folder_path: str) -> None:
"""
Clear the contents of a folder.
:param folder_path: The path to the folder to clear.
"""
for file in os.listdir(folder_path):
file_path = os.path.join(folder_path, file)
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
def get_file_type(file_path: str) -> str:
"""
@@ -70,6 +84,8 @@ def get_file_type(file_path: str) -> str:
# Supported image extensions
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp"}
# Supported (web) video extensions
VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".avi", ".wmv"}
# Supported PDF extension
PDF_EXTENSION = ".pdf"
@@ -84,6 +100,8 @@ def get_file_type(file_path: str) -> str:
file_type = "image"
elif file_extension == PDF_EXTENSION:
file_type = "pdf"
elif file_extension in VIDEO_EXTENSIONS:
file_type = "video"
else:
file_type = "unknown"
@@ -189,110 +207,62 @@ def init_webserver_folders(root_file_path: str) -> Dict[str, str]:
files_static_root = os.path.join(root_file_path, "files/")
static_folder_root = os.path.join(root_file_path, "ui")
workdir_root = os.path.join(root_file_path, "workdir")
skills_dir = os.path.join(root_file_path, "skills")
user_skills_dir = os.path.join(skills_dir, "user")
global_skills_dir = os.path.join(skills_dir, "global")
os.makedirs(files_static_root, exist_ok=True)
os.makedirs(os.path.join(files_static_root, "user"), exist_ok=True)
os.makedirs(static_folder_root, exist_ok=True)
os.makedirs(workdir_root, exist_ok=True)
os.makedirs(skills_dir, exist_ok=True)
os.makedirs(user_skills_dir, exist_ok=True)
os.makedirs(global_skills_dir, exist_ok=True)
folders = {
"files_static_root": files_static_root,
"static_folder_root": static_folder_root,
"workdir_root": workdir_root,
"skills_dir": skills_dir,
"user_skills_dir": user_skills_dir,
"global_skills_dir": global_skills_dir,
}
return folders
def skill_from_folder(folder: str) -> List[Dict[str, str]]:
def get_skills_from_prompt(skills: List[Skill], work_dir: str) -> str:
"""
Given a folder, return a dict of the skill (name, python file content). Only python files are considered.
Create a prompt with the content of all skills and write the skills to a file named skills.py in the work_dir.
:param folder: The folder to search for skills
:return: A list of dictionaries, each representing a skill
"""
skills = []
for root, dirs, files in os.walk(folder):
for file in files:
if file.endswith(".py"):
skill_name = file.split(".")[0]
skill_file_path = os.path.join(root, file)
with open(skill_file_path, "r", encoding="utf-8") as f:
skill_content = f.read()
skills.append({"name": skill_name, "content": skill_content, "file_name": file})
return skills
def get_all_skills(user_skills_path: str, global_skills_path: str, dest_dir: str = None) -> List[Dict[str, str]]:
"""
Get all skills from the user and global skills directories. If dest_dir, copy all skills to dest_dir.
:param user_skills_path: The path to the user skills directory
:param global_skills_path: The path to the global skills directory
:param dest_dir: The destination directory to copy all skills to
:return: A dictionary of user and global skills
"""
user_skills = skill_from_folder(user_skills_path)
os.makedirs(user_skills_path, exist_ok=True)
global_skills = skill_from_folder(global_skills_path)
skills = {
"user": user_skills,
"global": global_skills,
}
if dest_dir:
# check if dest_dir exists
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
# copy all skills to dest_dir
for skill in user_skills + global_skills:
skill_file_path = os.path.join(dest_dir, skill["file_name"])
with open(skill_file_path, "w", encoding="utf-8") as f:
f.write(skill["content"])
return skills
def get_skills_prompt(skills: List[Dict[str, str]]) -> str:
"""
Get a prompt with the content of all skills.
:param skills: A dictionary of user and global skills
:param skills: A dictionary skills
:return: A string containing the content of all skills
"""
user_skills = skills["user"]
global_skills = skills["global"]
all_skills = user_skills + global_skills
prompt = """
instruction = """
While solving the task you may use functions in the files below.
To use a function from a file in code, import the file and then use the function.
While solving the task you may use functions below which will be available in a file called skills.py .
To use a function skill.py in code, IMPORT THE FUNCTION FROM skills.py and then use the function.
If you need to install python packages, write shell code to
install via pip and use --quiet option.
"""
for skill in all_skills:
prompt = "" # filename: skills.py
for skill in skills:
prompt += f"""
##### Begin of {skill["file_name"]} #####
##### Begin of {skill.title} #####
{skill["content"]}
{skill.content}
#### End of {skill["file_name"]} ####
#### End of {skill.title} ####
"""
return prompt
# check if work_dir exists
if not os.path.exists(work_dir):
os.makedirs(work_dir)
# check if skills.py exist. if exists, append to the file, else create a new file and write to it
if os.path.exists(os.path.join(work_dir, "skills.py")):
with open(os.path.join(work_dir, "skills.py"), "a", encoding="utf-8") as f:
f.write(prompt)
else:
with open(os.path.join(work_dir, "skills.py"), "w", encoding="utf-8") as f:
f.write(prompt)
return instruction + prompt
def delete_files_in_folder(folders: Union[str, List[str]]) -> None:
@@ -327,7 +297,7 @@ def delete_files_in_folder(folders: Union[str, List[str]]) -> None:
print(f"Failed to delete {path}. Reason: {e}")
def get_default_agent_config(work_dir: str, skills_suffix: str = "") -> AgentWorkFlowConfig:
def get_default_agent_config(work_dir: str) -> AgentWorkFlowConfig:
"""
Get a default agent flow config .
"""
@@ -360,7 +330,7 @@ def get_default_agent_config(work_dir: str, skills_suffix: str = "") -> AgentWor
type="assistant",
config=AgentConfig(
name="primary_assistant",
system_message=autogen.AssistantAgent.DEFAULT_SYSTEM_MESSAGE + skills_suffix,
system_message=autogen.AssistantAgent.DEFAULT_SYSTEM_MESSAGE,
llm_config=llm_config,
),
)
@@ -370,6 +340,7 @@ def get_default_agent_config(work_dir: str, skills_suffix: str = "") -> AgentWor
sender=userproxy_spec,
receiver=assistant_spec,
type="default",
description="Default agent flow config",
)
return flow_config
@@ -388,66 +359,17 @@ def extract_successful_code_blocks(messages: List[Dict[str, str]]) -> List[str]:
List[str]: A list containing the code blocks that were successfully executed, including backticks.
"""
successful_code_blocks = []
code_block_regex = r"```[\s\S]*?```" # Regex pattern to capture code blocks enclosed in triple backticks.
# Regex pattern to capture code blocks enclosed in triple backticks.
code_block_regex = r"```[\s\S]*?```"
for i, message in enumerate(messages):
for i, row in enumerate(messages):
message = row["message"]
if message["role"] == "user" and "execution succeeded" in message["content"]:
if i > 0 and messages[i - 1]["role"] == "assistant":
prev_content = messages[i - 1]["content"]
if i > 0 and messages[i - 1]["message"]["role"] == "assistant":
prev_content = messages[i - 1]["message"]["content"]
# Find all matches for code blocks
code_blocks = re.findall(code_block_regex, prev_content)
successful_code_blocks.extend(code_blocks) # Add the code blocks with backticks
# Add the code blocks with backticks
successful_code_blocks.extend(code_blocks)
return successful_code_blocks
def create_skills_from_code(dest_dir: str, skills: Union[str, List[str]]) -> None:
"""
Create skills from a list of code blocks.
Parameters:
dest_dir (str): The destination directory to copy all skills to.
skills (Union[str, List[str]]): A list of strings containing code blocks.
"""
# Ensure skills is a list
if isinstance(skills, str):
skills = [skills]
# Check if dest_dir exists
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
for skill in skills:
# Attempt to parse the code and extract the top-level function name
try:
parsed = ast.parse(skill)
function_name = None
for node in parsed.body:
if isinstance(node, ast.FunctionDef):
function_name = node.name
break
if function_name is None:
raise ValueError("No top-level function definition found.")
# Sanitize the function name for use as a file name
function_name = "".join(ch for ch in function_name if ch.isalnum() or ch == "_")
skill_file_name = f"{function_name}.py"
except (ValueError, SyntaxError):
skill_file_name = "new_skill.py"
# If the generated/sanitized name already exists, append an index
skill_file_path = os.path.join(dest_dir, skill_file_name)
index = 1
while os.path.exists(skill_file_path):
base, ext = os.path.splitext(skill_file_name)
if base.endswith(f"_{index - 1}"):
base = base.rsplit("_", 1)[0]
skill_file_path = os.path.join(dest_dir, f"{base}_{index}{ext}")
index += 1
# Write the skill to the file
with open(skill_file_path, "w", encoding="utf-8") as f:
f.write(skill)

View File

@@ -0,0 +1,3 @@
VERSION = "0.0.18a"
__version__ = VERSION
APP_NAME = "autogenstudio"

View File

@@ -10,29 +10,13 @@ from fastapi import HTTPException
from ..datamodel import (
ChatWebRequestModel,
DBWebRequestModel,
CreateSkillWebRequestModel,
DeleteMessageWebRequestModel,
Message,
Session,
)
from ..utils import (
create_skills_from_code,
get_all_skills,
load_messages,
md5_hash,
save_message,
delete_message,
init_webserver_folders,
get_skills_prompt,
get_sessions,
create_session,
delete_user_sessions,
publish_session,
get_gallery,
DBManager,
)
from ..utils import md5_hash, init_webserver_folders, DBManager, dbutils
from ..autogenchat import AutoGenChatManager
from ..chatmanager import AutoGenChatManager
app = FastAPI()
@@ -54,7 +38,8 @@ app.add_middleware(
root_file_path = os.path.dirname(os.path.abspath(__file__))
folders = init_webserver_folders(root_file_path) # init folders skills, workdir, static, files etc
# init folders skills, workdir, static, files etc
folders = init_webserver_folders(root_file_path)
api = FastAPI(root_path="/api")
# mount an api route such that the main route serves the ui and the /api
@@ -72,32 +57,23 @@ chatmanager = AutoGenChatManager() # manage calls to autogen
@api.post("/messages")
async def add_message(req: ChatWebRequestModel):
message = Message(**req.message.dict())
user_history = load_messages(user_id=message.user_id, session_id=req.message.session_id, dbmanager=dbmanager)
user_history = dbutils.get_messages(user_id=message.user_id, session_id=req.message.session_id, dbmanager=dbmanager)
# save incoming message to db
save_message(message=message, dbmanager=dbmanager)
dbutils.create_message(message=message, dbmanager=dbmanager)
user_dir = os.path.join(folders["files_static_root"], "user", md5_hash(message.user_id))
os.makedirs(user_dir, exist_ok=True)
# load skills, append to chat
skills = get_all_skills(
os.path.join(folders["user_skills_dir"], md5_hash(message.user_id)),
folders["global_skills_dir"],
dest_dir=os.path.join(user_dir, "scratch"),
)
skills_prompt = get_skills_prompt(skills)
try:
response_message: Message = chatmanager.chat(
message=message,
history=user_history,
work_dir=user_dir,
skills_prompt=skills_prompt,
flow_config=req.flow_config,
)
# save assistant response to db
save_message(message=response_message, dbmanager=dbmanager)
dbutils.create_message(message=response_message, dbmanager=dbmanager)
response = {
"status": True,
"message": response_message.content,
@@ -113,11 +89,11 @@ async def add_message(req: ChatWebRequestModel):
@api.get("/messages")
def get_messages(user_id: str = None, session_id: str = None):
async def get_messages(user_id: str = None, session_id: str = None):
if user_id is None:
raise HTTPException(status_code=400, detail="user_id is required")
try:
user_history = load_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager)
user_history = dbutils.get_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager)
return {
"status": True,
@@ -133,9 +109,9 @@ def get_messages(user_id: str = None, session_id: str = None):
@api.get("/gallery")
def get_gallery_items(gallery_id: str = None):
async def get_gallery_items(gallery_id: str = None):
try:
gallery = get_gallery(gallery_id=gallery_id, dbmanager=dbmanager)
gallery = dbutils.get_gallery(gallery_id=gallery_id, dbmanager=dbmanager)
return {
"status": True,
"data": gallery,
@@ -150,13 +126,13 @@ def get_gallery_items(gallery_id: str = None):
@api.get("/sessions")
def get_user_sessions(user_id: str = None):
async def get_user_sessions(user_id: str = None):
"""Return a list of all sessions for a user"""
if user_id is None:
raise HTTPException(status_code=400, detail="user_id is required")
try:
user_sessions = get_sessions(user_id=user_id, dbmanager=dbmanager)
user_sessions = dbutils.get_sessions(user_id=user_id, dbmanager=dbmanager)
return {
"status": True,
@@ -178,14 +154,14 @@ async def create_user_session(req: DBWebRequestModel):
try:
session = Session(user_id=req.session.user_id, flow_config=req.session.flow_config)
user_sessions = create_session(user_id=req.user_id, session=session, dbmanager=dbmanager)
user_sessions = dbutils.create_session(user_id=req.user_id, session=session, dbmanager=dbmanager)
return {
"status": True,
"message": "Session created successfully",
"data": user_sessions,
}
except Exception as ex_error:
print(ex_error)
print(traceback.format_exc())
return {
"status": False,
"message": "Error occurred while creating session: " + str(ex_error),
@@ -195,10 +171,9 @@ async def create_user_session(req: DBWebRequestModel):
@api.post("/sessions/publish")
async def publish_user_session_to_gallery(req: DBWebRequestModel):
"""Create a new session for a user"""
print(req.session, "**********")
try:
gallery_item = publish_session(req.session, tags=req.tags, dbmanager=dbmanager)
gallery_item = dbutils.create_gallery(req.session, tags=req.tags, dbmanager=dbmanager)
return {
"status": True,
"message": "Session successfully published",
@@ -212,12 +187,31 @@ async def publish_user_session_to_gallery(req: DBWebRequestModel):
}
@api.delete("/sessions/delete")
async def delete_user_session(req: DBWebRequestModel):
"""Delete a session for a user"""
try:
sessions = dbutils.delete_session(session=req.session, dbmanager=dbmanager)
return {
"status": True,
"message": "Session deleted successfully",
"data": sessions,
}
except Exception as ex_error:
print(traceback.format_exc())
return {
"status": False,
"message": "Error occurred while deleting session: " + str(ex_error),
}
@api.post("/messages/delete")
async def remove_message(req: DeleteMessageWebRequestModel):
"""Delete a message from the database"""
try:
messages = delete_message(
messages = dbutils.delete_message(
user_id=req.user_id, msg_id=req.msg_id, session_id=req.session_id, dbmanager=dbmanager
)
return {
@@ -233,66 +227,28 @@ async def remove_message(req: DeleteMessageWebRequestModel):
}
@api.post("/cleardb")
async def clear_db(req: DBWebRequestModel):
"""Clear user conversation history database and files"""
# user_files_dir = os.path.join(folders["files_static_root"], "user", md5_hash(req.user_id))
# user_skills_dir = os.path.join(folders["user_skills_dir"], md5_hash(req.user_id))
# delete_files_in_folder([user_files_dir])
@api.get("/skills")
async def get_user_skills(user_id: str):
try:
delete_message(
user_id=req.user_id, msg_id=None, session_id=req.session.session_id, dbmanager=dbmanager, delete_all=True
)
sessions = delete_user_sessions(user_id=req.user_id, session_id=req.session.session_id, dbmanager=dbmanager)
skills = dbutils.get_skills(user_id, dbmanager=dbmanager)
return {
"status": True,
"data": {
"sessions": sessions,
},
"message": "Messages and files cleared successfully",
"message": "Skills retrieved successfully",
"data": skills,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while deleting message: " + str(ex_error),
"message": "Error occurred while retrieving skills: " + str(ex_error),
}
@api.get("/skills")
def get_skills(user_id: str):
skills = get_all_skills(os.path.join(folders["user_skills_dir"], md5_hash(user_id)), folders["global_skills_dir"])
return {
"status": True,
"message": "Skills retrieved successfully",
"data": skills,
}
@api.post("/skills")
def create_user_skills(req: CreateSkillWebRequestModel):
"""_summary_
Args:
user_id (str): the user id
code (str): code that represents the skill to be created
Returns:
_type_: dict
"""
user_skills_dir = os.path.join(folders["user_skills_dir"], md5_hash(req.user_id))
async def create_user_skills(req: DBWebRequestModel):
try:
create_skills_from_code(dest_dir=user_skills_dir, skills=req.skills)
skills = get_all_skills(
os.path.join(folders["user_skills_dir"], md5_hash(req.user_id)), folders["global_skills_dir"]
)
skills = dbutils.upsert_skill(skill=req.skill, dbmanager=dbmanager)
return {
"status": True,
@@ -306,3 +262,142 @@ def create_user_skills(req: CreateSkillWebRequestModel):
"status": False,
"message": "Error occurred while creating skills: " + str(ex_error),
}
@api.delete("/skills/delete")
async def delete_user_skills(req: DBWebRequestModel):
"""Delete a skill for a user"""
try:
skills = dbutils.delete_skill(req.skill, dbmanager=dbmanager)
return {
"status": True,
"message": "Skill deleted successfully",
"data": skills,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while deleting skill: " + str(ex_error),
}
@api.get("/agents")
async def get_user_agents(user_id: str):
try:
agents = dbutils.get_agents(user_id, dbmanager=dbmanager)
return {
"status": True,
"message": "Agents retrieved successfully",
"data": agents,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while retrieving agents: " + str(ex_error),
}
@api.post("/agents")
async def create_user_agents(req: DBWebRequestModel):
"""Create a new agent for a user"""
try:
agents = dbutils.upsert_agent(agent_flow_spec=req.agent, dbmanager=dbmanager)
return {
"status": True,
"message": "Agent created successfully",
"data": agents,
}
except Exception as ex_error:
print(traceback.format_exc())
return {
"status": False,
"message": "Error occurred while creating agent: " + str(ex_error),
}
@api.delete("/agents/delete")
async def delete_user_agent(req: DBWebRequestModel):
"""Delete an agent for a user"""
try:
agents = dbutils.delete_agent(agent=req.agent, dbmanager=dbmanager)
return {
"status": True,
"message": "Agent deleted successfully",
"data": agents,
}
except Exception as ex_error:
print(traceback.format_exc())
return {
"status": False,
"message": "Error occurred while deleting agent: " + str(ex_error),
}
@api.get("/workflows")
async def get_user_workflows(user_id: str):
try:
workflows = dbutils.get_workflows(user_id, dbmanager=dbmanager)
return {
"status": True,
"message": "Workflows retrieved successfully",
"data": workflows,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while retrieving workflows: " + str(ex_error),
}
@api.post("/workflows")
async def create_user_workflow(req: DBWebRequestModel):
"""Create a new workflow for a user"""
try:
workflow = dbutils.upsert_workflow(workflow=req.workflow, dbmanager=dbmanager)
return {
"status": True,
"message": "Workflow created successfully",
"data": workflow,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while creating workflow: " + str(ex_error),
}
@api.delete("/workflows/delete")
async def delete_user_workflow(req: DBWebRequestModel):
"""Delete a workflow for a user"""
try:
workflow = dbutils.delete_workflow(workflow=req.workflow, dbmanager=dbmanager)
return {
"status": True,
"message": "Workflow deleted successfully",
"data": workflow,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while deleting workflow: " + str(ex_error),
}

View File

@@ -2,6 +2,8 @@ from typing import List, Optional
from dataclasses import asdict
import autogen
from .datamodel import AgentFlowSpec, AgentWorkFlowConfig, Message
from .utils import get_skills_from_prompt, clear_folder
from datetime import datetime
class AutoGenWorkFlowManager:
@@ -14,7 +16,7 @@ class AutoGenWorkFlowManager:
config: AgentWorkFlowConfig,
history: Optional[List[Message]] = None,
work_dir: str = None,
assistant_prompt: str = None,
clear_work_dir: bool = True,
) -> None:
"""
Initializes the AutoGenFlow with agents specified in the config and optional
@@ -26,13 +28,29 @@ class AutoGenWorkFlowManager:
"""
self.work_dir = work_dir or "work_dir"
self.assistant_prompt = assistant_prompt or ""
if clear_work_dir:
clear_folder(self.work_dir)
self.sender = self.load(config.sender)
self.receiver = self.load(config.receiver)
self.agent_history = []
if history:
self.populate_history(history)
def process_reply(self, recipient, messages, sender, config):
if "callback" in config and config["callback"] is not None:
callback = config["callback"]
callback(sender, recipient, messages[-1])
iteration = {
"sender": sender.name,
"recipient": recipient.name,
"message": messages[-1],
"timestamp": datetime.now().isoformat(),
}
self.agent_history.append(iteration)
return False, None
def _sanitize_history_message(self, message: str) -> str:
"""
Sanitizes the message e.g. remove references to execution completed
@@ -86,6 +104,10 @@ class AutoGenWorkFlowManager:
agent_spec.config.is_termination_msg = agent_spec.config.is_termination_msg or (
lambda x: "TERMINATE" in x.get("content", "").rstrip()
)
skills_prompt = ""
if agent_spec.skills:
# get skill prompt, also write skills to a file named skills.py
skills_prompt = get_skills_from_prompt(agent_spec.skills, self.work_dir)
if agent_spec.type == "userproxy":
code_execution_config = agent_spec.config.code_execution_config or {}
@@ -98,7 +120,7 @@ class AutoGenWorkFlowManager:
+ "\n\n"
+ agent_spec.config.system_message
+ "\n\n"
+ self.assistant_prompt
+ skills_prompt
)
return agent_spec
@@ -117,8 +139,10 @@ class AutoGenWorkFlowManager:
agent_spec = self.sanitize_agent_spec(agent_spec)
if agent_spec.type == "assistant":
agent = autogen.AssistantAgent(**asdict(agent_spec.config))
agent.register_reply([autogen.Agent, None], reply_func=self.process_reply, config={"callback": None})
elif agent_spec.type == "userproxy":
agent = autogen.UserProxyAgent(**asdict(agent_spec.config))
agent.register_reply([autogen.Agent, None], reply_func=self.process_reply, config={"callback": None})
else:
raise ValueError(f"Unknown agent type: {agent_spec.type}")
return agent

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -7,7 +7,7 @@ require("dotenv").config({
const config: GatsbyConfig = {
pathPrefix: `${process.env.PREFIX_PATH_VALUE}`,
siteMetadata: {
title: `AutoGen Assistant`,
title: `AutoGen Studio`,
description: `Build Multi-Agent Apps`,
siteUrl: `http://tbd.place`,
},

View File

@@ -1,8 +1,8 @@
{
"name": "AutoGen_Assistant",
"name": "AutoGen_Studio",
"version": "1.0.0",
"private": true,
"description": "AutoGen Assistant - Build LLM Enabled Agents",
"description": "AutoGen Studio - Build LLM Enabled Agents",
"author": "SPIRAL Team",
"keywords": [
"gatsby"
@@ -10,7 +10,7 @@
"scripts": {
"develop": "gatsby clean && gatsby develop",
"start": "gatsby clean && gatsby develop",
"build": "gatsby clean && rm -rf ../autogenra/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && cp -r public/ ../autogenra/web/ui",
"build": "gatsby clean && rm -rf ../autogenstudio/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && cp -r public/ ../autogenstudio/web/ui",
"serve": "gatsby serve",
"clean": "gatsby clean",
"typecheck": "tsc --noEmit"

View File

@@ -8,16 +8,25 @@ import {
ArrowPathIcon,
ArrowDownRightIcon,
} from "@heroicons/react/24/outline";
import React, { ReactNode, useRef, useState } from "react";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import Icon from "./icons";
import { Button, Input, Modal, Tooltip, message } from "antd";
import { Button, Input, Modal, Select, Slider, Tooltip, message } from "antd";
import remarkGfm from "remark-gfm";
import ReactMarkdown from "react-markdown";
import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { truncateText } from "./utils";
import { IModelConfig } from "./types";
import { fetchJSON, getServerUrl, truncateText } from "./utils";
import {
IAgentFlowSpec,
IFlowConfig,
IModelConfig,
ISkill,
IStatus,
} from "./types";
import { ResizableBox } from "react-resizable";
import debounce from "lodash.debounce";
import TextArea from "antd/es/input/TextArea";
import { appContext } from "../hooks/provider";
interface CodeProps {
node?: any;
@@ -266,11 +275,15 @@ export const MessageBox = ({ title, children, className }: IProps) => {
);
};
export const GroupView = ({ children, title, className = "" }: any) => {
export const GroupView = ({
children,
title,
className = " bg-primary ",
}: any) => {
return (
<div className={`rounded mt-4 border-secondary ${className}`}>
<div className="mt-4 p-2 rounded border relative">
<div className="absolute -top-5 bg-primary p-2 inline-block">
<div className={`absolute -top-5 p-2 inline-block ${className}`}>
{title}
</div>
<div className="mt-2"> {children}</div>
@@ -730,6 +743,13 @@ export const ModelSelector = ({
value={newModelConfig?.api_type}
onChange={(e) => updateNewModelConfig("api_type", e.target.value)}
/>
<Input
className="mt-2"
placeholder="API Version (optional)"
value={newModelConfig?.api_version}
onChange={(e) => updateNewModelConfig("api_version", e.target.value)}
/>
</Modal>
</div>
);
@@ -841,3 +861,528 @@ export const PdfViewer = ({ url }: { url: string }) => {
</div>
);
};
export const AgentFlowSpecView = ({
title = "Agent Specification",
flowSpec,
setFlowSpec,
}: {
title: string;
flowSpec: IAgentFlowSpec;
setFlowSpec: (newFlowSpec: IAgentFlowSpec) => void;
editMode?: boolean;
}) => {
// Local state for the FlowView component
const [localFlowSpec, setLocalFlowSpec] =
React.useState<IAgentFlowSpec>(flowSpec);
// Event handlers for updating local state and propagating changes
const onControlChange = (value: any, key: string) => {
const updatedFlowSpec = {
...localFlowSpec,
config: { ...localFlowSpec.config, [key]: value },
};
setLocalFlowSpec(updatedFlowSpec);
setFlowSpec(updatedFlowSpec);
};
const onDebouncedControlChange = React.useCallback(
debounce((value: any, key: string) => {
onControlChange(value, key);
}, 3000),
[onControlChange]
);
const llm_config = localFlowSpec.config.llm_config || { config_list: [] };
return (
<>
<div className="text-accent">{title}</div>
<GroupView title={flowSpec.config.name} className="mb-4">
<ControlRowView
title="Agent Name"
className="mt-4"
description="Name of the agent"
value={flowSpec.config.name}
control={
<Input
className="mt-2"
placeholder="Agent Name"
value={flowSpec.config.name}
onChange={(e) => {
onControlChange(e.target.value, "name");
}}
/>
}
/>
<ControlRowView
title="Agent Description"
className="mt-4"
description="Description of the agent"
value={flowSpec.description || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={flowSpec.description}
onChange={(e) => {
const updatedFlowSpec = {
...localFlowSpec,
description: e.target.value,
};
setLocalFlowSpec(updatedFlowSpec);
setFlowSpec(updatedFlowSpec);
}}
/>
}
/>
<ControlRowView
title="Max Consecutive Auto Reply"
className="mt-4"
description="Max consecutive auto reply messages before termination."
value={flowSpec.config.max_consecutive_auto_reply}
control={
<Slider
min={2}
max={30}
defaultValue={flowSpec.config.max_consecutive_auto_reply}
step={1}
onAfterChange={(value: any) => {
onControlChange(value, "max_consecutive_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Human Input Mode"
description="Defines when to request human input"
value={flowSpec.config.human_input_mode}
control={
<Select
className="mt-2 w-full"
defaultValue={flowSpec.config.human_input_mode}
onChange={(value: any) => {
onControlChange(value, "human_input_mode");
}}
options={
[
{ label: "NEVER", value: "NEVER" },
{ label: "TERMINATE", value: "TERMINATE" },
{ label: "ALWAYS", value: "ALWAYS" },
] as any
}
/>
}
/>
{llm_config && (
<ControlRowView
title="System Message"
className="mt-4"
description="Free text to control agent behavior"
value={flowSpec.config.system_message}
control={
<TextArea
className="mt-2 w-full"
value={flowSpec.config.system_message}
rows={3}
onChange={(e) => {
onDebouncedControlChange(e.target.value, "system_message");
}}
/>
}
/>
)}
{llm_config && (
<ControlRowView
title="Model"
className="mt-4"
description="Defines which models are used for the agent."
value={llm_config?.config_list?.[0]?.model}
control={
<ModelSelector
className="mt-2 w-full"
configs={llm_config.config_list || []}
setConfigs={(config_list: IModelConfig[]) => {
const llm_config = {
...flowSpec.config.llm_config,
config_list,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
)}
{
<ControlRowView
title="Skills"
className="mt-4"
description="Defines skills available to the agent."
value={(flowSpec.skills && flowSpec.skills[0]?.title) || ""}
control={
<SkillSelector
className="mt-2 w-full"
skills={flowSpec.skills || []}
setSkills={(skills: ISkill[]) => {
const updatedFlowSpec = {
...localFlowSpec,
skills,
};
setLocalFlowSpec(updatedFlowSpec);
setFlowSpec(updatedFlowSpec);
}}
/>
}
/>
}
</GroupView>
</>
);
};
interface SkillSelectorProps {
skills: ISkill[];
setSkills: (skills: ISkill[]) => void;
className?: string;
}
export const SkillSelector: React.FC<SkillSelectorProps> = ({
skills,
setSkills,
className,
}) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [showSkillModal, setShowSkillModal] = React.useState(false);
const [newSkill, setNewSkill] = useState<ISkill | null>(null);
const [localSkills, setLocalSkills] = useState<ISkill[]>(skills);
const [selectedSkill, setSelectedSkill] = useState<ISkill | null>(null);
const handleRemoveSkill = (index: number) => {
const updatedSkills = localSkills.filter((_, i) => i !== index);
setLocalSkills(updatedSkills);
setSkills(updatedSkills);
};
const handleAddSkill = () => {
if (newSkill) {
const updatedSkills = [...localSkills, newSkill];
setLocalSkills(updatedSkills);
setSkills(updatedSkills);
setNewSkill(null);
}
};
useEffect(() => {
if (selectedSkill) {
setShowSkillModal(true);
}
}, [selectedSkill]);
return (
<>
<Modal
title={selectedSkill?.title}
width={800}
open={showSkillModal}
onOk={() => {
setShowSkillModal(false);
setSelectedSkill(null);
}}
onCancel={() => {
setShowSkillModal(false);
setSelectedSkill(null);
}}
>
{selectedSkill && (
<div>
<div className="mb-2">{selectedSkill.file_name}</div>
<CodeBlock code={selectedSkill?.content} language="python" />
</div>
)}
</Modal>
<div className={`${className} flex flex-wrap gap-2 `}>
{localSkills.map((skill, index) => (
<div
key={"skillitemrow" + index}
className=" mb-1 p-1 px-2 rounded border"
>
<span
role="button"
onClick={() => {
setSelectedSkill(skill);
}}
className=" inline-block "
>
{skill.title}
</span>
<XMarkIcon
role="button"
onClick={() => handleRemoveSkill(index)}
className="ml-1 text-primary hover:text-accent duration-300 w-4 h-4 inline-block"
/>
</div>
))}
<div
className="inline-flex mr-1 mb-1 p-1 px-2 rounded border hover:border-accent duration-300 hover:text-accent"
role="button"
onClick={() => {
setIsModalVisible(true);
}}
>
add <PlusIcon className="w-4 h-4 inline-block mt-1" />
</div>
</div>
<Modal
title="Add Skill"
open={isModalVisible}
onOk={handleAddSkill}
onCancel={() => setIsModalVisible(false)}
footer={[
<Button key="back" onClick={() => setIsModalVisible(false)}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
onClick={() => {
handleAddSkill();
setIsModalVisible(false);
}}
>
Add Skill
</Button>,
]}
>
<SkillLoader skill={newSkill} setSkill={setNewSkill} />
</Modal>
</>
);
};
export const SkillLoader = ({
skill,
setSkill,
}: {
skill: ISkill | null;
setSkill: (skill: ISkill | null) => void;
}) => {
const [skills, setSkills] = useState<ISkill[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`;
const fetchSkills = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setSkills(data.data);
if (data.data.length > 0) {
setSkill(data.data[0]);
}
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listSkillsUrl, payLoad, onSuccess, onError);
};
useEffect(() => {
fetchSkills();
}, []);
const skillOptions = skills.map((skill: ISkill, index: number) => ({
label: skill.title,
value: index,
}));
return (
<div className="relative">
<LoadingOverlay loading={loading} />
<ControlRowView
title="Skills"
description="Select an available skill"
value={skill?.title || skills[0]?.title}
control={
<>
{skills && (
<>
<Select
className="mt-2 w-full"
defaultValue={skill?.title || skills[0]?.title}
value={skill?.title || skills[0]?.title}
onChange={(value: any) => {
setSkill(skills[value]);
}}
options={skillOptions}
/>
{(skill || skills[0]) && (
<CodeBlock
className="mt-4"
code={skill?.content || skills[0]?.content || ""}
language="python"
/>
)}
</>
)}
</>
}
/>
</div>
);
};
export const FlowConfigViewer = ({
flowConfig,
setFlowConfig,
}: {
flowConfig: IFlowConfig;
setFlowConfig: (newFlowConfig: IFlowConfig) => void;
}) => {
// Local state for sender and receiver FlowSpecs
const [senderFlowSpec, setSenderFlowSpec] = React.useState<IAgentFlowSpec>(
flowConfig.sender
);
const [localFlowConfig, setLocalFlowConfig] =
React.useState<IFlowConfig>(flowConfig);
const [receiverFlowSpec, setReceiverFlowSpec] =
React.useState<IAgentFlowSpec>(flowConfig.receiver);
// Update the local state and propagate changes to the parent component
const updateSenderFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setSenderFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, sender: newFlowSpec });
};
const updateReceiverFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setReceiverFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, receiver: newFlowSpec });
};
const updateFlowConfigName = (newName: string) => {
const updatedFlowConfig = { ...localFlowConfig, name: newName };
setLocalFlowConfig(updatedFlowConfig);
setFlowConfig(updatedFlowConfig);
};
// React.useEffect(() => {
// setLocalFlowConfig(flowConfig);
// }, [flowConfig]);
return (
<>
{/* <div className="mb-2">{flowConfig.name}</div> */}
<ControlRowView
title="Workflow Name"
className="mt-4 mb-2"
description="Name of the workflow"
value={localFlowConfig.name}
control={
<Input
className="mt-2 w-full"
value={localFlowConfig.name}
onChange={(e) => updateFlowConfigName(e.target.value)}
/>
}
/>
<ControlRowView
title="Workflow Description"
className="mt-4 mb-2"
description="Description of the workflow"
value={localFlowConfig.description}
control={
<Input
className="mt-2 w-full"
value={localFlowConfig.description}
onChange={(e) => {
const updatedConfig = {
...localFlowConfig,
description: e.target.value,
};
setLocalFlowConfig(updatedConfig);
setFlowConfig(updatedConfig);
}}
/>
}
/>
<ControlRowView
title="Summary Method"
description="Defines the method to summarize the conversation"
value={localFlowConfig.summary_method || "last"}
control={
<Select
className="mt-2 w-full"
defaultValue={localFlowConfig.summary_method || "last"}
onChange={(value: any) => {
const updatedConfig = {
...localFlowConfig,
summary_method: value,
};
setLocalFlowConfig(updatedConfig);
setFlowConfig(updatedConfig);
}}
options={
[
{ label: "last", value: "last" },
{ label: "none", value: "none" },
{ label: "llm", value: "llm" },
] as any
}
/>
}
/>
<div className="flex gap-3 ">
<div className="w-1/2">
<div className="">
<AgentFlowSpecView
title="Sender"
flowSpec={senderFlowSpec}
setFlowSpec={updateSenderFlowSpec}
/>
</div>
</div>
<div className="w-1/2">
<AgentFlowSpecView
title="Receiver"
flowSpec={receiverFlowSpec}
setFlowSpec={updateReceiverFlowSpec}
/>
</div>
</div>
</>
);
};

View File

@@ -23,7 +23,8 @@ const Header = ({ meta, link }: any) => {
const user_id = user ? user.username : "unknown";
const links: any[] = [
{ name: "Home", href: "/" },
{ name: "Build", href: "/build" },
{ name: "Playground", href: "/" },
{ name: "Gallery", href: "/gallery" },
// { name: "Data Explorer", href: "/explorer" },
];

View File

@@ -34,7 +34,7 @@ export interface ILLMConfig {
export interface IAgentConfig {
name: string;
llm_config?: ILLMConfig | boolean;
llm_config?: ILLMConfig | false;
human_input_mode: string;
max_consecutive_auto_reply: number;
system_message: string | "";
@@ -45,13 +45,21 @@ export interface IAgentConfig {
export interface IAgentFlowSpec {
type: "assistant" | "userproxy" | "groupchat";
config: IAgentConfig;
timestamp?: string;
id?: string;
skills?: Array<ISkill>;
description?: string;
user_id?: string;
}
export interface IFlowConfig {
name: string;
description: string;
sender: IAgentFlowSpec;
receiver: IAgentFlowSpec;
type: "default" | "groupchat";
timestamp?: string;
summary_method?: "none" | "last" | "llm";
}
export interface IModelConfig {
@@ -71,9 +79,9 @@ export interface IMetadataFile {
}
export interface IChatSession {
session_id: string;
timestamp: string;
id: string;
user_id: string;
timestamp: string;
flow_config: IFlowConfig;
}
@@ -84,3 +92,13 @@ export interface IGalleryItem {
tags: Array<string>;
timestamp: string;
}
export interface ISkill {
title: string;
file_name: string;
content: string;
id?: string;
timestamp?: string;
description?: string;
user_id?: string;
}

View File

@@ -222,31 +222,13 @@ export const formatDuration = (seconds: number) => {
return parts.length > 0 ? parts.join(" ") : "0 sec";
};
export const getDefaultConfigFlows = () => {
export const sampleWorkflowConfig = () => {
const llm_model_config: IModelConfig[] = [
{
model: "gpt-4-1106-preview",
},
{
model: "gpt-3.5-turbo-16k",
},
{
model: "TheBloke/zephyr-7B-alpha-AWQ",
base_url: "http://localhost:8000/v1",
},
];
const llm_model_config_35turbo: IModelConfig = {
model: "gpt-3.5-turbo-16k",
};
const llm_config_35turbo: ILLMConfig = {
config_list: [llm_model_config_35turbo],
temperature: 0.1,
timeout: 600,
cache_seed: null,
};
const llm_config: ILLMConfig = {
config_list: llm_model_config,
temperature: 0.1,
@@ -279,38 +261,20 @@ export const getDefaultConfigFlows = () => {
"You are a helpful assistant that can use available functions when needed to solve problems. At each point, do your best to determine if the user's request has been addressed. IF THE REQUEST HAS NOT BEEN ADDRESSED, RESPOND WITH CODE TO ADDRESS IT. IF A FAILURE OCCURRED (e.g., due to a missing library) AND SOME ADDITIONAL CODE WAS WRITTEN (e.g. code to install the library), ENSURE THAT THE ORIGINAL CODE TO ADDRESS THE TASK STILL GETS EXECUTED. If the request HAS been addressed, respond with a summary of the result. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request ' or 'The tallest mountain in Africa is ..' etc. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE.",
};
const visualizationAssistantConfig: IAgentConfig = {
name: "visualization_assistant",
llm_config: llm_config,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 4,
system_message: `Your task is to ensure you generate a high quality visualization for the user. Your visualizations must follow best practices and you must articulate your reasoning for your choices. The visualization must not have grid or outline box. The visualization should have an APPROPRIATE ASPECT RATIO e..g rectangular for time series data. The title must be bold. Importantly, if THE CHART IS A LINE CHART, you MUST ADD ALINE OF BEST FIT and ADD TEXT ON THE SLOPE OF EACH LINE. Note that today's date is ${new Date().toLocaleDateString()}. At each point, do your best to determine if the user's request has been addressed and if so, respond with a summary. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request '. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE.`,
};
const visualizationAssistantFlowSpec: IAgentFlowSpec = {
type: "assistant",
config: visualizationAssistantConfig,
};
const assistantFlowSpec: IAgentFlowSpec = {
type: "assistant",
config: assistantConfig,
};
const GeneralFlowConfig: IFlowConfig = {
name: "General Agent Workflow",
const workFlowConfig: IFlowConfig = {
name: "Default Agent Workflow",
description: "Default Agent Workflow",
sender: userProxyFlowSpec,
receiver: assistantFlowSpec,
type: "default",
};
const VisualizationChatFlowConfig: IFlowConfig = {
name: "Visualization Agent Workflow",
sender: userProxyFlowSpec,
receiver: visualizationAssistantFlowSpec,
type: "default",
};
return [GeneralFlowConfig, VisualizationChatFlowConfig];
return workFlowConfig;
};
export const getModels = () => {
@@ -431,4 +395,9 @@ export const examplePrompts = [
prompt:
"List out the top 5 rivers in africa and their length and return that as a markdown table. Do not try to write any code, just write the table",
},
{
title: "Paint",
prompt:
"paint a picture of a glass of ethiopian coffee, freshly brewed in a tall glass cup, on a table right in front of a lush green forest scenery",
},
];

View File

@@ -0,0 +1,337 @@
import {
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Modal, message } from "antd";
import * as React from "react";
import { IAgentFlowSpec, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import { fetchJSON, getServerUrl, timeAgo, truncateText } from "../../utils";
import {
AgentFlowSpecView,
Card,
LaunchButton,
LoadingOverlay,
} from "../../atoms";
const AgentsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listAgentsUrl = `${serverUrl}/agents?user_id=${user?.email}`;
const saveAgentsUrl = `${serverUrl}/agents`;
const deleteAgentUrl = `${serverUrl}/agents/delete`;
const [agents, setAgents] = React.useState<IAgentFlowSpec[] | null>([]);
const [selectedAgent, setSelectedAgent] =
React.useState<IAgentFlowSpec | null>(null);
const [showNewAgentModal, setShowNewAgentModal] = React.useState(false);
const [showAgentModal, setShowAgentModal] = React.useState(false);
const sampleAgent: IAgentFlowSpec = {
type: "assistant",
description: "Sample assistant",
user_id: user?.email,
config: {
name: "sample_assistant",
llm_config: {
config_list: [
{
model: "gpt-4-1106-preview",
},
],
temperature: 0.1,
timeout: 600,
cache_seed: null,
},
human_input_mode: "NEVER",
max_consecutive_auto_reply: 8,
system_message: " ..",
},
};
const [newAgent, setNewAgent] = React.useState<IAgentFlowSpec | null>(
sampleAgent
);
const deleteAgent = (agent: IAgentFlowSpec) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
agent: agent,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
console.log("agents", data.data);
setAgents(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteAgentUrl, payLoad, onSuccess, onError);
};
const fetchAgent = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
console.log("agents", data.data);
setAgents(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listAgentsUrl, payLoad, onSuccess, onError);
};
const saveAgent = (agent: IAgentFlowSpec) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
agent: agent,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
// console.log("agents", data.data);
setAgents(data.data);
} else {
message.error(data.message);
}
setLoading(false);
setNewAgent(sampleAgent);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(saveAgentsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchAgent();
}
}, []);
React.useEffect(() => {
if (selectedAgent) {
console.log("selected agent", selectedAgent);
}
}, [selectedAgent]);
React.useEffect(() => {
if (newAgent) {
console.log("new agent", newAgent);
}
}, [newAgent]);
const agentRows = (agents || []).map((agent: IAgentFlowSpec, i: number) => {
return (
<div key={"agentrow" + i} className=" " style={{ width: "200px" }}>
<div className="h-full">
<Card
className="h-full p-2 cursor-pointer"
title={agent.config.name}
onClick={() => {
setSelectedAgent(agent);
setShowAgentModal(true);
}}
>
<div className="my-2">
{" "}
{truncateText(agent.description || "", 70)}
</div>
<div className="text-xs">{timeAgo(agent.timestamp || "")}</div>
</Card>
<div className="text-right mt-2">
<div
role="button"
className="text-accent text-xs inline-block"
onClick={() => {
deleteAgent(agent);
}}
>
<TrashIcon className=" w-5, h-5 cursor-pointer inline-block" />
<span className="text-xs"> delete</span>
</div>
</div>
</div>
</div>
);
});
const AgentModal = ({
agent,
setAgent,
showAgentModal,
setShowAgentModal,
handler,
}: {
agent: IAgentFlowSpec | null;
setAgent: (agent: IAgentFlowSpec | null) => void;
showAgentModal: boolean;
setShowAgentModal: (show: boolean) => void;
handler?: (agent: IAgentFlowSpec | null) => void;
}) => {
const [localAgent, setLocalAgent] = React.useState<IAgentFlowSpec | null>(
agent
);
return (
<Modal
title={
<>
Agent Specification{" "}
<span className="text-accent font-normal">
{agent?.config.name}
</span>{" "}
</>
}
width={800}
open={showAgentModal}
onOk={() => {
setAgent(null);
setShowAgentModal(false);
if (handler) {
handler(localAgent);
}
}}
onCancel={() => {
setAgent(null);
setShowAgentModal(false);
}}
>
{agent && (
<AgentFlowSpecView
title=""
flowSpec={localAgent || agent}
setFlowSpec={setLocalAgent}
/>
)}
{/* {JSON.stringify(localAgent)} */}
</Modal>
);
};
return (
<div className=" ">
<AgentModal
agent={selectedAgent}
setAgent={setSelectedAgent}
setShowAgentModal={setShowAgentModal}
showAgentModal={showAgentModal}
handler={(agent: IAgentFlowSpec | null) => {
if (agent) {
saveAgent(agent);
}
}}
/>
<AgentModal
agent={newAgent || sampleAgent}
setAgent={setNewAgent}
setShowAgentModal={setShowNewAgentModal}
showAgentModal={showNewAgentModal}
handler={(agent: IAgentFlowSpec | null) => {
if (agent) {
saveAgent(agent);
}
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Agents ({agentRows.length}){" "}
</div>
<LaunchButton
className="text-sm p-2 px-3"
onClick={() => {
setShowNewAgentModal(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Agent
</LaunchButton>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent that can reused in your agent workflow{" "}
{selectedAgent?.config.name}
</div>
{agents && agents.length > 0 && (
<div className="w-full relative">
<LoadingOverlay loading={loading} />
<div className=" flex flex-wrap gap-3">{agentRows}</div>
</div>
)}
{agents && agents.length === 0 && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No agents found. Please create a new agent.
</div>
)}
</div>
</div>
</div>
);
};
export default AgentsView;

View File

@@ -0,0 +1,68 @@
import * as React from "react";
import SkillsView from "./skills";
import AgentsView from "./agents";
import WorkflowView from "./workflow";
import { Tabs } from "antd";
import {
BugAntIcon,
Square2StackIcon,
Square3Stack3DIcon,
} from "@heroicons/react/24/outline";
const BuildView = () => {
return (
<div className=" ">
{/* <div className="mb-4 text-2xl">Build </div> */}
<div className="mb-6 text-sm text-secondary">
{" "}
Create skills, agents and workflows for building multiagent capabilities{" "}
</div>
<div className="mb-4">
{" "}
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="3"
tabPosition="left"
items={[
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Skills
</div>
),
key: "1",
children: <SkillsView />,
},
{
label: (
<>
<Square2StackIcon className="h-4 w-4 inline-block mr-1" />
Agents
</>
),
key: "2",
children: <AgentsView />,
},
{
label: (
<>
<Square3Stack3DIcon className="h-4 w-4 inline-block mr-1" />
Workflows
</>
),
key: "3",
children: <WorkflowView />,
},
]}
/>
</div>
<div></div>
</div>
);
};
export default BuildView;

View File

@@ -1,75 +1,66 @@
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Modal, message } from "antd";
import {
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Input, Modal, message } from "antd";
import * as React from "react";
import { IStatus } from "../../types";
import { ISkill, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getSampleSkill,
getServerUrl,
timeAgo,
truncateText,
} from "../../utils";
import {
CodeBlock,
CollapseBox,
LaunchButton,
LoadBox,
LoadingOverlay,
MarkdownView,
} from "../../atoms";
import { Card, CodeBlock, LaunchButton, LoadingOverlay } from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import TextArea from "antd/es/input/TextArea";
const SkillsView = ({ setMessages, skillup }: any) => {
const SkillsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const setSessions = useConfigStore((state) => state.setSessions);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const clearDbUrl = `${serverUrl}/cleardb`;
const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`;
const saveSkillsUrl = `${serverUrl}/skills/`;
const clearSkillsUrl = `${serverUrl}/skills/clear?user_id=${user?.email}`;
const saveSkillsUrl = `${serverUrl}/skills`;
const deleteSkillsUrl = `${serverUrl}/skills/delete`;
const [skills, setSkills] = React.useState<any>({});
const [skillsLoading, setSkillsLoading] = React.useState(false);
const [skills, setSkills] = React.useState<ISkill[] | null>([]);
const [selectedSkill, setSelectedSkill] = React.useState<any>(null);
const [showSkillModal, setShowSkillModal] = React.useState(false);
const [showNewSkillModal, setShowNewSkillModal] = React.useState(false);
const [newSkillTitle, setNewSkillTitle] = React.useState("");
const sampleSkill = getSampleSkill();
const [skillCode, setSkillCode] = React.useState(sampleSkill);
const session = useConfigStore((state) => state.session);
// console.log("skukkup", skillup);
const clearDb = () => {
const deleteSkill = (skill: ISkill) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
session: session,
skill: skill,
}),
};
console.log("payload", payLoad);
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
setMessages([]);
setSessions(data.data?.sessions);
setSkills(data.data);
} else {
message.error(data.message);
}
@@ -80,22 +71,12 @@ const SkillsView = ({ setMessages, skillup }: any) => {
message.error(err.message);
setLoading(false);
};
fetchJSON(clearDbUrl, payLoad, onSuccess, onError);
fetchJSON(deleteSkillsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
console.log("fetching messages", skillup.get);
//
if (skillup.get !== "default") {
fetchSkills();
}
}
}, [skillup.get]);
const fetchSkills = () => {
setError(null);
setSkillsLoading(true);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
@@ -105,20 +86,19 @@ const SkillsView = ({ setMessages, skillup }: any) => {
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
console.log("skills", data.data);
// console.log("skills", data.data);
setSkills(data.data);
} else {
message.error(data.message);
}
setSkillsLoading(false);
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setSkillsLoading(false);
setLoading(false);
};
fetchJSON(listSkillsUrl, payLoad, onSuccess, onError);
};
@@ -131,8 +111,15 @@ const SkillsView = ({ setMessages, skillup }: any) => {
return;
}
const skill: ISkill = {
title: newSkillTitle,
file_name: "skill.py",
content: skillCode,
user_id: user?.email,
};
setError(null);
setSkillsLoading(true);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
@@ -142,26 +129,25 @@ const SkillsView = ({ setMessages, skillup }: any) => {
},
body: JSON.stringify({
user_id: user?.email,
skills: skillCode,
skill: skill,
}),
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
console.log("skills", data.data);
// console.log("skills", data.data);
setSkills(data.data);
} else {
message.error(data.message);
}
setSkillsLoading(false);
setLoading(false);
setSkillCode("");
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setSkillsLoading(false);
setLoading(false);
};
fetchJSON(saveSkillsUrl, payLoad, onSuccess, onError);
};
@@ -173,69 +159,36 @@ const SkillsView = ({ setMessages, skillup }: any) => {
}
}, []);
let userSkills: any[] = [];
let globalSkills: any[] = [];
if (skills) {
userSkills = skills.user;
globalSkills = skills.global;
}
const showSkillRows = (
skills: any[],
title: string,
open: boolean = false
) => {
const skillrows = (skills || []).map((skill: any, i: number) => {
return (
<div
role="button"
const skillRows = (skills || []).map((skill: ISkill, i: number) => {
return (
<div key={"skillrow" + i} className=" " style={{ width: "200px" }}>
<Card
className="h-full p-2 cursor-pointer"
title={skill.title}
onClick={() => {
setSelectedSkill(skill);
setShowSkillModal(true);
}}
key={"skillrow" + i}
className="hover:bg-primary rounded p-2 rounded-b-none duration-300 text-primary text-sm border-b border-dashed py-1 break-all gap-2 "
title={skill?.docstring}
>
{" "}
<span className="font-semibold">{skill?.name}</span>
<div className="text-secondary">
{truncateText(skill.content, 50)}
<div className="my-2"> {truncateText(skill.content, 70)}</div>
<div className="text-xs">{timeAgo(skill.timestamp || "")}</div>
</Card>
<div className="text-right mt-2">
<div
role="button"
className="text-accent text-xs inline-block"
onClick={() => {
deleteSkill(skill);
}}
>
<TrashIcon className=" w-5, h-5 cursor-pointer inline-block" />
<span className="text-xs"> delete</span>
</div>
</div>
);
});
return (
<div className="mb-2">
<CollapseBox
open={open}
title={
<div className="font-semibold ">
{" "}
{title} ({skills.length})
</div>
}
>
<>
{skillrows}
{(!skills || skills.length == 0) && (
<div className=" rounded p-2 px-3 text-xs my-1">
{" "}
No {title} created yet.
</div>
)}
</>
</CollapseBox>
</div>
);
};
let windowHeight, skillsMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
skillsMaxHeight = windowHeight - 400 + "px";
}
});
return (
<div className=" ">
@@ -253,6 +206,7 @@ const SkillsView = ({ setMessages, skillup }: any) => {
{selectedSkill && (
<div>
<div className="mb-2">{selectedSkill.file_name}</div>
<CodeBlock code={selectedSkill?.content} language="python" />
</div>
)}
@@ -278,6 +232,13 @@ const SkillsView = ({ setMessages, skillup }: any) => {
<div className="mb-2">
Provide code for a new skill or create from current conversation.
</div>
<Input
className="mb-2"
placeholder="Skill Title"
onChange={(e) => {
setNewSkillTitle(e.target.value);
}}
/>
<TextArea
value={skillCode}
onChange={(e) => {
@@ -289,53 +250,44 @@ const SkillsView = ({ setMessages, skillup }: any) => {
</Modal>
<div className="mb-2 relative">
<LoadingOverlay loading={loading} />
<div
style={{
maxHeight: skillsMaxHeight,
}}
className="overflow-x-hidden scroll rounded "
>
<div className="font-semibold mb-2 pb-1 border-b"> Skills </div>
<div className="">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Skills ({skillRows.length}){" "}
</div>
<LaunchButton
className="text-sm p-2 px-3"
onClick={() => {
setShowNewSkillModal(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Skill
</LaunchButton>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Skills are python functions that agents can use to solve tasks.{" "}
</div>
{userSkills && <>{showSkillRows(userSkills, "User Skills")}</>}
{skills && skills.length > 0 && (
<div
// style={{ height: "400px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<div className=" flex flex-wrap gap-3">{skillRows}</div>
</div>
)}
{globalSkills && globalSkills.length > 0 && (
<>{showSkillRows(globalSkills, "Global Skills")}</>
{skills && skills.length === 0 && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No skills found. Please create a new skill.
</div>
)}
</div>
<div className="flex">
<div className="flex-1"></div>
<LaunchButton
className="text-sm p-2 px-3"
onClick={() => {
setShowNewSkillModal(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Skill
</LaunchButton>
</div>
</div>
<hr className="mb-2" />
<div
role="button"
className="inline-block text-xs hover:text-accent"
onClick={clearDb}
>
{!loading && (
<>
<TrashIcon className="w-5, h-5 inline-block mr-1" />
Clear Conversation
</>
)}
{loading && <LoadBox subtitle={"clearing db .."} />}
</div>
</div>
);

View File

@@ -0,0 +1,308 @@
import {
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Modal, message } from "antd";
import * as React from "react";
import { IFlowConfig, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sampleWorkflowConfig,
timeAgo,
truncateText,
} from "../../utils";
import {
Card,
FlowConfigViewer,
LaunchButton,
LoadingOverlay,
} from "../../atoms";
const WorkflowView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const saveWorkflowsUrl = `${serverUrl}/workflows`;
const deleteWorkflowsUrl = `${serverUrl}/workflows/delete`;
const [workflows, setWorkflows] = React.useState<IFlowConfig[] | null>([]);
const [selectedWorkflow, setSelectedWorkflow] =
React.useState<IFlowConfig | null>(null);
const defaultConfig = sampleWorkflowConfig();
const [newWorkflow, setNewWorkflow] = React.useState<IFlowConfig | null>(
defaultConfig
);
const [showWorkflowModal, setShowWorkflowModal] = React.useState(false);
const [showNewWorkflowModal, setShowNewWorkflowModal] = React.useState(false);
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
console.log("workflows", data.data);
setWorkflows(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
const deleteWorkFlow = (workflow: IFlowConfig) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
workflow: workflow,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setWorkflows(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteWorkflowsUrl, payLoad, onSuccess, onError);
};
const saveWorkFlow = (workflow: IFlowConfig) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
workflow: workflow,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
// console.log("workflows", data.data);
setWorkflows(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(saveWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchWorkFlow();
}
}, []);
React.useEffect(() => {
if (selectedWorkflow) {
setShowWorkflowModal(true);
}
}, [selectedWorkflow]);
const workflowRows = (workflows || []).map(
(workflow: IFlowConfig, i: number) => {
return (
<div key={"workflowrow" + i} className=" " style={{ width: "200px" }}>
<div className="h-full ">
{" "}
<Card
className="h-full block p-2 cursor-pointer"
title={workflow.name}
onClick={() => {
setSelectedWorkflow(workflow);
}}
>
<div className="my-2"> {truncateText(workflow.name, 70)}</div>
<div className="text-xs">{timeAgo(workflow.timestamp || "")}</div>
</Card>
<div className="text-right mt-2">
<div
role="button"
className="text-accent text-xs inline-block"
onClick={() => {
deleteWorkFlow(workflow);
}}
>
<TrashIcon className=" w-5, h-5 cursor-pointer inline-block" />
<span className="text-xs"> delete</span>
</div>
</div>
</div>
</div>
);
}
);
const WorkflowModal = ({
workflow,
setWorkflow,
showWorkflowModal,
setShowWorkflowModal,
handler,
}: {
workflow: IFlowConfig | null;
setWorkflow: (workflow: IFlowConfig | null) => void;
showWorkflowModal: boolean;
setShowWorkflowModal: (show: boolean) => void;
handler?: (workflow: IFlowConfig) => void;
}) => {
const [localWorkflow, setLocalWorkflow] =
React.useState<IFlowConfig | null>(workflow);
return (
<Modal
title={
<>
Agent Specification{" "}
<span className="text-accent font-normal">
{localWorkflow?.name}
</span>{" "}
</>
}
width={800}
open={showWorkflowModal}
onOk={() => {
setShowWorkflowModal(false);
if (handler) {
handler(localWorkflow as IFlowConfig);
}
}}
onCancel={() => {
setShowWorkflowModal(false);
setWorkflow(null);
}}
>
{localWorkflow && (
<FlowConfigViewer
flowConfig={localWorkflow}
setFlowConfig={setLocalWorkflow}
/>
)}
</Modal>
);
};
return (
<div className=" ">
<WorkflowModal
workflow={selectedWorkflow}
setWorkflow={setSelectedWorkflow}
showWorkflowModal={showWorkflowModal}
setShowWorkflowModal={setShowWorkflowModal}
handler={(workflow: IFlowConfig) => {
saveWorkFlow(workflow);
setShowWorkflowModal(false);
}}
/>
<WorkflowModal
workflow={newWorkflow}
setWorkflow={setNewWorkflow}
showWorkflowModal={showNewWorkflowModal}
setShowWorkflowModal={setShowNewWorkflowModal}
handler={(workflow: IFlowConfig) => {
saveWorkFlow(workflow);
setShowNewWorkflowModal(false);
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Workflows ({workflowRows.length}){" "}
</div>
<LaunchButton
className="-mt-2 text-sm p-2 px-3"
onClick={() => {
setShowNewWorkflowModal(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Workflow
</LaunchButton>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent workflow that can be used to handle tasks.
</div>
{workflows && workflows.length > 0 && (
<div
// style={{ minHeight: "500px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<div className="flex flex-wrap gap-3">{workflowRows}</div>
</div>
)}
{workflows && workflows.length === 0 && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No workflows found. Please create a new workflow.
</div>
)}
</div>
</div>
</div>
);
};
export default WorkflowView;

View File

@@ -9,7 +9,7 @@ import {
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import { navigate } from "gatsby";
import ChatBox from "../ra/chatbox";
import ChatBox from "../playground/chatbox";
const GalleryView = ({ location }: any) => {
const serverUrl = getServerUrl();

View File

@@ -26,7 +26,7 @@ const ChatBox = ({
editable = true,
}: {
initMessages: IMessage[] | null;
editable: boolean;
editable?: boolean;
}) => {
const session: IChatSession | null = useConfigStore((state) => state.session);
const queryInputRef = React.useRef<HTMLInputElement>(null);
@@ -44,7 +44,9 @@ const ChatBox = ({
const messages = useConfigStore((state) => state.messages);
const setMessages = useConfigStore((state) => state.setMessages);
const flowConfig: IFlowConfig = useConfigStore((state) => state.flowConfig);
const workflowConfig: IFlowConfig | null = useConfigStore(
(state) => state.workflowConfig
);
let pageHeight, chatMaxHeight;
if (typeof window !== "undefined") {
@@ -88,7 +90,7 @@ const ChatBox = ({
body: JSON.stringify({
user_id: user?.email,
msg_id: messageId,
session_id: session?.session_id,
session_id: session?.id,
}),
};
@@ -166,29 +168,29 @@ const ChatBox = ({
});
}
if (messages.length - 1 === i) {
// items.push({
// type: "divider",
// });
// if (messages.length - 1 === i) {
// // items.push({
// // type: "divider",
// // });
items.push({
label: (
<div
onClick={() => {
console.log("deleting", message);
deleteMessage(message.msg_id);
}}
>
<TrashIcon
title={"Delete message"}
className="h-4 w-4 mr-1 inline-block"
/>
Delete Message
</div>
),
key: "deletemessage",
});
}
// items.push({
// label: (
// <div
// onClick={() => {
// console.log("deleting", message);
// deleteMessage(message.msg_id);
// }}
// >
// <TrashIcon
// title={"Delete message"}
// className="h-4 w-4 mr-1 inline-block"
// />
// Delete Message
// </div>
// ),
// key: "deletemessage",
// });
// }
const menu = (
<Dropdown menu={{ items }} trigger={["click"]} placement="bottomRight">
@@ -227,7 +229,7 @@ const ChatBox = ({
} p-2 rounded ${css}`}
>
{" "}
{items.length > 0 && <div className=" ">{menu}</div>}
{items.length > 0 && editable && <div className=" ">{menu}</div>}
{isUser && (
<>
<div className="inline-block">{message.text}</div>
@@ -319,11 +321,9 @@ const ChatBox = ({
msg_id: userMessage.msg_id,
user_id: user?.email || "",
root_msg_id: "0",
session_id: session?.session_id || "",
session_id: session?.id || "",
};
console.log("messagePayload", messagePayload);
const textUrl = `${serverUrl}/messages`;
const postData = {
method: "POST",
@@ -333,7 +333,7 @@ const ChatBox = ({
},
body: JSON.stringify({
message: messagePayload,
flow_config: flowConfig,
flow_config: workflowConfig,
}),
};
setLoading(true);

View File

@@ -1,10 +1,15 @@
import { DocumentTextIcon, PhotoIcon } from "@heroicons/react/24/outline";
import {
DocumentTextIcon,
PhotoIcon,
VideoCameraIcon,
} from "@heroicons/react/24/outline";
import * as React from "react";
import {
CodeBlock,
CodeLoader,
CollapseBox,
ExpandView,
GroupView,
ImageLoader,
MarkdownView,
PdfViewer,
@@ -14,13 +19,13 @@ import { IMetadataFile } from "../../types";
import Icon from "../../icons";
const MetaDataView = ({ metadata }: { metadata: any | null }) => {
console.log(metadata);
const serverUrl = getServerUrl();
const renderFileContent = (file: IMetadataFile, i: number) => {
const file_type = file.extension;
const is_image = ["image"].includes(file.type);
const is_code = ["code"].includes(file.type);
const is_pdf = ["pdf"].includes(file.type);
const is_video = ["video"].includes(file.type);
const file_name = file.name || "unknown";
const file_path = file.path || "unknown";
@@ -56,6 +61,34 @@ const MetaDataView = ({ metadata }: { metadata: any | null }) => {
</div>
);
icon = fileView;
} else if (is_video) {
fileView = (
<div className="mb-2">
{fileTitle}
<video controls className="w-full rounded">
<source
src={`${serverUrl}/${file_path}`}
type={`video/${file_type}`}
/>
Your browser does not support the video tag.
</video>
</div>
);
// Update icon to show a video-related icon
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary "
>
<VideoCameraIcon className="h-14 w-14" />
</div>
</div>
);
} else if (is_code) {
fileView = (
<div className="h">
@@ -121,11 +154,23 @@ const MetaDataView = ({ metadata }: { metadata: any | null }) => {
const files = (metadata.files || []).map(renderFile);
const messages = (metadata.messages || []).map((message: any, i: number) => (
<div className="border-b border-dashed" key={"messagerow" + i}>
<MarkdownView data={message?.content} className="text-sm" />
</div>
));
const messages = (metadata.messages || []).map((message: any, i: number) => {
return (
<div className="borpder-b mb-2 border-dashed" key={"messagerow" + i}>
<GroupView
title={
<div className="rounded p-1 px-2 inline-block text-xs bg-secondary">
<span className="font-semibold">{message.sender}</span> ( to{" "}
{message.recipient} )
</div>
}
className="m"
>
<MarkdownView data={message.message?.content} className="text-sm" />
</GroupView>
</div>
);
});
const hasContent = files.length > 0;
const hasMessages = messages.length > 0;

View File

@@ -31,7 +31,7 @@ const RAView = () => {
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const fetchMessagesUrl = `${serverUrl}/messages?user_id=${user?.email}&session_id=${session?.session_id}`;
const fetchMessagesUrl = `${serverUrl}/messages?user_id=${user?.email}&session_id=${session?.id}`;
const fetchMessages = () => {
setError(null);
@@ -48,7 +48,6 @@ const RAView = () => {
const onSuccess = (data: any) => {
// console.log(data);
if (data && data.status) {
console.log("******* messages received ", data);
setMessages(data.data);
message.success(data.message);
} else {
@@ -75,19 +74,11 @@ const RAView = () => {
<div className="h-full ">
<div className="flex h-full ">
<div className=" mr-2 rounded">
<SideBarView
setMessages={setMessages}
skillup={skillup}
config={{ get: config, set: setConfig }}
/>
<SideBarView />
</div>
<div className=" flex-1 ">
{" "}
<ChatBox
config={{ get: config, set: setConfig }}
initMessages={messages}
skillup={skillup}
/>
<ChatBox initMessages={messages} />
</div>
</div>
</div>

View File

@@ -1,30 +1,16 @@
import {
ClockIcon,
GlobeAltIcon,
PlusIcon,
QueueListIcon,
Square3Stack3DIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Modal, message } from "antd";
import { message } from "antd";
import * as React from "react";
import { IChatSession, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getSampleSkill,
getServerUrl,
timeAgo,
truncateText,
} from "../../utils";
import {
CodeBlock,
CollapseBox,
LaunchButton,
LoadBox,
LoadingOverlay,
MarkdownView,
} from "../../atoms";
import { fetchJSON, getServerUrl, timeAgo, truncateText } from "../../utils";
import { LaunchButton, LoadingOverlay } from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import TextArea from "antd/es/input/TextArea";
const SessionsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
@@ -38,15 +24,48 @@ const SessionsView = ({}: any) => {
const listSessionUrl = `${serverUrl}/sessions?user_id=${user?.email}`;
const createSessionUrl = `${serverUrl}/sessions`;
const publishSessionUrl = `${serverUrl}/sessions/publish`;
const deleteSessionUrl = `${serverUrl}/sessions/delete`;
const sessions = useConfigStore((state) => state.sessions);
const flowConfig = useConfigStore((state) => state.flowConfig);
const workflowConfig = useConfigStore((state) => state.workflowConfig);
const setSessions = useConfigStore((state) => state.setSessions);
// const [session, setSession] =
// React.useState<IChatSession | null>(null);
const session = useConfigStore((state) => state.session);
const setSession = useConfigStore((state) => state.setSession);
const deleteSession = (session: IChatSession) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
session: session,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setSessions(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteSessionUrl, payLoad, onSuccess, onError);
};
const fetchSessions = () => {
setError(null);
setLoading(true);
@@ -59,10 +78,9 @@ const SessionsView = ({}: any) => {
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
console.log("sesssions", data);
// console.log("sesssions", data);
setSessions(data.data);
if (data.data && data.data.length === 0) {
createSession();
@@ -99,7 +117,6 @@ const SessionsView = ({}: any) => {
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
// setSessions(data.data);
@@ -132,7 +149,7 @@ const SessionsView = ({}: any) => {
session === null
? {
user_id: user?.email,
flow_config: flowConfig,
flow_config: workflowConfig,
session_id: null,
}
: session,
@@ -146,9 +163,7 @@ const SessionsView = ({}: any) => {
body: JSON.stringify(body),
};
console.log("payload", body);
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
setSessions(data.data);
@@ -173,12 +188,15 @@ const SessionsView = ({}: any) => {
}, []);
const sessionRows = sessions.map((data: IChatSession, index: number) => {
const isSelected = session?.session_id === data.session_id;
const isSelected = session?.id === data.id;
const rowClass = isSelected
? "bg-accent text-white"
: "bg-secondary text-primary";
return (
<div key={"sessionsrow" + index} className=" mb-2 pb-1 ">
<div
key={"sessionsrow" + index}
className=" mb-2 pb-1 border-b border-dashed "
>
<div
className={`rounded p-2 cursor-pointer ${rowClass}`}
role="button"
@@ -186,21 +204,30 @@ const SessionsView = ({}: any) => {
setSession(data);
}}
>
<div className="text-xs">{truncateText(data.session_id, 27)}</div>
<div className="text-xs">{truncateText(data.id, 27)}</div>
<div className="text-xs text-right ">{timeAgo(data.timestamp)} </div>
</div>
<div className="flex mt-1 text-secondary">
<div className="flex mt-2 text-secondary">
<div className="flex-1"></div>
<div
role="button"
onClick={() => {
deleteSession(data);
}}
className="text-xs px-2 hover:text-accent cursor-pointer"
>
<TrashIcon className="w-4 h-4 inline-block mr-1 " />
delete{" "}
</div>
<div
role="button"
onClick={() => {
console.log("pubish session", data);
publishSession();
}}
className="text-xs px-2 hover:text-accent cursor-pointer"
>
{" "}
<GlobeAltIcon className="w-4 h-4 inline-block mr-1 " />
publish{" "}
</div>
</div>
@@ -218,9 +245,11 @@ const SessionsView = ({}: any) => {
return (
<div className=" ">
<div className="mb-2 relative">
<LoadingOverlay loading={loading} />
<div className="">
<div className="font-semibold mb-2 pb-1 border-b">Sessions </div>
<div className="font-semibold mb-2 pb-1 border-b">
<Square3Stack3DIcon className="h-5 w-5 inline-block mr-1" />
Sessions{" "}
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Create a new session or select an existing session to view chat.
@@ -229,8 +258,9 @@ const SessionsView = ({}: any) => {
style={{
maxHeight: "300px",
}}
className="mb-4 overflow-y-scroll scroll rounded "
className="mb-4 overflow-y-scroll scroll rounded relative "
>
<LoadingOverlay loading={loading} />
{sessionRows}
</div>
{(!sessions || sessions.length == 0) && (

View File

@@ -1,10 +1,10 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import * as React from "react";
import SkillsView from "./skills";
import AgentsView from "./agents";
import AgentsView from "./workflows";
import SessionsView from "./sessions";
import { useConfigStore } from "../../../hooks/store";
const SideBarView = ({ setMessages, notify, skillup, config }: any) => {
const SideBarView = () => {
const [isOpen, setIsOpen] = React.useState(true);
const minWidth = isOpen ? "270px" : "50px";
@@ -14,12 +14,13 @@ const SideBarView = ({ setMessages, notify, skillup, config }: any) => {
sidebarMaxHeight = windowHeight - 180 + "px";
}
const workflowConfig = useConfigStore((state) => state.workflowConfig);
return (
<div
style={{
minWidth: minWidth,
maxWidth: minWidth,
// maxHeight: sidebarMaxHeight,
height: "calc(100vh - 190px)",
}}
className=" "
@@ -27,13 +28,7 @@ const SideBarView = ({ setMessages, notify, skillup, config }: any) => {
<div className=" transition overflow-hidden duration-300 flex flex-col h-full p-2 overflow-y-scroll scroll rounded ">
<div className={`${isOpen ? "" : "hidden"} `}>
<AgentsView />
<SessionsView config={config} />
<SkillsView
notify={notify}
setMessages={setMessages}
skillup={skillup}
config={config}
/>
{workflowConfig && <SessionsView />}
</div>
</div>
<div

View File

@@ -0,0 +1,118 @@
import { Select, message } from "antd";
import * as React from "react";
import { LoadingOverlay } from "../../atoms";
import { IFlowConfig, IStatus } from "../../types";
import { useConfigStore } from "../../../hooks/store";
import { fetchJSON, getServerUrl } from "../../utils";
import { appContext } from "../../../hooks/provider";
import { Link } from "gatsby";
import { Square2StackIcon } from "@heroicons/react/24/outline";
const AgentsWorkflowView = () => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const workflowConfig = useConfigStore((state) => state.workflowConfig);
const setWorkflowConfig = useConfigStore((state) => state.setWorkflowConfig);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const [workflowConfigs, setWorkflowConfigs] = React.useState<IFlowConfig[]>(
[]
);
const [selectedConfig, setSelectedConfig] = React.useState<number>(0);
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setWorkflowConfigs(data.data);
if (data.data.length > 0) {
setWorkflowConfig(data.data[0]);
}
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchWorkFlow();
}
}, []);
return (
<div className=" mb-4 relative">
<div className="font-semibold pb-2 border-b">
<Square2StackIcon className="h-5 w-5 inline-block mr-1" />
Workflow{" "}
</div>
<div className="text-xs mt-2 mb-2 pb-1 ">
{" "}
Select or create an agent workflow.{" "}
</div>
<div className="relative mt-2 ">
<LoadingOverlay loading={loading} />
{workflowConfigs && workflowConfigs.length > 0 && (
<Select
className="w-full"
value={workflowConfigs[selectedConfig].name}
onChange={(value: any) => {
setSelectedConfig(value);
setWorkflowConfig(workflowConfigs[value]);
}}
options={
workflowConfigs.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
)}
<div className="mt-2 text-xs">
{" "}
Create new workflows{" "}
<span className="text-accent">
{" "}
<Link to="/build">here</Link>
</span>{" "}
</div>
</div>
{!workflowConfigs ||
(workflowConfigs && workflowConfigs.length === 0 && (
<div className="p-1 border rounded text-xs px-2 text-secondary">
{" "}
No agent workflows found.
</div>
))}
</div>
);
};
export default AgentsWorkflowView;

View File

@@ -1,12 +1,10 @@
import { create } from "zustand";
import { getDefaultConfigFlows } from "../components/utils";
import { IChatMessage, IChatSession, IFlowConfig } from "../components/types";
interface ConfigState {
flowConfigs: IFlowConfig[];
setFlowConfigs: (flowConfigs: IFlowConfig[]) => void;
flowConfig: IFlowConfig;
setFlowConfig: (flowConfig: IFlowConfig) => void;
workflowConfig: IFlowConfig | null;
setWorkflowConfig: (flowConfig: IFlowConfig) => void;
messages: IChatMessage[] | null;
setMessages: (messages: IChatMessage[]) => void;
session: IChatSession | null;
@@ -16,10 +14,8 @@ interface ConfigState {
}
export const useConfigStore = create<ConfigState>()((set) => ({
flowConfigs: getDefaultConfigFlows(),
setFlowConfigs: (flowConfigs) => set({ flowConfigs }),
flowConfig: getDefaultConfigFlows()[0],
setFlowConfig: (flowConfig) => set({ flowConfig }),
workflowConfig: null,
setWorkflowConfig: (workflowConfig) => set({ workflowConfig }),
messages: null,
setMessages: (messages) => set({ messages }),
session: null,

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import BuildView from "../components/views/builder/build";
// markup
const IndexPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/build"}>
<main style={{ height: "100%" }} className=" h-full ">
<BuildView />
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default IndexPage;

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import RAView from "../components/views/ra/ra";
import RAView from "../components/views/playground/ra";
// markup
const IndexPage = ({ data }: any) => {

View File

@@ -32,8 +32,5 @@ module.exports = {
},
},
},
plugins: [
require("@tailwindcss/line-clamp"),
require("@tailwindcss/typography"),
],
plugins: [require("@tailwindcss/typography")],
};

View File

@@ -1,5 +1,6 @@
{
"name": "General Agent Workflow",
"description": "A general agent workflow",
"sender": {
"type": "userproxy",
"config": {

View File

@@ -4,9 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## AutoGen Assistant API\n",
"## AutoGen Studio Agent Workflow API Example\n",
"\n",
"This notebook focuses on demonstrating capabilities of the autogen assistant python api. \n",
"This notebook focuses on demonstrating capabilities of the autogen studio workflow python api. \n",
"\n",
"- Declarative Specification of an Agent Workflow \n",
"- Loading the specification and runnning the resulting agent\n",
@@ -17,13 +17,14 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"work_dir work_dir\n",
"userproxy (to primary_assistant):\n",
"\n",
"What is the height of the Eiffel Tower?\n",
@@ -31,7 +32,7 @@
"--------------------------------------------------------------------------------\n",
"primary_assistant (to userproxy):\n",
"\n",
"The Eiffel Tower, located in Paris, France, has a height of approximately 300 meters (984 feet) to the tip. Its height can vary slightly due to temperature changes that cause the metal to expand and contract. Including antennas, the total height of the Eiffel Tower is about 330 meters (1,083 feet). TERMINATE\n",
"The Eiffel Tower, located in Paris, France, has a height of approximately 300 meters (984 feet) to the tip. Its height can vary slightly due to temperature changes which cause the metal to expand and contract. Including antennas, the total height of the Eiffel Tower is about 330 meters (1,083 feet). TERMINATE\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
@@ -39,13 +40,13 @@
],
"source": [
"import json \n",
"from autogenra import AutoGenWorkFlowManager, FlowConfig \n",
"from autogenstudio import AutoGenWorkFlowManager, AgentWorkFlowConfig \n",
"\n",
"# load an agent specification in JSON \n",
"agent_spec= json.load(open('agent_spec.json'))\n",
"\n",
"# Creat a An AutoGen Workflow Configuration from the agent specification\n",
"agent_work_flow_config = FlowConfig(**agent_spec)\n",
"agent_work_flow_config = AgentWorkFlowConfig(**agent_spec)\n",
"\n",
"# Create a Workflow from the configuration \n",
"agent_work_flow = AutoGenWorkFlowManager(agent_work_flow_config) \n",
@@ -54,6 +55,13 @@
"task_query = \"What is the height of the Eiffel Tower?\"\n",
"agent_work_flow.run(message=task_query)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {

View File

@@ -3,11 +3,11 @@ requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "autogenra"
name = "autogenstudio"
authors = [
{ name="Autogen Team", email="autogen@microsoft.com" },
{ name="AutoGen Team", email="autogen@microsoft.com" },
]
description = "Autogen Assistant UI"
description = "AutoGen Studio"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.9"
@@ -35,16 +35,16 @@ include-package-data = true
[tool.setuptools.dynamic]
version = {attr = "autogenra.version.VERSION"}
version = {attr = "autogenstudio.version.VERSION"}
readme = {file = ["README.md"]}
[tool.setuptools.packages.find]
include = ["autogenra*"]
include = ["autogenstudio*"]
exclude = ["*.tests*"]
namespaces = false
[tool.setuptools.package-data]
"autogenra" = ["*.*"]
"autogenstudio" = ["*.*"]
[tool.pytest.ini_options]
filterwarnings = [
@@ -58,4 +58,4 @@ filterwarnings = [
"Bug Tracker" = "https://github.com/microsoft/autogen/issues"
[project.scripts]
autogenra = "autogenra.cli:run"
autogenstudio = "autogenstudio.cli:run"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

View File

@@ -1,176 +0,0 @@
---
title: "AutoGen Assistant: Interactively Explore Multi-Agent Workflows"
authors:
- victordibia
- gagb
- samershi
tags: [AutoGen, UI, web, UX]
---
![AutoGen Assistant solving a task with multiple agents that generate a pdf document with images.](img/autogenra_home.png)
<p align="center"><em>AutoGen Assistant solving a task with multiple agents that generate a pdf document.</em></p>
## TLDR
To help you rapidly prototype multi-agent solutions for your tasks, we are introducing AutoGen Assistant, an interface powered by [AutoGen](https://github.com/microsoft/autogen/tree/main/autogen). It allows you to:
- Declaratively define and modify agents and multi-agent workflows through a point and click, drag and drop interface (e.g., you can select the parameters of two agents that will communicate to solve your task).
- Use our UI to create chat sessions with the specified agents and view results (e.g., view chat history, generated files, and time taken).
- Explicitly add skills to your agents and accomplish more tasks.
- Publish your sessions to a local gallery.
AutoGen Assistant is [open source](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-assistant), give it a try!
## Introduction
The accelerating pace of technology has ushered us into an era where digital assistants (or agents) are becoming integral to our lives. [AutoGen](https://github.com/microsoft/autogen/tree/main/autogen) has emerged as a leading framework for orchestrating the power of agents. In the spirit of expanding this frontier and democratizing this capability, we are thrilled to introduce a new user-friendly interface: the **AutoGen Assistant**. Built upon the leading foundation of AutoGen and robust, modern web technologies like React.
With the AutoGen Assistant, users can rapidly create, manage, and interact with agents that can learn, adapt, and collaborate. As we release this interface into the open-source community, our ambition is not only to enhance productivity but to inspire a level of personalized interaction between humans and agents.
## Getting Started with AutoGen Assistant
The following guide will help you get the AutoGen Assistant up and running on your system.
### Configuring an LLM Provider
Before you install AutoGen Assistant, you need access to a language model. You can get this set up by following the steps in the AutoGen documentation [here](https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints). Configure your environment with either `OPENAI_API_KEY` or `AZURE_OPENAI_API_KEY`.
For example, in your terminal, you would set the API key like this:
```bash
export OPENAI_API_KEY=<your_api_key>
```
You can also specify the model directly in the agent's configuration as shown below.
```python
llm_config = LLMConfig(
config_list=[{
"model": "gpt-4",
"api_key": "<azure_api_key>",
"a": "<azure apis base>",
"api_type": "azure",
"api_version": "2023-06-01-preview"
}],
temperature=0,
)
```
### Installation
1. **Install from PyPi**
We recommend using a virtual environment (e.g., conda) to avoid conflicts with existing Python packages. With Python 3.10 or newer active in your virtual environment, use pip to install AutoGen Assistant:
```bash
pip install autogenra
```
2. **Install from Source**
> Note: This approach requires some familiarity with building interfaces in React.
If you prefer to install from source, ensure you have Python 3.10+ and Node.js (version above 14.15.0) installed. Here's how you get started:
- Clone the AutoGen Assistant repository and install its Python dependencies:
```bash
pip install -e .
```
- Navigate to the `samples/apps/autogen-assistant/frontend` directory, install dependencies, and build the UI:
```bash
npm install -g gatsby-cli
npm install --global yarn
cd frontend
yarn install
yarn build
```
For Windows users, to build the frontend, you may need alternative commands provided in the [autogen assistant readme](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-assistant).
For Windows users, to build the frontend, you may need alternative commands provided in the [AutoGen Assistant readme](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-assistant).
### Running the Application
Once installed, run the web UI by entering the following in your terminal:
```bash
autogenra ui --port 8081
```
This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Assistant.
Now that you have AutoGen Assistant installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills.
## What Can You Do with the AutoGen Assistant?
The AutoGen Assistant proposes some high-level concepts that help compose agents to solve tasks.
**Agent Workflow**: An agent workflow is a specification of a set of agents that can work together to accomplish a task. The simplest version of this is a setup with two agents a user proxy agent (that represents a user i.e. it compiles code and prints result) and an assistant that can address task requests (e.g., generating plans, writing code, evaluating responses, proposing error recovery steps, etc.). A more complex flow could be a group chat where even more agents work towards a solution.
![Specify Agents.](img/autogenra_config.png)
**Session**: A session refers to a period of continuous interaction or engagement with an agent workflow, typically characterized by a sequence of activities or operations aimed at achieving specific objectives. It includes the agent workflow configuration, the interactions between the user and the agents. A session can be “published” to a “gallery”.
**Skills**: Skills are functions (e.g., Python functions) that describe how to solve a task. In general, a good skill has a descriptive name (e.g. `generate_images`), extensive docstrings and good defaults (e.g., writing out files to disk for persistence and reuse). You can add new skills to the AutoGen Assistant via the provided UI. At inference time, these skills are made available to the assistant agent as they address your tasks.
AutoGen Assistant comes with 3 example skills: `fetch_profile`, `find_papers`, `generate_images`. Please feel free to review the repo to learn more about how they work.
![View and add skills.](img/autogenra_skills.png)
## The AutoGen Assistant API
While the AutoGen Assistant is a web interface, it is powered by an underlying python API that is reusable and modular. Importantly, we have implemented an API where agent workflows can be declaratively specified (in JSON), loaded and run. An example of the current API is shown below. Please consult the repo for latest updates.
```python
import json
from autogenra import AutoGenWorkFlowManager, FlowConfig
# load an agent specification in JSON
agent_spec = json.load(open('agent_spec.json'))
# Create an AutoGen Workflow Configuration from the agent specification
agent_work_flow_config = FlowConfig(**agent_spec)
# Create a Workflow from the configuration
agent_work_flow = AutoGenWorkFlowManager(agent_work_flow_config)
# Run the workflow on a task
task_query = "What is the height of the Eiffel Tower?"
agent_work_flow.run(message=task_query)
```
## Road Map and Next Steps
As we continue to develop and refine the AutoGen Assistant, the road map below outlines an array of enhancements and new features planned for future releases. Here's what users can look forward to:
- **Complex Agent Workflows**: We're working on integrating support for more sophisticated agent workflows, such as `GroupChat`, allowing for richer interaction between multiple agents or dynamic topologies.
- **Improved User Experience**: This includes features like streaming intermediate model output for real-time feedback, better summarization of agent responses, information on costs of each interaction. We will also invest in improving the workflow for composing and reusing agents. We will also explore support for more interactive human in the loop feedback to agents.
- **Expansion of Agent Skills**: We will work towards improving the workflow for authoring, composing and reusing agent skills.
- **Community Features**: Facilitation of sharing and collaboration within the AutoGen Assistant user community is a key goal. We're exploring options for sharing sessions and results more easily among users and contributing to a shared repository of skills, agents, and agent workflows.
## FAQs
**Q: How can I add more skills to the AutoGen Assistant?**
A: You can extend the capabilities of your agents by adding new Python functions. The AutoGen Assistant interface also lets you directly paste functions that can be reused in the agent workflow.
**Q: Where can I adjust the agent configurations and settings?**
A: You can modify agent configurations directly from the UI or by editing the default configurations in the `utils.py` file under the `get_default_agent_config()` method (assuming you are building your own UI).
**Q: If I want to reset the conversation with an agent, how do I go about it?**
A: To reset your conversation history, you can delete the `database.sqlite` file. If you need to clear user-specific data, remove the relevant `autogenra/web/files/user/<user_id_md5hash>` folder.
**Q: Is it possible to view the output and messages generated by the agents during interactions?**
A: Yes, you can view the generated messages in the debug console of the web UI, providing insights into the agent interactions. Alternatively, you can inspect the `database.sqlite` file for a comprehensive record of messages.
**Q: Where can I find documentation and support for AutoGen Assistant?**
A: Our GitHub repository hosts comprehensive documentation, including a detailed getting-started guide and an FAQ section. For additional support, you can raise issues on the repository or reach out to the community on forums and discussion boards associated with the project.

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -0,0 +1,208 @@
---
title: "AutoGen Studio: Interactively Explore Multi-Agent Workflows"
authors:
- victordibia
- gagb
- samershi
tags: [AutoGen, UI, web, UX]
---
![AutoGen Studio Playground View: Solving a task with multiple agents that generate a pdf document with images.](img/autogenstudio_home.png)
<p align="center"><em>AutoGen Studio: Solving a task with multiple agents that generate a pdf document with images.</em></p>
## TLDR
To help you rapidly prototype multi-agent solutions for your tasks, we are introducing AutoGen Studio, an interface powered by [AutoGen](https://github.com/microsoft/autogen/tree/main/autogen). It allows you to:
- Declaratively define and modify agents and multi-agent workflows through a point and click, drag and drop interface (e.g., you can select the parameters of two agents that will communicate to solve your task).
- Use our UI to create chat sessions with the specified agents and view results (e.g., view chat history, generated files, and time taken).
- Explicitly add skills to your agents and accomplish more tasks.
- Publish your sessions to a local gallery.
AutoGen Studio is open source [code here](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio), and can be installed via pip. Give it a try!
```bash
pip install autogenstudio
```
## Introduction
The accelerating pace of technology has ushered us into an era where digital assistants (or agents) are becoming integral to our lives. [AutoGen](https://github.com/microsoft/autogen/tree/main/autogen) has emerged as a leading framework for orchestrating the power of agents. In the spirit of expanding this frontier and democratizing this capability, we are thrilled to introduce a new user-friendly interface: **AutoGen Studio**.
With AutoGen Studio, users can rapidly create, manage, and interact with agents that can learn, adapt, and collaborate. As we release this interface into the open-source community, our ambition is not only to enhance productivity but to inspire a level of personalized interaction between humans and agents.
> **Note**: AutoGen Studio is meant to help you rapidly prototype multi-agent workflows and demonstrate an example of end user interfaces built with AutoGen. It is not meant to be a production-ready app.
## Getting Started with AutoGen Studio
The following guide will help you get AutoGen Studio up and running on your system.
### Configuring an LLM Provider
To get started, you need access to a language model. You can get this set up by following the steps in the AutoGen documentation [here](https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints). Configure your environment with either `OPENAI_API_KEY` or `AZURE_OPENAI_API_KEY`.
For example, in your terminal, you would set the API key like this:
```bash
export OPENAI_API_KEY=<your_api_key>
```
You can also specify the model directly in the agent's configuration as shown below.
```python
llm_config = LLMConfig(
config_list=[{
"model": "gpt-4",
"api_key": "<azure_api_key>",
"base_url": "<azure api base url>",
"api_type": "azure",
"api_version": "2023-06-01-preview"
}],
temperature=0,
)
```
### Installation
1. **Install from PyPi**
We recommend using a virtual environment (e.g., conda) to avoid conflicts with existing Python packages. With Python 3.10 or newer active in your virtual environment, use pip to install AutoGen Studio:
```bash
pip install autogenstudio
```
2. **Install from Source**
> Note: This approach requires some familiarity with building interfaces in React.
If you prefer to install from source, ensure you have Python 3.10+ and Node.js (version above 14.15.0) installed. Here's how you get started:
- Clone the AutoGen Studio repository and install its Python dependencies:
```bash
pip install -e .
```
- Navigate to the `samples/apps/autogen-studio/frontend` directory, install dependencies, and build the UI:
```bash
npm install -g gatsby-cli
npm install --global yarn
cd frontend
yarn install
yarn build
```
For Windows users, to build the frontend, you may need alternative commands provided in the [autogen studio readme](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio).
### Running the Application
Once installed, run the web UI by entering the following in your terminal:
```bash
autogenstudio ui --port 8081
```
This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Studio.
Now that you have AutoGen Studio installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills.
## What Can You Do with AutoGen Studio?
The AutoGen Studio UI is organized into 3 high level sections - **Build**, **Playground**, and **Gallery**.
### Build
![Specify Agents.](img/autogenstudio_config.png)
This section focuses on defining the properties of agents and agent workflows. It includes the following concepts:
**Skills**: Skills are functions (e.g., Python functions) that describe how to solve a task. In general, a good skill has a descriptive name (e.g. `generate_images`), extensive docstrings and good defaults (e.g., writing out files to disk for persistence and reuse). You can add new skills to AutoGen Studio via the provided UI. At inference time, these skills are made available to the assistant agent as they address your tasks.
![View and add skills.](img/autogenstudio_skills.png)
<p align="center"><em>AutoGen Studio Build View: View, add or edit skills that an agent can leverage in addressing tasks.</em></p>
**Agents**: This provides an interface to declaratively specify properties for an AutoGen agent (mirrors most of the members of a base [AutoGen conversable agent](https://github.com/microsoft/autogen/blob/main/autogen/agentchat/conversable_agent.py) class).
**Agent Workflows**: An agent workflow is a specification of a set of agents that can work together to accomplish a task. The simplest version of this is a setup with two agents a user proxy agent (that represents a user i.e. it compiles code and prints result) and an assistant that can address task requests (e.g., generating plans, writing code, evaluating responses, proposing error recovery steps, etc.). A more complex flow could be a group chat where even more agents work towards a solution.
### Playground
![AutoGen Studio Playground View: Solving a task with multiple agents that generate a pdf document with images.](img/autogenstudio_home.png)
<p align="center"><em>AutoGen Studio Playground View: Agents collaborate, use available skills (ability to generate images) to address a user task (generate pdf's).</em></p>
The playground section is focused on interacting with agent workflows defined in the previous build section. It includes the following concepts:
**Session**: A session refers to a period of continuous interaction or engagement with an agent workflow, typically characterized by a sequence of activities or operations aimed at achieving specific objectives. It includes the agent workflow configuration, the interactions between the user and the agents. A session can be “published” to a “gallery”.
**Chat View**: A chat is a sequence of interactions between a user and an agent. It is a part of a session.
### Gallery
This section is focused on sharing and reusing artifacts (e.g., workflow configurations, sessions, etc.).
AutoGen Studio comes with 3 example skills: `fetch_profile`, `find_papers`, `generate_images`. Please feel free to review the repo to learn more about how they work.
## The AutoGen Studio API
While AutoGen Studio is a web interface, it is powered by an underlying python API that is reusable and modular. Importantly, we have implemented an API where agent workflows can be declaratively specified (in JSON), loaded and run. An example of the current API is shown below. Please consult the [AutoGen Studio repo](https://microsoft.github.io/autogen/docs/autogenstudio) for more details.
```python
import json
from autogenstudio import AutoGenWorkFlowManager, AgentWorkFlowConfig
# load an agent specification in JSON
agent_spec = json.load(open('agent_spec.json'))
# Create an AutoGen Workflow Configuration from the agent specification
agent_work_flow_config = FlowConfig(**agent_spec)
# Create a Workflow from the configuration
agent_work_flow = AutoGenWorkFlowManager(agent_work_flow_config)
# Run the workflow on a task
task_query = "What is the height of the Eiffel Tower?"
agent_work_flow.run(message=task_query)
```
## Road Map and Next Steps
As we continue to develop and refine AutoGen Studio, the road map below outlines an array of enhancements and new features planned for future releases. Here's what users can look forward to:
- **Complex Agent Workflows**: We're working on integrating support for more sophisticated agent workflows, such as `GroupChat`, allowing for richer interaction between multiple agents or dynamic topologies.
- **Improved User Experience**: This includes features like streaming intermediate model output for real-time feedback, better summarization of agent responses, information on costs of each interaction. We will also invest in improving the workflow for composing and reusing agents. We will also explore support for more interactive human in the loop feedback to agents.
- **Expansion of Agent Skills**: We will work towards improving the workflow for authoring, composing and reusing agent skills.
- **Community Features**: Facilitation of sharing and collaboration within AutoGen Studio user community is a key goal. We're exploring options for sharing sessions and results more easily among users and contributing to a shared repository of skills, agents, and agent workflows.
### FAQ
**Q: Where can I adjust the default skills, agent and workflow configurations?**
A: You can modify agent configurations directly from the UI or by editing the `autogentstudio/utils/dbdefaults.json` file which is used to initialize the database.
**Q: If I want to reset the entire conversation with an agent, how do I go about it?**
A: To reset your conversation history, you can delete the `database.sqlite` file. If you need to clear user-specific data, remove the relevant `autogenstudio/web/files/user/<user_id_md5hash>` folder.
**Q: Is it possible to view the output and messages generated by the agents during interactions?**
A: Yes, you can view the generated messages in the debug console of the web UI, providing insights into the agent interactions. Alternatively, you can inspect the `database.sqlite` file for a comprehensive record of messages.
**Q: Where can I find documentation and support for AutoGen Studio?**
A: We are constantly working to improve AutoGen Studio. For the latest updates, please refer to the [AutoGen Studio Readme](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio). For additional support, please open an issue on [GitHub](https://github.com/microsoft/autogen) or ask questions on [Discord](https://discord.gg/pAbnFJrkgZ).
<br />