mirror of
https://github.com/open-webui/open-webui
synced 2025-04-23 15:55:23 +00:00
feat: code interpreter
This commit is contained in:
parent
0d33725d21
commit
8685256002
@ -1078,6 +1078,7 @@ async def process_chat_response(
|
|||||||
# Streaming response
|
# Streaming response
|
||||||
if event_emitter and event_caller:
|
if event_emitter and event_caller:
|
||||||
task_id = str(uuid4()) # Create a unique task ID.
|
task_id = str(uuid4()) # Create a unique task ID.
|
||||||
|
model_id = form_data.get("model", "")
|
||||||
|
|
||||||
# Handle as a background task
|
# Handle as a background task
|
||||||
async def post_response_handler(response, events):
|
async def post_response_handler(response, events):
|
||||||
@ -1100,8 +1101,15 @@ async def process_chat_response(
|
|||||||
else:
|
else:
|
||||||
content = f'{content}<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{reasoning_display_content}\n</details>\n'
|
content = f'{content}<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{reasoning_display_content}\n</details>\n'
|
||||||
|
|
||||||
|
elif block["type"] == "code_interpreter":
|
||||||
|
attributes = block.get("attributes", {})
|
||||||
|
lang = attributes.get("lang", "")
|
||||||
|
attribute_type = attributes.get("type", "")
|
||||||
|
|
||||||
|
content = f"{content}```{lang if lang else attribute_type}\n{block['content']}\n```\n"
|
||||||
else:
|
else:
|
||||||
content = f"{content}{block['type']}: {block['content']}\n"
|
block_content = str(block["content"]).strip()
|
||||||
|
content = f"{content}{block['type']}: {block_content}\n"
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
@ -1217,94 +1225,186 @@ async def process_chat_response(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async for line in response.body_iterator:
|
async def stream_body_handler(response):
|
||||||
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
nonlocal content
|
||||||
data = line
|
nonlocal content_blocks
|
||||||
|
|
||||||
# Skip empty lines
|
async for line in response.body_iterator:
|
||||||
if not data.strip():
|
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
||||||
continue
|
data = line
|
||||||
|
|
||||||
# "data:" is the prefix for each event
|
# Skip empty lines
|
||||||
if not data.startswith("data:"):
|
if not data.strip():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Remove the prefix
|
# "data:" is the prefix for each event
|
||||||
data = data[len("data:") :].strip()
|
if not data.startswith("data:"):
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
# Remove the prefix
|
||||||
data = json.loads(data)
|
data = data[len("data:") :].strip()
|
||||||
|
|
||||||
if "selected_model_id" in data:
|
try:
|
||||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
data = json.loads(data)
|
||||||
metadata["chat_id"],
|
|
||||||
metadata["message_id"],
|
|
||||||
{
|
|
||||||
"selectedModelId": data["selected_model_id"],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
value = (
|
|
||||||
data.get("choices", [])[0]
|
|
||||||
.get("delta", {})
|
|
||||||
.get("content")
|
|
||||||
)
|
|
||||||
|
|
||||||
if value:
|
if "selected_model_id" in data:
|
||||||
content = f"{content}{value}"
|
model_id = data["selected_model_id"]
|
||||||
content_blocks[-1]["content"] = (
|
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||||
content_blocks[-1]["content"] + value
|
metadata["chat_id"],
|
||||||
|
metadata["message_id"],
|
||||||
|
{
|
||||||
|
"selectedModelId": model_id,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
choices = data.get("choices", [])
|
||||||
|
if not choices:
|
||||||
|
continue
|
||||||
|
|
||||||
print(f"Content: {content}")
|
value = choices[0].get("delta", {}).get("content")
|
||||||
print(f"Content Blocks: {content_blocks}")
|
|
||||||
|
|
||||||
if DETECT_REASONING:
|
if value:
|
||||||
content, content_blocks = tag_content_handler(
|
content = f"{content}{value}"
|
||||||
"reasoning",
|
content_blocks[-1]["content"] = (
|
||||||
reasoning_tags,
|
content_blocks[-1]["content"] + value
|
||||||
content,
|
|
||||||
content_blocks,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if DETECT_CODE_INTERPRETER:
|
if DETECT_REASONING:
|
||||||
content, content_blocks = tag_content_handler(
|
content, content_blocks = tag_content_handler(
|
||||||
"code_interpreter",
|
"reasoning",
|
||||||
code_interpreter_tags,
|
reasoning_tags,
|
||||||
content,
|
content,
|
||||||
content_blocks,
|
content_blocks,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ENABLE_REALTIME_CHAT_SAVE:
|
if DETECT_CODE_INTERPRETER:
|
||||||
# Save message in the database
|
content, content_blocks = tag_content_handler(
|
||||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
"code_interpreter",
|
||||||
metadata["chat_id"],
|
code_interpreter_tags,
|
||||||
metadata["message_id"],
|
content,
|
||||||
{
|
content_blocks,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ENABLE_REALTIME_CHAT_SAVE:
|
||||||
|
# Save message in the database
|
||||||
|
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||||
|
metadata["chat_id"],
|
||||||
|
metadata["message_id"],
|
||||||
|
{
|
||||||
|
"content": serialize_content_blocks(
|
||||||
|
content_blocks
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = {
|
||||||
"content": serialize_content_blocks(
|
"content": serialize_content_blocks(
|
||||||
content_blocks
|
content_blocks
|
||||||
),
|
),
|
||||||
},
|
}
|
||||||
)
|
|
||||||
else:
|
await event_emitter(
|
||||||
data = {
|
{
|
||||||
|
"type": "chat:completion",
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
done = "data: [DONE]" in line
|
||||||
|
if done:
|
||||||
|
# Clean up the last text block
|
||||||
|
if content_blocks[-1]["type"] == "text":
|
||||||
|
content_blocks[-1]["content"] = content_blocks[-1][
|
||||||
|
"content"
|
||||||
|
].strip()
|
||||||
|
|
||||||
|
if not content_blocks[-1]["content"]:
|
||||||
|
content_blocks.pop()
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
log.debug("Error: ", e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if response.background:
|
||||||
|
await response.background()
|
||||||
|
|
||||||
|
await stream_body_handler(response)
|
||||||
|
|
||||||
|
MAX_RETRIES = 5
|
||||||
|
retries = 0
|
||||||
|
|
||||||
|
while (
|
||||||
|
content_blocks[-1]["type"] == "code_interpreter"
|
||||||
|
and retries < MAX_RETRIES
|
||||||
|
):
|
||||||
|
retries += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
if content_blocks[-1]["attributes"].get("type") == "code":
|
||||||
|
output = await event_caller(
|
||||||
|
{
|
||||||
|
"type": "execute:pyodide",
|
||||||
|
"data": {
|
||||||
|
"id": str(uuid4()),
|
||||||
|
"code": content_blocks[-1]["content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
output = str(e)
|
||||||
|
|
||||||
|
content_blocks.append(
|
||||||
|
{
|
||||||
|
"type": "code_interpreter",
|
||||||
|
"attributes": {
|
||||||
|
"type": "output",
|
||||||
|
},
|
||||||
|
"content": output,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
content_blocks.append(
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"content": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = await generate_chat_completion(
|
||||||
|
request,
|
||||||
|
{
|
||||||
|
"model": model_id,
|
||||||
|
"stream": True,
|
||||||
|
"messages": [
|
||||||
|
*form_data["messages"],
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
"content": serialize_content_blocks(
|
"content": serialize_content_blocks(
|
||||||
content_blocks
|
content_blocks
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
|
],
|
||||||
await event_emitter(
|
},
|
||||||
{
|
user,
|
||||||
"type": "chat:completion",
|
|
||||||
"data": data,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
done = "data: [DONE]" in line
|
if isinstance(res, StreamingResponse):
|
||||||
if done:
|
await stream_body_handler(res)
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
continue
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.debug(e)
|
||||||
|
break
|
||||||
|
|
||||||
|
await event_emitter(
|
||||||
|
{
|
||||||
|
"type": "chat:completion",
|
||||||
|
"data": {
|
||||||
|
"content": serialize_content_blocks(content_blocks),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
title = Chats.get_chat_title_by_id(metadata["chat_id"])
|
title = Chats.get_chat_title_by_id(metadata["chat_id"])
|
||||||
data = {
|
data = {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import { spring } from 'svelte/motion';
|
import { spring } from 'svelte/motion';
|
||||||
|
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
|
||||||
|
|
||||||
let loadingProgress = spring(0, {
|
let loadingProgress = spring(0, {
|
||||||
stiffness: 0.05
|
stiffness: 0.05
|
||||||
@ -100,7 +101,104 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const chatEventHandler = async (event) => {
|
const executePythonAsWorker = async (id, code, cb) => {
|
||||||
|
let result = null;
|
||||||
|
let stdout = null;
|
||||||
|
let stderr = null;
|
||||||
|
|
||||||
|
let executing = true;
|
||||||
|
let packages = [
|
||||||
|
code.includes('requests') ? 'requests' : null,
|
||||||
|
code.includes('bs4') ? 'beautifulsoup4' : null,
|
||||||
|
code.includes('numpy') ? 'numpy' : null,
|
||||||
|
code.includes('pandas') ? 'pandas' : null,
|
||||||
|
code.includes('sklearn') ? 'scikit-learn' : null,
|
||||||
|
code.includes('scipy') ? 'scipy' : null,
|
||||||
|
code.includes('re') ? 'regex' : null,
|
||||||
|
code.includes('seaborn') ? 'seaborn' : null
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const pyodideWorker = new PyodideWorker();
|
||||||
|
|
||||||
|
pyodideWorker.postMessage({
|
||||||
|
id: id,
|
||||||
|
code: code,
|
||||||
|
packages: packages
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (executing) {
|
||||||
|
executing = false;
|
||||||
|
stderr = 'Execution Time Limit Exceeded';
|
||||||
|
pyodideWorker.terminate();
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
cb(
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
result: result
|
||||||
|
},
|
||||||
|
(_key, value) => (typeof value === 'bigint' ? value.toString() : value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
pyodideWorker.onmessage = (event) => {
|
||||||
|
console.log('pyodideWorker.onmessage', event);
|
||||||
|
const { id, ...data } = event.data;
|
||||||
|
|
||||||
|
console.log(id, data);
|
||||||
|
|
||||||
|
data['stdout'] && (stdout = data['stdout']);
|
||||||
|
data['stderr'] && (stderr = data['stderr']);
|
||||||
|
data['result'] && (result = data['result']);
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
cb(
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
result: result
|
||||||
|
},
|
||||||
|
(_key, value) => (typeof value === 'bigint' ? value.toString() : value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
executing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
pyodideWorker.onerror = (event) => {
|
||||||
|
console.log('pyodideWorker.onerror', event);
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
cb(
|
||||||
|
JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
result: result
|
||||||
|
},
|
||||||
|
(_key, value) => (typeof value === 'bigint' ? value.toString() : value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
executing = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const chatEventHandler = async (event, cb) => {
|
||||||
const chat = $page.url.pathname.includes(`/c/${event.chat_id}`);
|
const chat = $page.url.pathname.includes(`/c/${event.chat_id}`);
|
||||||
|
|
||||||
let isFocused = document.visibilityState !== 'visible';
|
let isFocused = document.visibilityState !== 'visible';
|
||||||
@ -113,11 +211,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.chat_id !== $chatId && !$temporaryChatEnabled) || isFocused) {
|
await tick();
|
||||||
await tick();
|
const type = event?.data?.type ?? null;
|
||||||
const type = event?.data?.type ?? null;
|
const data = event?.data?.data ?? null;
|
||||||
const data = event?.data?.data ?? null;
|
|
||||||
|
|
||||||
|
if ((event.chat_id !== $chatId && !$temporaryChatEnabled) || isFocused) {
|
||||||
if (type === 'chat:completion') {
|
if (type === 'chat:completion') {
|
||||||
const { done, content, title } = data;
|
const { done, content, title } = data;
|
||||||
|
|
||||||
@ -149,6 +247,11 @@
|
|||||||
} else if (type === 'chat:tags') {
|
} else if (type === 'chat:tags') {
|
||||||
tags.set(await getAllTags(localStorage.token));
|
tags.set(await getAllTags(localStorage.token));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (type === 'execute:pyodide') {
|
||||||
|
console.log('execute:pyodide', data);
|
||||||
|
executePythonAsWorker(data.id, data.code, cb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user