Skip to content
Open

Demo #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions python/packages/autogen-studio/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
FROM python:3.10-slim
WORKDIR /code

RUN pip install -U gunicorn autogenstudio
# 1. Install build-tools if you have any compiled deps
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*

# Create a non-root user
# 2. Create and switch to a non-root user
RUN useradd -m -u 1000 user
USER user
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH \
AUTOGENSTUDIO_APPDIR=/home/user/app

# 3. Copy your local package metadata first (for caching)
WORKDIR $HOME/app
COPY --chown=user . .

COPY --chown=user . $HOME/app
# 4. Install Gunicorn (in case your CLI/UI entrypoint uses it)
RUN pip install --upgrade pip \
&& pip install --user gunicorn uvicorn

CMD gunicorn -w $((2 * $(getconf _NPROCESSORS_ONLN) + 1)) --timeout 12600 -k uvicorn.workers.UvicornWorker autogenstudio.web.app:app --bind "0.0.0.0:8081"
# 5. Install your package in editable mode
RUN pip install --user -e .

# 6. Expose port and set the default CMD
EXPOSE 8081
CMD ["python", "-m", "gunicorn", \
"-w", "4", \
"--timeout", "12600", \
"-k", "uvicorn.workers.UvicornWorker", \
"autogenstudio.web.app:app", \
"--bind", "0.0.0.0:8081"]
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def create_default_gallery() -> GalleryConfig:
os.environ[key] = "test"

# url = "https://raw.githubusercontent.com/microsoft/autogen/refs/heads/main/python/packages/autogen-studio/autogenstudio/gallery/default.json"
builder = GalleryBuilder(id="gallery_default", name="Default Component Gallery")
builder = GalleryBuilder(id="collection_default", name="Default Component Collection")

