Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a Tauri desktop app with Lynx UI for OpenAI Agent SDK #179

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,29 @@ We'd like to acknowledge the excellent work of the open-source community, especi
- [uv](https://github.com/astral-sh/uv) and [ruff](https://github.com/astral-sh/ruff)

We're committed to continuing to build the Agents SDK as an open source framework so others in the community can expand on our approach.

## Tauri Desktop App

A new Tauri project has been added in the `tauri-app` directory to create a desktop application for creating and handling AI agents in the OpenAI Agent SDK. The UI is built using Lynx and is designed to be user-friendly.

### Setup and Run

1. Install Tauri and set up a new Tauri project in the `tauri-app` directory. Refer to the Tauri documentation for detailed instructions.

2. Install Lynx in the Tauri project by adding it as a dependency in the `Cargo.toml` file.

3. Create a new Rust file in the Tauri project to handle the Lynx integration.

4. In your Tauri `src-tauri/src/main.rs` file, import the Lynx library and the new Rust file you created for the Lynx integration.

5. Modify the Tauri `src-tauri/src/main.rs` file to initialize and run the Lynx UI components when the Tauri application starts.

6. Update your Tauri frontend code to communicate with the Lynx UI components using Tauri's IPC (Inter-Process Communication) to send messages between the frontend and the Lynx components.

7. Implement features for creating, configuring, and managing agents, including instructions, models, tools, context, output types, handoffs, guardrails, and cloning.

8. Implement features for managing context, tracing, function tools, models, and debug logging as per the OpenAI Agent SDK.

9. Implement custom hooks for agent lifecycle events by subclassing the `AgentHooks` class and integrating them into the Tauri application.

10. Ensure the frontend code handles messages sent via IPC and updates the UI accordingly.
7 changes: 7 additions & 0 deletions tauri-app/src-tauri/src/lynx_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use lynx::Lynx;
use tauri::Window;

pub fn initialize_lynx(window: &Window) {
let lynx = Lynx::new();
lynx.run(window);
}
15 changes: 15 additions & 0 deletions tauri-app/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use tauri::Manager;
use lynx::Lynx;

mod lynx_integration;

fn main() {
tauri::Builder::default()
.setup(|app| {
let main_window = app.get_window("main").unwrap();
lynx_integration::initialize_lynx(&main_window);
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
36 changes: 36 additions & 0 deletions tauri-app/src/frontend/agent_hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AgentHooks } from 'openai-agents';

class CustomAgentHooks extends AgentHooks {
constructor(displayName) {
super();
this.eventCounter = 0;
this.displayName = displayName;
}

async on_start(context, agent) {
this.eventCounter += 1;
console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} started`);
}

async on_end(context, agent, output) {
this.eventCounter += 1;
console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} ended with output ${output}`);
}

async on_handoff(context, agent, source) {
this.eventCounter += 1;
console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${source.name} handed off to ${agent.name}`);
}

async on_tool_start(context, agent, tool) {
this.eventCounter += 1;
console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} started tool ${tool.name}`);
}

async on_tool_end(context, agent, tool, result) {
this.eventCounter += 1;
console.log(`### (${this.displayName}) ${this.eventCounter}: Agent ${agent.name} ended tool ${tool.name} with result ${result}`);
}
}

export default CustomAgentHooks;
54 changes: 54 additions & 0 deletions tauri-app/src/frontend/agent_management.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Agent, Runner, function_tool, handoff, GuardrailFunctionOutput, RunContextWrapper } from 'openai-agents';
import CustomAgentHooks from './agent_hooks';

const agents = {};

export function createAgent(agentData) {
const {
name,
instructions,
model,
tools,
context,
outputTypes,
handoffs,
guardrails,
cloning,
} = agentData;

const agent = new Agent({
name,
instructions,
model,
tools: tools.map(tool => function_tool(tool)),
context: new RunContextWrapper(context),
output_type: outputTypes,
handoffs: handoffs.map(handoffData => handoff(handoffData)),
input_guardrails: guardrails.input.map(guardrail => new GuardrailFunctionOutput(guardrail)),
output_guardrails: guardrails.output.map(guardrail => new GuardrailFunctionOutput(guardrail)),
hooks: new CustomAgentHooks(name),
});

if (cloning) {
agent.clone();
}

agents[name] = agent;
}

export function runAgent(agentName, input) {
const agent = agents[agentName];
if (!agent) {
throw new Error(`Agent with name ${agentName} does not exist.`);
}

return Runner.run(agent, input);
}

export function getAgent(agentName) {
return agents[agentName];
}

export function getAllAgents() {
return Object.values(agents);
}
30 changes: 30 additions & 0 deletions tauri-app/src/frontend/chat_interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { runAgent } from './agent_management';

const chatContainer = document.getElementById('chat-container');
const chatInput = document.getElementById('chat-input');
const chatForm = document.getElementById('chat-form');

chatForm.addEventListener('submit', async (event) => {
event.preventDefault();

const userMessage = chatInput.value;
appendMessage('User', userMessage);

try {
const agentResponse = await runAgent('chat-agent', userMessage);
appendMessage('Agent', agentResponse);
} catch (error) {
console.error('Error running agent:', error);
appendMessage('Error', 'Failed to get response from agent.');
}

chatInput.value = '';
});

function appendMessage(sender, message) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', sender.toLowerCase());
messageElement.innerText = `${sender}: ${message}`;
chatContainer.appendChild(messageElement);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
67 changes: 67 additions & 0 deletions tauri-app/src/frontend/configuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { validate } from 'pydantic';
import { GuardrailFunctionOutput, InputGuardrail, OutputGuardrail } from 'openai-agents';

