mirror of https://github.com/kortix-ai/suna.git
instant stream
This commit is contained in:
parent
b5e79d264f
commit
f8fc799726
|
@ -0,0 +1,75 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Modern Landing Page</title>
|
||||||
|
<link rel="stylesheet" href="reset.css">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="header container">
|
||||||
|
<div class="header-logo">Brand</div>
|
||||||
|
<nav>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="#home">Home</a></li>
|
||||||
|
<li><a href="#features">Features</a></li>
|
||||||
|
<li><a href="#contact">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container hero-content">
|
||||||
|
<h1 class="hero-title">Transform Your Digital Experience</h1>
|
||||||
|
<p class="hero-subtitle">Discover innovative solutions that drive your business forward</p>
|
||||||
|
<a href="#features" class="cta-button">Get Started</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="features" class="features">
|
||||||
|
<div class="container">
|
||||||
|
<div class="feature-grid">
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-icon">🚀</div>
|
||||||
|
<h3>Fast Performance</h3>
|
||||||
|
<p>Optimized solutions for maximum efficiency</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-icon">🔒</div>
|
||||||
|
<h3>Secure Platform</h3>
|
||||||
|
<p>Advanced security measures to protect your data</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-icon">📊</div>
|
||||||
|
<h3>Smart Analytics</h3>
|
||||||
|
<p>Comprehensive insights for strategic decisions</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-content">
|
||||||
|
<div class="footer-logo">Brand</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="#home">Home</a>
|
||||||
|
<a href="#features">Features</a>
|
||||||
|
<a href="#contact">Contact</a>
|
||||||
|
</div>
|
||||||
|
<div class="footer-social">
|
||||||
|
<a href="#">Twitter</a>
|
||||||
|
<a href="#">LinkedIn</a>
|
||||||
|
<a href="#">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-copyright">
|
||||||
|
© 2023 Brand. All rights reserved.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth}body{line-height:1.6;font-family:'-apple-system','BlinkMacSystemFont','Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Open Sans','Helvetica Neue',sans-serif}img{max-width:100%;height:auto}a{text-decoration:none;color:inherit}
|
|
@ -0,0 +1 @@
|
||||||
|
:root{--primary-color:#3498db;--secondary-color:#2ecc71;--text-color:#333;--background-color:#f4f4f4;--accent-color:#e74c3c}body{scroll-behavior:smooth;font-smooth:always;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--background-color);color:var(--text-color)}.container{max-width:1200px;margin:0 auto;padding:0 15px}.header{display:flex;justify-content:space-between;align-items:center;padding:20px 0;background-color:white;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.header-logo{font-size:24px;font-weight:bold;color:var(--primary-color)}.nav-links{display:flex;list-style:none;gap:20px}.nav-links a{color:var(--text-color);transition:color 0.3s ease}.nav-links a:hover{color:var(--primary-color)}.hero{display:flex;align-items:center;min-height:70vh;background:linear-gradient(135deg,var(--primary-color),var(--secondary-color));color:white;text-align:center}.hero-content{max-width:800px;margin:0 auto}.hero-title{font-size:48px;margin-bottom:20px;font-weight:bold}.hero-subtitle{font-size:24px;margin-bottom:30px}.cta-button{display:inline-block;background-color:white;color:var(--primary-color);padding:12px 30px;border-radius:5px;font-weight:bold;transition:transform 0.3s ease}.cta-button:hover{transform:scale(1.05)}.footer{background-color:#333;color:white;text-align:center;padding:20px 0}.features{padding:80px 0;background-color:white}.feature-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:30px}.feature{text-align:center;padding:30px;border-radius:10px;transition:all 0.3s ease;box-shadow:0 4px 6px rgba(0,0,0,0.1);background-color:white}.feature:hover{transform:translateY(-10px);box-shadow:0 8px 15px rgba(0,0,0,0.15);background-color:var(--background-color)}.feature-icon{font-size:48px;margin-bottom:20px}.feature h3{margin-bottom:15px;font-size:20px}.feature p{color:var(--text-color)}.footer{background-color:#1a1a1a;color:white;padding:40px 0}.footer-content{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px}.footer-logo{font-size:24px;font-weight:bold}.footer-links{display:flex;gap:20px}.footer-links a{color:white;transition:color 0.3s ease}.footer-links a:hover{color:var(--primary-color)}.footer-social{display:flex;gap:15px}.footer-social a{color:white;transition:color 0.3s ease}.footer-social a:hover{color:var(--secondary-color)}.footer-copyright{text-align:center;padding-top:20px;border-top:1px solid rgba(255,255,255,0.1)}@media(max-width:768px){.header{flex-direction:column;text-align:center}.header-logo{margin-bottom:15px}.nav-links{flex-direction:column;align-items:center}.hero{text-align:center}.hero-title{font-size:36px}.hero-subtitle{font-size:18px}.feature-grid{grid-template-columns:1fr}.features{padding:40px 0}.footer-content{flex-direction:column;text-align:center}.footer-links,.footer-social{margin-top:20px;flex-direction:column;align-items:center}}
|
|
@ -1,7 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, AsyncGenerator, Callable
|
from typing import Dict, Any, AsyncGenerator, Callable
|
||||||
from agentpress.tool_executor import ToolExecutor
|
|
||||||
from agentpress.tool_parser import ToolParser
|
from agentpress.tool_parser import ToolParser
|
||||||
|
from agentpress.tool_executor import ToolExecutor
|
||||||
|
import asyncio
|
||||||
|
|
||||||
class LLMResponseProcessor:
|
class LLMResponseProcessor:
|
||||||
"""
|
"""
|
||||||
|
@ -23,6 +24,9 @@ class LLMResponseProcessor:
|
||||||
tool_calls_buffer (Dict): Buffer for storing incomplete tool calls during streaming
|
tool_calls_buffer (Dict): Buffer for storing incomplete tool calls during streaming
|
||||||
processed_tool_calls (set): Set of already processed tool call IDs
|
processed_tool_calls (set): Set of already processed tool call IDs
|
||||||
current_message (Dict): Current message being processed in streaming mode
|
current_message (Dict): Current message being processed in streaming mode
|
||||||
|
content_buffer (str): Buffer for accumulating content during streaming
|
||||||
|
tool_calls_accumulated (list): List of tool calls accumulated during streaming
|
||||||
|
message_added (bool): Flag to indicate if a message has been added to the thread
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -44,7 +48,9 @@ class LLMResponseProcessor:
|
||||||
# State tracking for streaming responses
|
# State tracking for streaming responses
|
||||||
self.tool_calls_buffer = {}
|
self.tool_calls_buffer = {}
|
||||||
self.processed_tool_calls = set()
|
self.processed_tool_calls = set()
|
||||||
self.current_message = None
|
self.content_buffer = ""
|
||||||
|
self.tool_calls_accumulated = []
|
||||||
|
self.message_added = False
|
||||||
|
|
||||||
async def process_stream(
|
async def process_stream(
|
||||||
self,
|
self,
|
||||||
|
@ -55,40 +61,43 @@ class LLMResponseProcessor:
|
||||||
"""
|
"""
|
||||||
Process streaming LLM response and handle tool execution.
|
Process streaming LLM response and handle tool execution.
|
||||||
|
|
||||||
Handles streaming responses chunk by chunk, managing tool execution timing
|
Yields chunks immediately as they arrive, while handling tool execution
|
||||||
based on configuration. Tools can be executed immediately as they are
|
and message management in the background.
|
||||||
identified in the stream, or collected and executed after the complete
|
|
||||||
response is received.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
response_stream: Stream of response chunks from the LLM
|
|
||||||
execute_tools: Whether to execute tool calls at all
|
|
||||||
immediate_execution: Whether to execute tools as they appear in the stream
|
|
||||||
or wait for complete response
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
Processed response chunks
|
|
||||||
"""
|
"""
|
||||||
pending_tool_calls = []
|
pending_tool_calls = []
|
||||||
|
|
||||||
async def process_chunk(chunk):
|
async def handle_message_management(chunk):
|
||||||
|
# Accumulate content
|
||||||
|
if hasattr(chunk.choices[0].delta, 'content') and chunk.choices[0].delta.content:
|
||||||
|
self.content_buffer += chunk.choices[0].delta.content
|
||||||
|
|
||||||
|
# Parse and accumulate tool calls
|
||||||
parsed_message, is_complete = await self.tool_parser.parse_stream(
|
parsed_message, is_complete = await self.tool_parser.parse_stream(
|
||||||
chunk,
|
chunk,
|
||||||
self.tool_calls_buffer
|
self.tool_calls_buffer
|
||||||
)
|
)
|
||||||
|
|
||||||
if parsed_message and 'tool_calls' in parsed_message:
|
if parsed_message and 'tool_calls' in parsed_message:
|
||||||
# Update or create message
|
self.tool_calls_accumulated = parsed_message['tool_calls']
|
||||||
if not self.current_message:
|
|
||||||
self.current_message = parsed_message
|
|
||||||
await self.add_message(self.thread_id, self.current_message)
|
|
||||||
else:
|
|
||||||
self.current_message['tool_calls'] = parsed_message['tool_calls']
|
|
||||||
await self.update_message(self.thread_id, self.current_message)
|
|
||||||
|
|
||||||
if execute_tools:
|
# Handle message management and tool execution
|
||||||
|
if chunk.choices[0].finish_reason or (self.content_buffer and self.tool_calls_accumulated):
|
||||||
|
message = {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": self.content_buffer
|
||||||
|
}
|
||||||
|
if self.tool_calls_accumulated:
|
||||||
|
message["tool_calls"] = self.tool_calls_accumulated
|
||||||
|
|
||||||
|
if not self.message_added:
|
||||||
|
await self.add_message(self.thread_id, message)
|
||||||
|
self.message_added = True
|
||||||
|
else:
|
||||||
|
await self.update_message(self.thread_id, message)
|
||||||
|
|
||||||
|
# Handle tool execution
|
||||||
|
if execute_tools and self.tool_calls_accumulated:
|
||||||
new_tool_calls = [
|
new_tool_calls = [
|
||||||
tool_call for tool_call in parsed_message['tool_calls']
|
tool_call for tool_call in self.tool_calls_accumulated
|
||||||
if tool_call['id'] not in self.processed_tool_calls
|
if tool_call['id'] not in self.processed_tool_calls
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -106,8 +115,9 @@ class LLMResponseProcessor:
|
||||||
else:
|
else:
|
||||||
pending_tool_calls.extend(new_tool_calls)
|
pending_tool_calls.extend(new_tool_calls)
|
||||||
|
|
||||||
# Process any pending tools at end of response
|
# Handle end of stream
|
||||||
if chunk.choices[0].finish_reason and not immediate_execution and pending_tool_calls:
|
if chunk.choices[0].finish_reason:
|
||||||
|
if not immediate_execution and pending_tool_calls:
|
||||||
results = await self.tool_executor.execute_tool_calls(
|
results = await self.tool_executor.execute_tool_calls(
|
||||||
tool_calls=pending_tool_calls,
|
tool_calls=pending_tool_calls,
|
||||||
available_functions=self.available_functions,
|
available_functions=self.available_functions,
|
||||||
|
@ -119,11 +129,11 @@ class LLMResponseProcessor:
|
||||||
self.processed_tool_calls.add(result['tool_call_id'])
|
self.processed_tool_calls.add(result['tool_call_id'])
|
||||||
pending_tool_calls.clear()
|
pending_tool_calls.clear()
|
||||||
|
|
||||||
return chunk
|
|
||||||
|
|
||||||
async for chunk in response_stream:
|
async for chunk in response_stream:
|
||||||
processed_chunk = await process_chunk(chunk)
|
# Start background task for message management and tool execution
|
||||||
yield processed_chunk
|
asyncio.create_task(handle_message_management(chunk))
|
||||||
|
# Immediately yield the chunk
|
||||||
|
yield chunk
|
||||||
|
|
||||||
async def process_response(
|
async def process_response(
|
||||||
self,
|
self,
|
||||||
|
|
Loading…
Reference in New Issue