diff --git a/README.md b/README.md index 210f6f4b..1b0966d8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/tauri-app/src-tauri/src/lynx_integration.rs b/tauri-app/src-tauri/src/lynx_integration.rs new file mode 100644 index 00000000..88b059b5 --- /dev/null +++ b/tauri-app/src-tauri/src/lynx_integration.rs @@ -0,0 +1,7 @@ +use lynx::Lynx; +use tauri::Window; + +pub fn initialize_lynx(window: &Window) { + let lynx = Lynx::new(); + lynx.run(window); +} diff --git a/tauri-app/src-tauri/src/main.rs b/tauri-app/src-tauri/src/main.rs new file mode 100644 index 00000000..99ec4086 --- /dev/null +++ b/tauri-app/src-tauri/src/main.rs @@ -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"); +} diff --git a/tauri-app/src/frontend/agent_hooks.js b/tauri-app/src/frontend/agent_hooks.js new file mode 100644 index 00000000..1252d4b2 --- /dev/null +++ b/tauri-app/src/frontend/agent_hooks.js @@ -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; diff --git a/tauri-app/src/frontend/agent_management.js b/tauri-app/src/frontend/agent_management.js new file mode 100644 index 00000000..c20934aa --- /dev/null +++ b/tauri-app/src/frontend/agent_management.js @@ -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); +} diff --git a/tauri-app/src/frontend/chat_interface.js b/tauri-app/src/frontend/chat_interface.js new file mode 100644 index 00000000..8bcbcf44 --- /dev/null +++ b/tauri-app/src/frontend/chat_interface.js @@ -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; +} diff --git a/tauri-app/src/frontend/configuration.js b/tauri-app/src/frontend/configuration.js new file mode 100644 index 00000000..7fda9fde --- /dev/null +++ b/tauri-app/src/frontend/configuration.js @@ -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); +} diff --git a/tauri-app/src/frontend/dashboard.js b/tauri-app/src/frontend/dashboard.js new file mode 100644 index 00000000..a6c8bf76 --- /dev/null +++ b/tauri-app/src/frontend/dashboard.js @@ -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(); +}); diff --git a/tauri-app/src/frontend/index.html b/tauri-app/src/frontend/index.html new file mode 100644 index 00000000..a6a7c625 --- /dev/null +++ b/tauri-app/src/frontend/index.html @@ -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>© 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> diff --git a/tauri-app/src/frontend/main.js b/tauri-app/src/frontend/main.js new file mode 100644 index 00000000..5ad07aed --- /dev/null +++ b/tauri-app/src/frontend/main.js @@ -0,0 +1,39 @@ +const { invoke } = window.__TAURI__.tauri; + +document.addEventListener("DOMContentLoaded", () => { + const createAgentForm = document.getElementById("create-agent-form"); + + createAgentForm.addEventListener("submit", async (event) => { + event.preventDefault(); + + const agentName = document.getElementById("agent-name").value; + const agentInstructions = document.getElementById("agent-instructions").value; + const agentModel = document.getElementById("agent-model").value; + const agentTools = document.getElementById("agent-tools").value; + const agentContext = document.getElementById("agent-context").value; + const agentOutputTypes = document.getElementById("agent-output-types").value; + const agentHandoffs = document.getElementById("agent-handoffs").value; + const agentGuardrails = document.getElementById("agent-guardrails").value; + const agentCloning = document.getElementById("agent-cloning").value; + + const agentData = { + name: agentName, + instructions: agentInstructions, + model: agentModel, + tools: agentTools, + context: agentContext, + outputTypes: agentOutputTypes, + handoffs: agentHandoffs, + guardrails: agentGuardrails, + cloning: agentCloning, + }; + + try { + await invoke("create_agent", { agentData }); + alert("Agent created successfully!"); + } catch (error) { + console.error("Error creating agent:", error); + alert("Failed to create agent."); + } + }); +}); diff --git a/tauri-app/src/frontend/notifications.js b/tauri-app/src/frontend/notifications.js new file mode 100644 index 00000000..3d2dd8e4 --- /dev/null +++ b/tauri-app/src/frontend/notifications.js @@ -0,0 +1,12 @@ +import { emit, listen } from '@tauri-apps/api/event'; + +export function notifyAgentLifecycleEvent(eventType, agentName, details) { + emit('agent-lifecycle-event', { eventType, agentName, details }); +} + +export function setupAgentLifecycleEventListener(callback) { + listen('agent-lifecycle-event', (event) => { + const { eventType, agentName, details } = event.payload; + callback(eventType, agentName, details); + }); +} diff --git a/tauri-app/src/frontend/visual_representation.js b/tauri-app/src/frontend/visual_representation.js new file mode 100644 index 00000000..dde3e55c --- /dev/null +++ b/tauri-app/src/frontend/visual_representation.js @@ -0,0 +1,23 @@ +import { getAllAgents } from './agent_management'; + +document.addEventListener("DOMContentLoaded", () => { + const visualRepresentationContainer = document.getElementById("visual-representation"); + + function displayVisualRepresentation() { + const agents = getAllAgents(); + visualRepresentationContainer.innerHTML = ""; + + agents.forEach(agent => { + const agentItem = document.createElement("div"); + agentItem.className = "agent-item"; + agentItem.innerHTML = ` + <h3>${agent.name}</h3> + <p>Tools: ${agent.tools.map(tool => tool.name).join(", ")}</p> + <p>Handoffs: ${agent.handoffs.map(handoff => handoff.name).join(", ")}</p> + `; + visualRepresentationContainer.appendChild(agentItem); + }); + } + + displayVisualRepresentation(); +});