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>
@@ -1,120 +0,0 @@
|
||||
# AutoGen Assistant
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
> Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background.
|
||||
|
||||
<!--  -->
|
||||
|
||||
## 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)
|
||||
@@ -1,3 +0,0 @@
|
||||
from .autogenflow import *
|
||||
from .autogenchat import *
|
||||
from .datamodel import *
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
VERSION = "0.0.09a"
|
||||
APP_NAME = "autogenra"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
Before Width: | Height: | Size: 198 KiB |
@@ -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;
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
recursive-include autogenra/web/ui *
|
||||
recursive-include autogenstudio/web/ui *
|
||||
recursive-exclude notebooks *
|
||||
recursive-exclude frontend *
|
||||
recursive-exclude docs *
|
||||
122
samples/apps/autogen-studio/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# AutoGen Studio
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
> Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background.
|
||||
|
||||
<!--  -->
|
||||
|
||||
## 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).
|
||||
3
samples/apps/autogen-studio/autogenstudio/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .chatmanager import *
|
||||
from .workflowmanager import *
|
||||
from .datamodel import *
|
||||
@@ -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()
|
||||
@@ -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():
|
||||
@@ -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
|
||||
189
samples/apps/autogen-studio/autogenstudio/utils/dbdefaults.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
671
samples/apps/autogen-studio/autogenstudio/utils/dbutils.py
Normal 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)
|
||||
@@ -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)
|
||||
3
samples/apps/autogen-studio/autogenstudio/version.py
Normal file
@@ -0,0 +1,3 @@
|
||||
VERSION = "0.0.18a"
|
||||
__version__ = VERSION
|
||||
APP_NAME = "autogenstudio"
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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
|
||||
BIN
samples/apps/autogen-studio/docs/ara_stockprices.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
@@ -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`,
|
||||
},
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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" },
|
||||
];
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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) && (
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
28
samples/apps/autogen-studio/frontend/src/pages/build.tsx
Normal 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;
|
||||
@@ -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) => {
|
||||
@@ -32,8 +32,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/line-clamp"),
|
||||
require("@tailwindcss/typography"),
|
||||
],
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "General Agent Workflow",
|
||||
"description": "A general agent workflow",
|
||||
"sender": {
|
||||
"type": "userproxy",
|
||||
"config": {
|
||||
@@ -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": {
|
||||
@@ -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"
|
||||
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 887 KiB |
|
Before Width: | Height: | Size: 165 KiB |
@@ -1,176 +0,0 @@
|
||||
---
|
||||
title: "AutoGen Assistant: Interactively Explore Multi-Agent Workflows"
|
||||
authors:
|
||||
- victordibia
|
||||
- gagb
|
||||
- samershi
|
||||
tags: [AutoGen, UI, web, UX]
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
<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.
|
||||
|
||||

|
||||
|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
|
After Width: | Height: | Size: 170 KiB |
BIN
website/blog/2023-12-01-AutoGenStudio/img/autogenstudio_home.png
Normal file
|
After Width: | Height: | Size: 802 KiB |
|
After Width: | Height: | Size: 150 KiB |
208
website/blog/2023-12-01-AutoGenStudio/index.mdx
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: "AutoGen Studio: Interactively Explore Multi-Agent Workflows"
|
||||
authors:
|
||||
- victordibia
|
||||
- gagb
|
||||
- samershi
|
||||
tags: [AutoGen, UI, web, UX]
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||
<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
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
<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
|
||||
|
||||

|
||||
<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 />
|
||||