# Set metadata
builder.set_metadata(
Expand Down
100 changes: 97 additions & 3 deletions python/packages/autogen-studio/autogenstudio/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import base64
from typing import Sequence

import io
import os
from typing import Sequence, Optional
from ..datamodel import Run
from autogen_agentchat.messages import ChatMessage, MultiModalMessage, TextMessage
from autogen_core import Image
from autogen_core.models import UserMessage
from loguru import logger

from azure.core.credentials import AzureKeyCredential
from azure.ai.formrecognizer import DocumentAnalysisClient

def _get_run(self, run_id: int) -> Optional[Run]:
"""Get run from database

Args:
run_id: id of the run to retrieve

Returns:
Optional[Run]: Run object if found, None otherwise
"""
response = self.db_manager.get(Run, filters={"id": run_id}, return_json=False)
return response.data[0] if response.status and response.data else None

def construct_task(query: str, files: list[dict] | None = None) -> Sequence[ChatMessage]:
def construct_task(query: str, files: list[dict] | None = None, env_vars: Optional[list[dict]] = None) -> Sequence[ChatMessage]:
"""
Construct a task from a query string and list of files.
Returns a list of ChatMessage objects suitable for processing by the agent system.
Expand All @@ -28,9 +44,25 @@ def construct_task(query: str, files: list[dict] | None = None) -> Sequence[Chat
if query:
messages.append(TextMessage(source="user", content=query))

for env_var in env_vars or []:
if env_var.name == "DOCUMENT_INTEL_ENDPOINT":
DOCUMENT_INTEL_ENDPOINT = env_var.value
elif env_var.name == "DOCUMENT_INTEL_KEY":
DOCUMENT_INTEL_KEY = env_var.value

# Process each file based on its type
for file in files:
try:
ftype = file.get("type", "")
fname = file.get("name", "unknown")
content_b64 = file["content"]

doc_intel_client = DocumentAnalysisClient(
endpoint=DOCUMENT_INTEL_ENDPOINT,
credential=AzureKeyCredential(DOCUMENT_INTEL_KEY),
)


if file.get("type", "").startswith("image/"):
# Handle image file using from_base64 method
# The content is already base64 encoded according to the convertFilesToBase64 function
Expand All @@ -48,6 +80,34 @@ def construct_task(query: str, files: list[dict] | None = None) -> Sequence[Chat
source="user", content=text_content, metadata={"filename": file.get("name", "unknown.txt")}
)
)
elif ftype == "application/pdf":
# 1) Decode the PDF bytes
pdf_bytes = base64.b64decode(content_b64)
stream = io.BytesIO(pdf_bytes)

# 2) Invoke Azure’s “prebuilt-document” model to get structured text
poller = doc_intel_client.begin_analyze_document(
"prebuilt-document",
document=stream
)
result = poller.result()

# 3) Concatenate everything into one big string (you could also
# chunk it by page or section, depending on your needs)
full_text = []
for page in result.pages:
for line in page.lines:
full_text.append(line.content)
document_text = "\n".join(full_text)

messages.append(
TextMessage(
source="user",
content=document_text,
metadata={"filename": fname, "filetype": ftype},
)
)

else:
# Log unsupported file types but still try to process based on best guess
logger.warning(f"Potentially unsupported file type: {file.get('type')} for file {file.get('name')}")
Expand All @@ -69,3 +129,37 @@ def construct_task(query: str, files: list[dict] | None = None) -> Sequence[Chat
# Continue processing other files even if one fails

return messages


def update_team_config(team_config: dict, tasks: Sequence[ChatMessage]) -> dict:
"""
Build and inject the selector_prompt into team_config based on incoming tasks.
Returns the updated team_config.
"""
cfg = team_config["config"]
template = cfg.get("selector_prompt", "You are a helpful assistant.")

# extract first matching TextMessage for context and form
context = next(
(t.content for t in tasks
if isinstance(t, TextMessage) and "context" in t.metadata.get("filename", "").lower()),
""
)
form = next(
(t.content for t in tasks
if isinstance(t, TextMessage) and "form" in t.metadata.get("filename", "").lower()),
""
)

# append any multimodal filenames
multimodal_files = [
t.metadata.get("filename", "unknown.file")
for t in tasks
if isinstance(t, MultiModalMessage)
]
if multimodal_files:
template += "\nIncluded files: " + ", ".join(multimodal_files)

# format and store back into the config
cfg["selector_prompt"] = template.format(context=context, form=form)
return team_config
20 changes: 17 additions & 3 deletions python/packages/autogen-studio/autogenstudio/web/routes/ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import asyncio
import json
from datetime import datetime
from typing import Optional

from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from fastapi.websockets import WebSocketState
from loguru import logger

from ...datamodel import Run, RunStatus
from ...utils.utils import construct_task
from ...datamodel import Run, RunStatus, Settings, SettingsConfig
from ...utils.utils import construct_task, update_team_config
from ..auth.dependencies import get_ws_auth_manager
from ..auth.wsauth import WebSocketAuthHandler
from ..deps import get_db, get_websocket_manager
Expand All @@ -35,6 +36,18 @@ async def run_websocket(
return

run = run_response.data[0]
async def _get_settings(user_id: str) -> Optional[Settings]:
"""Get user settings from database
Args:
user_id: User ID to retrieve settings for
Returns:
Optional[dict]: User settings if found, None otherwise
"""
response = db.get(filters={"user_id": user_id}, model_class=Settings, return_json=False)
return response.data[0] if response.status and response.data else None

user_settings = await _get_settings(run.user_id)
env_vars = SettingsConfig(**user_settings.config).environment if user_settings else None # type: ignore

if run.status not in [RunStatus.CREATED, RunStatus.ACTIVE]:
await websocket.close(code=4003, reason="Run not in valid state")
Expand Down Expand Up @@ -84,9 +97,10 @@ async def run_websocket(
if message.get("type") == "start":
# Handle start message
logger.info(f"Received start request for run {run_id}")
task = construct_task(query=message.get("task"), files=message.get("files"))
task = construct_task(query=message.get("task"), files=message.get("files"), env_vars=env_vars)

team_config = message.get("team_config")
team_config = update_team_config(team_config, task)
if task and team_config:
# Start the stream in a separate task
asyncio.create_task(ws_manager.start_stream(run_id, task, team_config))
Expand Down
7 changes: 4 additions & 3 deletions python/packages/autogen-studio/frontend/gatsby-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ require("dotenv").config({
path: envFile,
});


const config: GatsbyConfig = {
pathPrefix: process.env.PREFIX_PATH_VALUE || "",
siteMetadata: {
title: `AutoGen Studio`,
description: `Build Multi-Agent Apps`,
siteUrl: `http://tbd.place`,
title: `AI Planet Studio`,
description: `Build Multi-Agent Worflows & Apps`,
siteUrl: `http://www.aiplanet.com`,
},
// More easily incorporate content into your pages through automatic TypeScript type generation and better GraphQL IntelliSense.
// If you use VSCode you can also use the GraphQL plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ const Footer = () => {
return (
<div className=" text-primary p-3 border-t border-secondary flex ">
<div className="text-xs flex-1">
Maintained by the AutoGen{" "}
<a
Maintained by the AI Planet Team.{" "}
{/* <a
target={"_blank"}
rel={"noopener noreferrer"}
className="underlipne inline-block border-accent border-b hover:text-accent"
href="https://microsoft.github.io/autogen/"
>
{" "}
Team.
</a>
</a> */}
</div>
{version && (
<div className="text-xs ml-2 text-secondary"> v{version}</div>
Expand Down
Loading