export function configureAgentGuardrails(agent, inputGuardrails, outputGuardrails) {
agent.input_guardrails = inputGuardrails.map(guardrail => new InputGuardrail(guardrail));
agent.output_guardrails = outputGuardrails.map(guardrail => new OutputGuardrail(guardrail));
}

export function configureAgentHandoffs(agent, handoffs) {
agent.handoffs = handoffs.map(handoffData => handoff(handoffData));
}

export function configureAgentTools(agent, tools) {
agent.tools = tools.map(tool => function_tool(tool));
}

export function provideAgentTemplates() {
return [
{
name: 'Basic Agent',
instructions: 'You are a basic agent.',
model: 'text-davinci-003',
tools: [],
context: {},
outputTypes: 'text',
handoffs: [],
guardrails: {
input: [],
output: [],
},
cloning: false,
},
{
name: 'Advanced Agent',
instructions: 'You are an advanced agent with multiple tools and guardrails.',
model: 'text-davinci-003',
tools: ['tool1', 'tool2'],
context: {},
outputTypes: 'json',
handoffs: ['handoff1', 'handoff2'],
guardrails: {
input: ['inputGuardrail1', 'inputGuardrail2'],
output: ['outputGuardrail1', 'outputGuardrail2'],
},
cloning: true,
},
];
}

export function validateAgentConfiguration(agentData) {
const schema = {
name: 'string',
instructions: 'string',
model: 'string',
tools: 'array',
context: 'object',
outputTypes: 'string',
handoffs: 'array',
guardrails: {
input: 'array',
output: 'array',
},
cloning: 'boolean',
};

return validate(agentData, schema);
}
73 changes: 73 additions & 0 deletions tauri-app/src/frontend/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { getAllAgents, runAgent, createAgent } from './agent_management';

document.addEventListener("DOMContentLoaded", () => {
const agentsList = document.getElementById("agents-list");

function displayAgents() {
const agents = getAllAgents();
agentsList.innerHTML = "";

agents.forEach(agent => {
const agentItem = document.createElement("div");
agentItem.className = "agent-item";
agentItem.innerHTML = `
<h3>${agent.name}</h3>
<p>Status: ${agent.status}</p>
<p>Configuration: ${JSON.stringify(agent.configuration)}</p>
<p>Recent Activities: ${agent.recentActivities.join(", ")}</p>
<button class="edit-agent" data-name="${agent.name}">Edit</button>
<button class="clone-agent" data-name="${agent.name}">Clone</button>
<button class="delete-agent" data-name="${agent.name}">Delete</button>
`;
agentsList.appendChild(agentItem);
});
}

function searchAgents(query) {
const agents = getAllAgents();
const filteredAgents = agents.filter(agent =>
agent.name.includes(query) ||
agent.model.includes(query) ||
agent.status.includes(query)
);
agentsList.innerHTML = "";

filteredAgents.forEach(agent => {
const agentItem = document.createElement("div");
agentItem.className = "agent-item";
agentItem.innerHTML = `
<h3>${agent.name}</h3>
<p>Status: ${agent.status}</p>
<p>Configuration: ${JSON.stringify(agent.configuration)}</p>
<p>Recent Activities: ${agent.recentActivities.join(", ")}</p>
<button class="edit-agent" data-name="${agent.name}">Edit</button>
<button class="clone-agent" data-name="${agent.name}">Clone</button>
<button class="delete-agent" data-name="${agent.name}">Delete</button>
`;
agentsList.appendChild(agentItem);
});
}

function handleAgentActions(event) {
const target = event.target;
const agentName = target.getAttribute("data-name");

if (target.classList.contains("edit-agent")) {
// Handle edit agent
} else if (target.classList.contains("clone-agent")) {
const agent = getAgent(agentName);
createAgent({ ...agent, name: `${agent.name}-clone` });
displayAgents();
} else if (target.classList.contains("delete-agent")) {
// Handle delete agent
}
}

document.getElementById("search-agent").addEventListener("input", (event) => {
searchAgents(event.target.value);
});

agentsList.addEventListener("click", handleAgentActions);

displayAgents();
});
63 changes: 63 additions & 0 deletions tauri-app/src/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Agent Management</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<header>
<h1>AI Agent Management</h1>
</header>
<main>
<section id="agent-creation">
<h2>Create Agent</h2>
<form id="create-agent-form">
<label for="agent-name">Agent Name:</label>
<input type="text" id="agent-name" name="agent-name" required>

<label for="agent-instructions">Instructions:</label>
<textarea id="agent-instructions" name="agent-instructions" required></textarea>

<label for="agent-model">Model:</label>
<input type="text" id="agent-model" name="agent-model" required>

<label for="agent-tools">Tools:</label>
<input type="text" id="agent-tools" name="agent-tools" required>

<label for="agent-context">Context:</label>
<textarea id="agent-context" name="agent-context" required></textarea>

<label for="agent-output-types">Output Types:</label>
<input type="text" id="agent-output-types" name="agent-output-types" required>

<label for="agent-handoffs">Handoffs:</label>
<input type="text" id="agent-handoffs" name="agent-handoffs" required>

<label for="agent-guardrails">Guardrails:</label>
<input type="text" id="agent-guardrails" name="agent-guardrails" required>

<label for="agent-cloning">Cloning:</label>
<input type="text" id="agent-cloning" name="agent-cloning" required>

<button type="submit">Create Agent</button>
</form>
</section>
<section id="agent-management">
<h2>Manage Agents</h2>
<div id="agents-list">
<!-- List of agents will be dynamically populated here -->
</div>
</section>
</main>
<footer>
<p>&copy; 2023 AI Agent Management</p>
</footer>
</div>
<script src="main.js"></script>
<script src="agent_hooks.js"></script>
<script src="agent_management.js"></script>
</body>
</html>
Loading