feat: Implement Trillium notes executor for searching and creating notes via ETAPI

- Added `trillium.py` for searching and creating notes with Trillium's ETAPI.
- Implemented `search_notes` and `create_note` functions with appropriate error handling and validation.

feat: Add web search functionality using DuckDuckGo

- Introduced `web_search.py` for performing web searches without API keys.
- Implemented `search_web` function with result handling and validation.

feat: Create provider-agnostic function caller for iterative tool calling

- Developed `function_caller.py` to manage LLM interactions with tools.
- Implemented iterative calling logic with error handling and tool execution.

feat: Establish a tool registry for managing available tools

- Created `registry.py` to define and manage tool availability and execution.
- Integrated feature flags for enabling/disabling tools based on environment variables.

feat: Implement event streaming for tool calling processes

- Added `stream_events.py` to manage Server-Sent Events (SSE) for tool calling.
- Enabled real-time updates during tool execution for enhanced user experience.

test: Add tests for tool calling system components

- Created `test_tools.py` to validate functionality of code execution, web search, and tool registry.
- Implemented asynchronous tests to ensure proper execution and result handling.

chore: Add Dockerfile for sandbox environment setup

- Created `Dockerfile` to set up a Python environment with necessary dependencies for code execution.

chore: Add debug regex script for testing XML parsing

- Introduced `debug_regex.py` to validate regex patterns against XML tool calls.

chore: Add HTML template for displaying thinking stream events

- Created `test_thinking_stream.html` for visualizing tool calling events in a user-friendly format.

test: Add tests for OllamaAdapter XML parsing

- Developed `test_ollama_parser.py` to validate XML parsing with various test cases, including malformed XML.
This commit is contained in:
serversdwn
2025-12-26 03:49:20 -05:00
parent f1471cde84
commit 64429b19e6
37 changed files with 3238 additions and 23 deletions

286
test_thinking_stream.html Normal file
View File

@@ -0,0 +1,286 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lyra - Show Your Work</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1a1a1a;
color: #e0e0e0;
height: 100vh;
display: flex;
flex-direction: column;
}
.container {
display: flex;
height: 100%;
overflow: hidden;
}
.panel {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
overflow: hidden;
}
.panel-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #333;
}
.chat-panel {
border-right: 1px solid #333;
}
.thinking-panel {
background: #0d0d0d;
}
.messages, .thinking-output {
flex: 1;
overflow-y: auto;
padding: 10px;
background: #222;
border-radius: 8px;
margin-bottom: 15px;
}
.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 6px;
line-height: 1.5;
}
.user-message {
background: #1e3a5f;
align-self: flex-end;
}
.assistant-message {
background: #2d2d2d;
}
.thinking-event {
margin-bottom: 10px;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
font-family: 'Courier New', monospace;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
.event-thinking { background: #1a3a1a; color: #90ee90; }
.event-tool_call { background: #3a2a1a; color: #ffa500; }
.event-tool_result { background: #1a2a3a; color: #87ceeb; }
.event-done { background: #2a1a3a; color: #da70d6; }
.event-error { background: #3a1a1a; color: #ff6b6b; }
.input-area {
display: flex;
gap: 10px;
}
input {
flex: 1;
padding: 12px;
background: #2d2d2d;
border: 1px solid #444;
border-radius: 6px;
color: #e0e0e0;
font-size: 14px;
}
button {
padding: 12px 24px;
background: #4a7c59;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
font-weight: bold;
transition: background 0.2s;
}
button:hover {
background: #5a9c69;
}
button:disabled {
background: #333;
cursor: not-allowed;
}
.status {
padding: 8px;
text-align: center;
font-size: 12px;
color: #888;
}
.status.connected { color: #90ee90; }
.status.disconnected { color: #ff6b6b; }
/* Mobile responsive */
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.chat-panel {
border-right: none;
border-bottom: 1px solid #333;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Chat Panel -->
<div class="panel chat-panel">
<div class="panel-header">💬 Chat</div>
<div class="messages" id="messages"></div>
<div class="input-area">
<input
type="text"
id="userInput"
placeholder="Ask something that requires tools... (e.g., 'Calculate 50/2 using Python')"
onkeypress="if(event.key==='Enter') sendMessage()"
>
<button onclick="sendMessage()" id="sendBtn">Send</button>
</div>
</div>
<!-- Thinking Panel -->
<div class="panel thinking-panel">
<div class="panel-header">🧠 Show Your Work</div>
<div class="thinking-output" id="thinking"></div>
<div class="status" id="status">Not connected</div>
</div>
</div>
<script>
const SESSION_ID = 'thinking-demo-' + Date.now();
let eventSource = null;
// Connect to thinking stream
function connectThinkingStream() {
if (eventSource) {
eventSource.close();
}
const url = `http://localhost:7081/stream/thinking/${SESSION_ID}`;
eventSource = new EventSource(url);
eventSource.onopen = () => {
document.getElementById('status').textContent = '🟢 Connected to thinking stream';
document.getElementById('status').className = 'status connected';
};
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
addThinkingEvent(data);
} catch (e) {
console.error('Failed to parse event:', e);
}
};
eventSource.onerror = () => {
document.getElementById('status').textContent = '🔴 Disconnected from thinking stream';
document.getElementById('status').className = 'status disconnected';
setTimeout(connectThinkingStream, 2000); // Reconnect after 2s
};
}
function addThinkingEvent(event) {
const thinking = document.getElementById('thinking');
const eventDiv = document.createElement('div');
eventDiv.className = `thinking-event event-${event.type}`;
if (event.type === 'connected') {
eventDiv.textContent = `✓ Connected (Session: ${event.session_id})`;
} else if (event.data && event.data.message) {
eventDiv.textContent = event.data.message;
} else {
eventDiv.textContent = JSON.stringify(event.data);
}
thinking.appendChild(eventDiv);
thinking.scrollTop = thinking.scrollHeight;
}
async function sendMessage() {
const input = document.getElementById('userInput');
const message = input.value.trim();
if (!message) return;
// Add user message to chat
addMessage('user', message);
input.value = '';
// Disable send button
const sendBtn = document.getElementById('sendBtn');
sendBtn.disabled = true;
sendBtn.textContent = 'Thinking...';
// Clear thinking panel
document.getElementById('thinking').innerHTML = '';
try {
const response = await fetch('http://localhost:7081/simple', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
session_id: SESSION_ID,
user_prompt: message,
backend: 'SECONDARY'
})
});
const data = await response.json();
addMessage('assistant', data.draft);
} catch (error) {
addMessage('assistant', `Error: ${error.message}`);
} finally {
sendBtn.disabled = false;
sendBtn.textContent = 'Send';
}
}
function addMessage(role, content) {
const messages = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}-message`;
messageDiv.textContent = content;
messages.appendChild(messageDiv);
messages.scrollTop = messages.scrollHeight;
}
// Connect on page load
connectThinkingStream();
</script>
</body>
</html>