Compare commits
3 Commits
refactor_1
...
b56619a2e6
Author | SHA1 | Date | |
---|---|---|---|
b56619a2e6 | |||
90e0b4ff7f | |||
47059dabdc |
40
.gitignore
vendored
40
.gitignore
vendored
@ -174,3 +174,43 @@ cython_debug/
|
||||
pyvenv.cfg
|
||||
.venv
|
||||
pip-selfcheck.json
|
||||
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
.next
|
3
dewey/.eslintrc.json
Normal file
3
dewey/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
36
dewey/.gitignore
vendored
Normal file
36
dewey/.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
36
dewey/README.md
Normal file
36
dewey/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
BIN
dewey/app/favicon.ico
Normal file
BIN
dewey/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
dewey/app/fonts/GeistMonoVF.woff
Normal file
BIN
dewey/app/fonts/GeistMonoVF.woff
Normal file
Binary file not shown.
BIN
dewey/app/fonts/GeistVF.woff
Normal file
BIN
dewey/app/fonts/GeistVF.woff
Normal file
Binary file not shown.
27
dewey/app/globals.css
Normal file
27
dewey/app/globals.css
Normal file
@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
35
dewey/app/layout.tsx
Normal file
35
dewey/app/layout.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Metadata } from "next";
|
||||
import localFont from "next/font/local";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/GeistVF.woff",
|
||||
variable: "--font-geist-sans",
|
||||
weight: "100 900",
|
||||
});
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/GeistMonoVF.woff",
|
||||
variable: "--font-geist-mono",
|
||||
weight: "100 900",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
101
dewey/app/page.tsx
Normal file
101
dewey/app/page.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="https://nextjs.org/icons/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="https://nextjs.org/icons/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="https://nextjs.org/icons/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="https://nextjs.org/icons/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="https://nextjs.org/icons/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
4
dewey/next.config.mjs
Normal file
4
dewey/next.config.mjs
Normal file
@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
5308
dewey/package-lock.json
generated
Normal file
5308
dewey/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
dewey/package.json
Normal file
26
dewey/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "dewey",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "14.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.13"
|
||||
}
|
||||
}
|
8
dewey/postcss.config.mjs
Normal file
8
dewey/postcss.config.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
19
dewey/tailwind.config.ts
Normal file
19
dewey/tailwind.config.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--background)",
|
||||
foreground: "var(--foreground)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
26
dewey/tsconfig.json
Normal file
26
dewey/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
401
index.html
401
index.html
@ -9,6 +9,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
@ -86,13 +88,17 @@
|
||||
color: #888;
|
||||
}
|
||||
.thought-summary {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.thought-summary.plan { background-color: #2c3e50; }
|
||||
.thought-summary.decision { background-color: #34495e; }
|
||||
.thought-summary.tool_call { background-color: #16a085; }
|
||||
.thought-summary.tool_result { background-color: #27ae60; }
|
||||
.thought-summary.think_more { background-color: #2980b9; }
|
||||
.thought-summary.answer { background-color: #8e44ad; }
|
||||
.thought-details {
|
||||
display: none;
|
||||
margin-left: 20px;
|
||||
@ -238,11 +244,111 @@
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-history-container {
|
||||
margin-top: 20px;
|
||||
background-color: #222;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#conversation-history {
|
||||
color: #fff;
|
||||
font-family: 'Noto Sans Mono', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.history-card {
|
||||
background-color: #2c3e50;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.history-role {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.history-content {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ff6b6b;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.retrying {
|
||||
background-color: #feca57;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#clear-history-button {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
font-family: 'Noto Sans Mono', monospace;
|
||||
font-size: 14px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#clear-history-button:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
#chat-tabs {
|
||||
display: flex;
|
||||
background-color: #222;
|
||||
padding: 10px 10px 0 10px;
|
||||
}
|
||||
|
||||
.chat-tab {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.chat-tab.active {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
#new-chat-button {
|
||||
background-color: #27ae60;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.close-tab {
|
||||
margin-left: 10px;
|
||||
color: #ff6b6b;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thinking-section {
|
||||
margin-bottom: 20px;
|
||||
border-left: 2px solid #444;
|
||||
padding-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-container">
|
||||
<div id="chat-area">
|
||||
<div id="chat-tabs"></div>
|
||||
<div id="chat-container"></div>
|
||||
<div id="input-container" class="pdp-panel">
|
||||
<div class="pdp-label">INPUT:</div>
|
||||
@ -272,6 +378,12 @@
|
||||
<div class="graph-title">GPU Memory</div>
|
||||
<canvas id="gpuMemoryChart"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Add this new section for conversation history -->
|
||||
<div class="conversation-history-container">
|
||||
<div class="graph-title">Conversation History</div>
|
||||
<div id="conversation-history"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -280,10 +392,80 @@
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const userInput = document.getElementById('user-input');
|
||||
const sendButton = document.getElementById('send-button');
|
||||
const chatTabs = document.getElementById('chat-tabs');
|
||||
|
||||
let thinkingElement = null;
|
||||
let thinkingDetails = null;
|
||||
let thinkingStartTime = null;
|
||||
let currentChatId = null;
|
||||
let chats = {};
|
||||
|
||||
function createNewChat() {
|
||||
const chatId = Date.now().toString();
|
||||
chats[chatId] = {
|
||||
messages: [],
|
||||
thinkingSections: []
|
||||
};
|
||||
addChatTab(chatId);
|
||||
switchToChat(chatId);
|
||||
saveChats();
|
||||
}
|
||||
|
||||
function addChatTab(chatId) {
|
||||
const tab = document.createElement('button');
|
||||
tab.classList.add('chat-tab');
|
||||
tab.textContent = `Chat ${Object.keys(chats).length}`;
|
||||
tab.onclick = () => switchToChat(chatId);
|
||||
|
||||
const closeButton = document.createElement('span');
|
||||
closeButton.classList.add('close-tab');
|
||||
closeButton.textContent = '×';
|
||||
closeButton.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
closeChat(chatId);
|
||||
};
|
||||
|
||||
tab.appendChild(closeButton);
|
||||
chatTabs.insertBefore(tab, chatTabs.lastElementChild);
|
||||
}
|
||||
|
||||
function switchToChat(chatId) {
|
||||
currentChatId = chatId;
|
||||
document.querySelectorAll('.chat-tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelector(`.chat-tab:nth-child(${Object.keys(chats).indexOf(chatId) + 1})`).classList.add('active');
|
||||
renderChat(chatId);
|
||||
}
|
||||
|
||||
function closeChat(chatId) {
|
||||
delete chats[chatId];
|
||||
saveChats();
|
||||
const tabToRemove = Array.from(chatTabs.children).find(tab => tab.textContent.includes(`Chat ${Object.keys(chats).indexOf(chatId) + 1}`));
|
||||
if (tabToRemove) {
|
||||
chatTabs.removeChild(tabToRemove);
|
||||
}
|
||||
if (currentChatId === chatId) {
|
||||
const remainingChatIds = Object.keys(chats);
|
||||
if (remainingChatIds.length > 0) {
|
||||
switchToChat(remainingChatIds[0]);
|
||||
} else {
|
||||
createNewChat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderChat(chatId) {
|
||||
chatContainer.innerHTML = '';
|
||||
const chat = chats[chatId];
|
||||
chat.messages.forEach(message => addMessage(message.content, message.isUser));
|
||||
chat.thinkingSections.forEach(section => {
|
||||
const thinkingSection = createThinkingSection();
|
||||
section.thoughts.forEach(thought => addThought(thought.type, thought.content, thought.details, thinkingSection));
|
||||
});
|
||||
}
|
||||
|
||||
function createThinkingSection() {
|
||||
const section = document.createElement('div');
|
||||
section.classList.add('thinking-section');
|
||||
chatContainer.appendChild(section);
|
||||
return section;
|
||||
}
|
||||
|
||||
function addMessage(message, isUser) {
|
||||
const messageElement = document.createElement('div');
|
||||
@ -292,65 +474,40 @@
|
||||
messageElement.innerHTML = isUser ? message : marked.parse(message);
|
||||
chatContainer.appendChild(messageElement);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
|
||||
if (currentChatId) {
|
||||
chats[currentChatId].messages.push({ content: message, isUser: isUser });
|
||||
saveChats();
|
||||
}
|
||||
}
|
||||
|
||||
function startThinking() {
|
||||
thinkingElement = document.createElement('div');
|
||||
thinkingElement.classList.add('thought-summary', 'collapsible');
|
||||
function addThought(type, content, details = '', thinkingSection) {
|
||||
const stepElement = document.createElement('div');
|
||||
stepElement.classList.add('thought-summary', 'collapsible', type);
|
||||
stepElement.textContent = type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' ') + ':';
|
||||
stepElement.onclick = toggleStepDetails;
|
||||
|
||||
const stepDetails = document.createElement('div');
|
||||
stepDetails.classList.add('thought-details');
|
||||
|
||||
const led = document.createElement('div');
|
||||
led.classList.add('led', 'blinking');
|
||||
|
||||
const textNode = document.createTextNode('Thinking...');
|
||||
|
||||
thinkingElement.appendChild(led);
|
||||
thinkingElement.appendChild(textNode);
|
||||
thinkingElement.onclick = toggleThinkingDetails;
|
||||
|
||||
thinkingDetails = document.createElement('div');
|
||||
thinkingDetails.classList.add('thought-details');
|
||||
|
||||
chatContainer.appendChild(thinkingElement);
|
||||
chatContainer.appendChild(thinkingDetails);
|
||||
|
||||
thinkingStartTime = Date.now();
|
||||
if (type === 'error') {
|
||||
stepElement.classList.add('error-message');
|
||||
if (content.includes('retrying')) {
|
||||
stepElement.classList.add('retrying');
|
||||
}
|
||||
stepDetails.innerHTML = marked.parse(content + '\n\nDetails:\n```\n' + details + '\n```');
|
||||
} else {
|
||||
stepDetails.innerHTML = marked.parse(content);
|
||||
}
|
||||
|
||||
thinkingSection.appendChild(stepElement);
|
||||
thinkingSection.appendChild(stepDetails);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function addThought(step, content) {
|
||||
if (thinkingDetails) {
|
||||
const stepElement = document.createElement('div');
|
||||
stepElement.classList.add('thought-summary', 'collapsible');
|
||||
stepElement.textContent = step;
|
||||
stepElement.onclick = toggleStepDetails;
|
||||
|
||||
const stepDetails = document.createElement('div');
|
||||
stepDetails.classList.add('thought-details');
|
||||
stepDetails.innerHTML = content;
|
||||
|
||||
thinkingDetails.appendChild(stepElement);
|
||||
thinkingDetails.appendChild(stepDetails);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function endThinking(thinkingTime) {
|
||||
if (thinkingElement) {
|
||||
const textNode = thinkingElement.childNodes[1];
|
||||
textNode.nodeValue = `Thinking... (${thinkingTime}s)`;
|
||||
const led = thinkingElement.querySelector('.led');
|
||||
led.classList.remove('blinking');
|
||||
led.style.backgroundColor = '#0f0';
|
||||
led.style.boxShadow = '0 0 10px #0f0';
|
||||
thinkingStartTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleThinkingDetails() {
|
||||
this.classList.toggle('open');
|
||||
const details = this.nextElementSibling;
|
||||
if (details) {
|
||||
details.style.display = details.style.display === 'none' ? 'block' : 'none';
|
||||
if (currentChatId) {
|
||||
const currentThinkingSection = chats[currentChatId].thinkingSections[chats[currentChatId].thinkingSections.length - 1];
|
||||
currentThinkingSection.thoughts.push({ type, content, details });
|
||||
saveChats();
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,34 +519,71 @@
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('thinking', (data) => {
|
||||
if (!thinkingElement) startThinking();
|
||||
addThought(data.step, 'Started');
|
||||
});
|
||||
function saveChats() {
|
||||
localStorage.setItem('chats', JSON.stringify(chats));
|
||||
}
|
||||
|
||||
socket.on('thought', (data) => {
|
||||
addThought('Result', data.content);
|
||||
});
|
||||
|
||||
socket.on('chat_response', (data) => {
|
||||
endThinking(data.thinking_time);
|
||||
addMessage(data.response, false);
|
||||
});
|
||||
|
||||
socket.on('error', (data) => {
|
||||
endThinking(data.thinking_time);
|
||||
addMessage(`Error: ${data.message}`, false);
|
||||
});
|
||||
function loadChats() {
|
||||
const storedChats = localStorage.getItem('chats');
|
||||
if (storedChats) {
|
||||
chats = JSON.parse(storedChats);
|
||||
Object.keys(chats).forEach(chatId => addChatTab(chatId));
|
||||
if (Object.keys(chats).length > 0) {
|
||||
switchToChat(Object.keys(chats)[0]);
|
||||
} else {
|
||||
createNewChat();
|
||||
}
|
||||
} else {
|
||||
createNewChat();
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const message = userInput.value.trim();
|
||||
if (message) {
|
||||
if (message && currentChatId) {
|
||||
addMessage(message, true);
|
||||
socket.emit('chat_request', { message: message });
|
||||
chats[currentChatId].thinkingSections.push({ thoughts: [] });
|
||||
socket.emit('chat_request', {
|
||||
message: message,
|
||||
conversation_history: chats[currentChatId].messages.filter(m => !m.isUser).map(m => ({ role: 'assistant', content: m.content }))
|
||||
.concat(chats[currentChatId].messages.filter(m => m.isUser).map(m => ({ role: 'user', content: m.content })))
|
||||
});
|
||||
userInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('thinking', (data) => {
|
||||
if (currentChatId) {
|
||||
const newThinkingSection = createThinkingSection();
|
||||
chats[currentChatId].thinkingSections.push({ thoughts: [] });
|
||||
addThought(data.step, 'Started', '', newThinkingSection);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('thought', (data) => {
|
||||
if (currentChatId) {
|
||||
const currentThinkingSection = chatContainer.querySelector('.thinking-section:last-child');
|
||||
addThought(data.type, data.content, data.details, currentThinkingSection);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('chat_response', (data) => {
|
||||
if (currentChatId) {
|
||||
addMessage(data.response, false);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('error', (data) => {
|
||||
if (currentChatId) {
|
||||
const currentThinkingSection = chatContainer.querySelector('.thinking-section:last-child');
|
||||
if (data.type === 'retrying') {
|
||||
addThought('error', data.content, '', currentThinkingSection);
|
||||
} else {
|
||||
addThought('error', data.message, '', currentThinkingSection);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sendButton.addEventListener('click', sendMessage);
|
||||
userInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
@ -398,6 +592,16 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Add new chat button
|
||||
const newChatButton = document.createElement('button');
|
||||
newChatButton.id = 'new-chat-button';
|
||||
newChatButton.textContent = '+ New Chat';
|
||||
newChatButton.onclick = createNewChat;
|
||||
chatTabs.appendChild(newChatButton);
|
||||
|
||||
// Load chats when the page loads
|
||||
loadChats();
|
||||
|
||||
const chartOptions = {
|
||||
type: 'line',
|
||||
options: {
|
||||
@ -570,6 +774,41 @@
|
||||
|
||||
window.addEventListener('resize', checkWindowSize);
|
||||
checkWindowSize(); // Initial check
|
||||
|
||||
// Add this new function to update the conversation history
|
||||
function updateConversationHistory(history) {
|
||||
const conversationHistoryElement = document.getElementById('conversation-history');
|
||||
conversationHistoryElement.innerHTML = '';
|
||||
|
||||
history.forEach(item => {
|
||||
const card = document.createElement('div');
|
||||
card.classList.add('history-card');
|
||||
|
||||
const role = document.createElement('div');
|
||||
role.classList.add('history-role');
|
||||
role.textContent = item.role.charAt(0).toUpperCase() + item.role.slice(1);
|
||||
|
||||
const content = document.createElement('pre');
|
||||
content.classList.add('history-content');
|
||||
content.innerHTML = hljs.highlightAuto(item.content).value;
|
||||
|
||||
card.appendChild(role);
|
||||
card.appendChild(content);
|
||||
conversationHistoryElement.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Add this new socket listener
|
||||
socket.on('conversation_history', (data) => {
|
||||
updateConversationHistory(data.history);
|
||||
});
|
||||
|
||||
// Add event listener for the clear history button
|
||||
clearHistoryButton.addEventListener('click', () => {
|
||||
if (confirm('Are you sure you want to clear the conversation history?')) {
|
||||
clearConversationHistory();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
279
main.py
279
main.py
@ -1,27 +1,29 @@
|
||||
import re
|
||||
from flask import Flask, send_from_directory
|
||||
from flask import Flask, send_from_directory, request
|
||||
from flask_socketio import SocketIO, emit
|
||||
from flask_openapi3 import OpenAPI, Info
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
from models import model_manager
|
||||
from tools import DefaultToolManager
|
||||
import structlog
|
||||
import time
|
||||
import psutil
|
||||
import GPUtil
|
||||
import threading
|
||||
import os
|
||||
import datetime
|
||||
|
||||
|
||||
|
||||
from tools import DefaultToolManager
|
||||
import ollama
|
||||
import re
|
||||
import json
|
||||
from datetime import datetime
|
||||
import pprint
|
||||
logger = structlog.get_logger()
|
||||
|
||||
openapi = OpenAPI(__name__, info=Info(title="LLM Chat Server", version="1.0.0"))
|
||||
app = openapi
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
tool_manager = DefaultToolManager()
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
logger.info("Serving index.html")
|
||||
@ -36,12 +38,16 @@ class ChatResponse(BaseModel):
|
||||
@socketio.on('chat_request')
|
||||
def handle_chat_request(data):
|
||||
user_input = data['message']
|
||||
logger.info("Received chat request", user_input=user_input)
|
||||
conversation_history = data.get('conversation_history', [])
|
||||
conversation_history = [{"role": "system", "content": ANSWER_QUESTION_PROMPT}] + conversation_history
|
||||
logger.info("Received chat request", user_input=user_input, conversation_history=conversation_history)
|
||||
|
||||
start_time = time.time()
|
||||
try:
|
||||
final_response = answer_question(user_input)
|
||||
thinking_time = round(time.time() - start_time, 2)
|
||||
final_response = answer_question_tools(user_input, conversation_history)
|
||||
end_time = time.time()
|
||||
thinking_time = round(end_time - start_time, 2)
|
||||
|
||||
emit('chat_response', {
|
||||
'response': final_response,
|
||||
'thinking_time': thinking_time
|
||||
@ -55,193 +61,98 @@ def handle_chat_request(data):
|
||||
'thinking_time': thinking_time
|
||||
})
|
||||
|
||||
def answer_question_tools(user_input: str, conversation_history: List[dict], max_retries: int = 100):
|
||||
global tool_manager
|
||||
|
||||
# If conversation_history is empty, initialize it with the system prompt
|
||||
if not conversation_history:
|
||||
conversation_history = [
|
||||
{"role": "system", "content": ANSWER_QUESTION_PROMPT},
|
||||
]
|
||||
|
||||
logger.info("Starting chat", user_input=user_input, conversation_history=conversation_history)
|
||||
# Add the new user input to the conversation history
|
||||
conversation_history.append({"role": "user", "content": user_input})
|
||||
|
||||
emit('thinking', {'step': 'Starting'})
|
||||
emit('conversation_history', {'history': conversation_history})
|
||||
|
||||
for iteration in range(max_retries):
|
||||
response = ollama.chat(model=PRIMARY_MODEL, messages=conversation_history, tools=tool_manager.get_tools_for_ollama_dict(), stream=False)
|
||||
assistant_message = response['message']
|
||||
|
||||
conversation_history.append(assistant_message)
|
||||
emit('conversation_history', {'history': conversation_history})
|
||||
pprint.pp(assistant_message)
|
||||
|
||||
if 'tool_calls' in assistant_message:
|
||||
emit('thought', {'type': 'decision', 'content': "Tool Call\n\n" + assistant_message['content']})
|
||||
for tool_call in assistant_message['tool_calls']:
|
||||
tool_name = tool_call['function']['name']
|
||||
tool_args = tool_call['function']['arguments']
|
||||
emit('thought', {'type': 'tool_call', 'content': f"Tool: {tool_name}\nArguments: {tool_args}"})
|
||||
tool_response = tool_manager.get_tool(tool_name).execute(tool_args)
|
||||
conversation_history.append({
|
||||
"role": "tool",
|
||||
"content": tool_response
|
||||
})
|
||||
emit('conversation_history', {'history': conversation_history})
|
||||
emit('thought', {'type': 'tool_result', 'content': tool_response})
|
||||
|
||||
reflection_prompt = "Reflect on the tool results. If there were any errors, propose multiple alternative approaches to solve the problem. If successful, consider if the result fully answers the user's query or if additional steps are needed."
|
||||
conversation_history.append({
|
||||
"role": "assistant",
|
||||
"content": reflection_prompt
|
||||
})
|
||||
emit('conversation_history', {'history': conversation_history})
|
||||
else:
|
||||
if "<answer>" in assistant_message['content'].lower():
|
||||
answer_content = re.search(r'<answer>(.*?)</answer>', assistant_message['content'], re.DOTALL)
|
||||
if answer_content:
|
||||
final_answer = answer_content.group(1).strip()
|
||||
emit('thought', {'type': 'answer', 'content': final_answer})
|
||||
return final_answer
|
||||
else:
|
||||
emit('thought', {'type': 'decision', 'content': "Think/Plan/Decision/Action\n\n" + assistant_message['content']})
|
||||
reflection_prompt = "Your last response didn't provide a final answer. Please reflect on your current understanding of the problem and consider if you need to use any tools or if you can now provide a final answer. If you're ready to give a final answer, put your response in tags <answer></answer>"
|
||||
conversation_history.append({"role": "assistant", "content": reflection_prompt})
|
||||
emit('conversation_history', {'history': conversation_history})
|
||||
|
||||
return f"Max iterations reached. Last response: {assistant_message['content']}"
|
||||
|
||||
ANSWER_QUESTION_PROMPT = f"""
|
||||
You are Dewy, created by DWS.
|
||||
The current date is {datetime.datetime.now().strftime("%A, %B %d, %Y")}. Dewy's knowledge base was last updated on April 2024.
|
||||
Answer questions about events prior to and after April 2024 the way a highly informed individual in April 2024 would, and let the human know this when relevant.
|
||||
You work through an iterative planning, execution, and reflection loop.
|
||||
The user will start a conversation with you. Before replying, you will have a chance to think through your response.
|
||||
Thinking is done on an internal scratchpad. You may generate context into this scratchpad to enrich your response.
|
||||
|
||||
You can generate three types of context: TOOLUSAGE, THOUGHTS, and FINAL RESPONSE.
|
||||
TOOLUSAGE is when you are using a tool.
|
||||
THOUGHTS is when you are thinking through your response.
|
||||
FINAL RESPONSE is when you have a final response to the user.
|
||||
When responding, you may only respond with one of the context types.
|
||||
Do not mix context types in your response.
|
||||
You must have at least one THOUGHTS.
|
||||
You cannot have a TOOLUSAGE at the same time as THOUGHTS or FINAL RESPONSE.
|
||||
|
||||
|
||||
You will begin your response with either TOOLUSAGE, THOUGHTS, or FINAL RESPONSE.
|
||||
|
||||
THOUGHTS:
|
||||
Thoughts can be used to generate additional context that can be used to complete your task.
|
||||
|
||||
FINAL RESPONSE:
|
||||
Once you have thought through your response, used any necessary tools, and have a final response, you will output that response to the user.
|
||||
Your output should be in Markdown format.
|
||||
Do not have any preamble or other text.
|
||||
|
||||
TOOLUSAGE:
|
||||
The following tools are available to you. You can use these tools to help you complete your task.
|
||||
The current date is {datetime.now().strftime("%A, %B %d, %Y")}, your knowledge cutoff was December 2023.
|
||||
You are Dewey, an AI assistant with access to external tools and the ability to think through complex problems. Your role is to assist users by leveraging tools when necessary, thinking deeply about problems, and providing accurate and helpful information, all with a cheerful, but witty personality. Here are the tools available to you:
|
||||
|
||||
{tool_manager.get_tools_and_descriptions_for_prompt()}
|
||||
You call a tool by placing the following text on a new line:
|
||||
<<|tool_name|arguments|>>
|
||||
The tool will execute and output the result.
|
||||
The scratchpad will be updated with the tool's output, and you will be able to continue your thought process.
|
||||
If you are using a tool, end your response with the tool call line.
|
||||
|
||||
When addressing a query, follow these steps:
|
||||
|
||||
Below are a few examples of how you can use the tools and scratchpad. Each example is separated by a <example> open and close tag. The example is a representation of the scratch pad. Each iteration on the scratch pad is delimited by a <iteration> open and close tag.
|
||||
<example>
|
||||
What is the Wikipedia article of the day?
|
||||
1. Analyze: Thoroughly analyze the query and consider multiple approaches to solving it.
|
||||
|
||||
<iteration>
|
||||
THOUGHTS
|
||||
The wikipedia article of the day is decided every day. Because it is dynamic, I will need to use a tool to search for the article.
|
||||
</iteration>
|
||||
2. Plan: Develop a plan of action, considering whether you need to use any tools or if you can answer directly.
|
||||
|
||||
<iteration>
|
||||
TOOLUSAGE
|
||||
<|search_web|Wikipedia article of the day|>
|
||||
<|RESULTS|>
|
||||
Wikipedia:Today's featured article - Wikipedia -- This star symbolizes the featured content on Wikipedia. Each day, a summary (roughly 975 characters long) of one of Wikipedia's featured articles (FAs) appears at the top of the Main Page as Today's Featured Article (TFA). The Main Page is viewed about 4.7 million times daily. TFAs are scheduled by the TFA coordinators: Wehwalt, Dank and Gog ... -> https://en.wikipedia.org/wiki/Wikipedia:Today's_featured_article
|
||||
Wikipedia:Today's featured article/Most viewed - Wikipedia -- This TFA STATS page is an attempt to recognise Wikipedia's most viewed today's featured articles.Articles are listed below based on page views surpassing 100,000 hits on the day of the article's appearance on the Main Page. Although Wolfgang Amadeus Mozart was Wikipedia's first Featured Article to be featured on the Main Page, page view statistics were not tracked until December 2007. -> https://en.wikipedia.org/wiki/Wikipedia:Today's_featured_article/Most_viewed
|
||||
Wikipedia:Featured articles - Wikipedia -- There are 6,582 featured articles out of 6,886,376 articles on the English Wikipedia (about 0.1% or one out of every 1,040 articles). Articles that no longer meet the criteria can be proposed for improvement or removal at featured article review. On non-mobile versions of our website, a small bronze star icon () on the top right corner of an ... -> https://en.wikipedia.org/wiki/Wikipedia:Featured_articles
|
||||
Wikipedia -- Wikipedia is a free online encyclopedia, created and edited by volunteers around the world and hosted by the Wikimedia Foundation. English 6,873,000+ articles 日本語 1,427,000+ 記事 -> https://www.wikipedia.org/
|
||||
How does Wikipedia article of the day in this subreddit get selected ... -- The ones posted here as the article of the day are Wikipedia's selected article of the day, which can be seen on the English wiki's main page. More info about how they are selected on Wikipedia, including selection criteria and links to upcoming featured article candidates (and discussion about them) can be found on the featured articles about ... -> https://www.reddit.com/r/wikipedia/comments/hbuosu/how_does_wikipedia_article_of_the_day_in_this/
|
||||
</iteration>
|
||||
3. Execute: If you need to use a tool, call it as you would a function. If not, proceed with your reasoning.
|
||||
|
||||
<iteration>
|
||||
THOUGHTS
|
||||
From the results, I can see that the first result provides a link to something that could be about the wikipedia article of the day. I should use a tool to get the contents of the page, and see if it answers the users question.
|
||||
</iteration>
|
||||
4. Reflect: After each step or tool use, reflect on the results:
|
||||
- If successful, consider if the result fully answers the user's query or if additional steps are needed.
|
||||
- If there were errors or the result is unsatisfactory, don't give up! Use Tree of Thoughts reasoning:
|
||||
a) Generate multiple alternative approaches or modifications to your previous approach.
|
||||
b) Briefly evaluate the potential of each alternative.
|
||||
c) Choose the most promising alternative and execute it.
|
||||
d) Repeat this process if needed, building upon your growing understanding of the problem.
|
||||
e) You cannot return a final answer after an error using a tool, you must try again.
|
||||
|
||||
<iteration>
|
||||
TOOLUSAGE
|
||||
<|get_readable_page_contents|https://en.wikipedia.org/wiki/Wikipedia:Today's_featured_article|>
|
||||
<|RESULTS|>
|
||||
Title: Wikipedia:Today's featured article
|
||||
\nFrom Wikipedia, the free encyclopedia\n\n\n\nFeatured articles shown on the Main Page\n\n\n\nFrom today\'s featured article\n-----------------------------\n\n\n\n**[Addie Viola Smith](/wiki/Addie_Viola_Smith "Addie Viola Smith")** (1893–1975\\) was an American attorney who served as the U.S. [trade commissioner](/wiki/Trade_commissioner "Trade commissioner") to Shanghai from 1928 to 1939, the first female [Foreign Service officer](/wiki/Foreign_Service_officer "Foreign Service officer") in the [U.S. Foreign Service](/wiki/United_States_Foreign_Service "United States Foreign Service") to work under the [Commerce Department](/wiki/United_States_Department_of_Commerce "United States Department of Commerce"), and the first woman to serve as trade commissioner. A native of [Stockton, California](/wiki/Stockton,_California "Stockton, California"), Smith moved to Washington, D.C., in 1917\\. While working for the [United States Department of Labor](/wiki/United_States_Department_of_Labor "United States Department of Labor"), she attended the [Washington College of Law](/wiki/American_University_Washington_College_of_Law "American University Washington College of Law") part\\-time, earning a [Bachelor of Laws](/wiki/Bachelor_of_Laws "Bachelor of Laws") degree in 1920\\. She joined the Foreign Service in October that year. Posted to Beijing as a clerk, she was promoted to assistant trade commissioner in Shanghai in 1922, and to trade commissioner in 1928\\. She later held roles in the U.S. government, world organizations, and the [United Nations](/wiki/United_Nations "United Nations"). Smith met her life partner, [Eleanor Mary Hinder](/wiki/Eleanor_Hinder "Eleanor Hinder"), in 1926; they moved to Hinder\'s native Australia in 1957, where stone seats are dedicated to them at the [E.\xa0G. Waterhouse National Camellia Gardens](/wiki/Eben_Gowrie_Waterhouse#Camellias "Eben Gowrie Waterhouse"). (**[Full\xa0article...](/wiki/Addie_Viola_Smith "Addie Viola Smith")**)\n\n\n\n\n\n\nFrom tomorrow\'s featured article\n--------------------------------\n\n\n\n\nFrom the day after tomorrow\'s featured article\n----------------------------------------------\n\n\n\n\n\n'
|
||||
</iteration>
|
||||
5. Iterate: Continue this process of execution and reflection, exploring different branches of thought as needed.
|
||||
|
||||
<iteration>
|
||||
THOUGHTS
|
||||
Based on the tool results, I can see that this page describers the daily featured article on Wikipedia. Todays featured article is Addie Viola Smith on the URL https://en.wikipedia.org/wiki/Addie_Viola_Smith
|
||||
The tool response also contains a short description of the article. I will use this to answer the users question.
|
||||
</iteration>
|
||||
6. Conclude: When you believe you have a comprehensive answer to the user's query, provide your final answer.
|
||||
|
||||
<iteration>
|
||||
FINAL RESPONSE
|
||||
The Wikipedia article of the day is [Addie Viola Smith](https://en.wikipedia.org/wiki/Addie_Viola_Smith). Addie Viola Smith was an American attorney who served as the U.S. trade commissioner to Shanghai from 1928 to 1939, the first female Foreign Service officer in the U.S. Foreign Service to work under the Commerce Department, and the first woman to serve as trade commissioner.
|
||||
</iteration>
|
||||
</example>
|
||||
Always explain your thought process, including your reasoning for each decision and how you arrived at your conclusions. If you're providing a final answer, put your response in tags <answer></answer>.
|
||||
|
||||
Do not reference the above examples in your response.
|
||||
|
||||
Any response that does not conform to the above rules will be rejected.
|
||||
Your response must begin with either TOOLUSAGE, THOUGHTS, or FINAL RESPONSE.
|
||||
Remember, complex problems often require multiple steps and iterations. Don't hesitate to break down the problem, use tools multiple times, or explore different approaches to arrive at the best solution.
|
||||
"""
|
||||
|
||||
def answer_question(user_input: str) -> tuple[List[str], str]:
|
||||
scratchpad = user_input
|
||||
response = model_manager.generate_text("qwen2.5:7b", user_input, max_length=1024, system=ANSWER_QUESTION_PROMPT)
|
||||
logger.debug("Initial response", response=response)
|
||||
emit('thinking', {'step': 'Answering Question'})
|
||||
emit('thought', {'content': response})
|
||||
done = False
|
||||
|
||||
# Loop until the response does not start with FINAL RESPONSE
|
||||
while not done:
|
||||
# The first line of the response is the context type,the rest is the content
|
||||
context_type = response.split("\n")[0].strip().lower()
|
||||
content = "\n".join(response.split("\n")[1:])
|
||||
emit('thought', {f'{context_type}': content})
|
||||
|
||||
logger.debug("Context type", context_type=context_type)
|
||||
if context_type == "toolusage":
|
||||
tool_name = content.split("|")[0].split("|")[0]
|
||||
arguments = content.split("|")[1].split("|")[0]
|
||||
emit('thinking', {'step': f'Executing tool {tool_name} with arguments {arguments}'})
|
||||
tool_result = tool_manager.execute_tool(tool_name, arguments)
|
||||
emit('thought', {'content': f"Tool {tool_name} result:\n{tool_result}"})
|
||||
scratchpad += f"\n<|RESULTS|>\n{tool_result}"
|
||||
elif context_type == "final response":
|
||||
done = True
|
||||
return content
|
||||
elif context_type == "thoughts":
|
||||
scratchpad += "\n" + content
|
||||
|
||||
|
||||
# Generate a response based on the scratchpad
|
||||
response = model_manager.generate_text("qwen2.5:7b", scratchpad, max_length=1024, system=ANSWER_QUESTION_PROMPT)
|
||||
logger.debug("Generated response", response=response)
|
||||
input("Press Enter to continue...")
|
||||
|
||||
|
||||
SELECT_BEST_MODEL_PROMPT = f"""
|
||||
You are a large language model whos job it is to evaluate a step that is part of a larger plan, and determine what LLM would be best suited to complete the step based on the capabilities of the LLM.
|
||||
|
||||
The LLMs and their capabilities are as follows:
|
||||
{"\n".join([f"{k}: {','.join(v)}" for k,v in model_manager.model_capabilities.items()])}
|
||||
|
||||
You will be provided with the current step of execution, the results of the previous steps in order, and the current chain of thought so far.
|
||||
If the chain of thought is too long, a summary of the current chain of thought will be provided.
|
||||
Your job is to use all this information to determine which of the provided LLMs would be best suited to complete the provided step given the capabilities of the LLM.
|
||||
Your response should be the full name of the LLM that should complete the step.
|
||||
Reply with only one of the following values: \n{'\n'.join(list(model_manager.model_capabilities.keys()))}
|
||||
"""
|
||||
|
||||
def select_best_model(step: str, results: List[str], context: str) -> tuple[str, str]:
|
||||
prompt = f"Current Step: {step}\n\nResults So Far: {results}\n\nCurrent Chain of Thought: {context}"
|
||||
logger.debug("Selecting best model", prompt=prompt, system=SELECT_BEST_MODEL_PROMPT)
|
||||
response = model_manager.generate_text("llama3.2:3b", prompt, max_length=50, system=SELECT_BEST_MODEL_PROMPT)
|
||||
model_name = response.strip().lower()
|
||||
return model_name, response
|
||||
|
||||
|
||||
def summarize_context(context: str) -> tuple[str, str]:
|
||||
prompt = f"Summarize the following context: {context}"
|
||||
logger.debug("Summarizing context", prompt=prompt)
|
||||
response = model_manager.generate_text("llama3.2:3b", prompt, max_length=300)
|
||||
return response, response
|
||||
|
||||
EXECUTE_STEP_PROMPT = """
|
||||
You are a large language model that has been selected to complete a step within a larger task.
|
||||
You have been selected to complete this step due to your specific capabilities.
|
||||
You will be provided with the job to do in this current step, the results of the previous steps in order, and the current chain of thought so far.
|
||||
If the chain of thought is too long, a summary of the current chain of thought will be provided.
|
||||
Your job is to use all this information to complete the step.
|
||||
Your response should be in two parts. The first part should be your thought process in completing the step, how you went about solving the step, assumptions made, relation to previous steps, and challenges faced.
|
||||
You must then output a line with the word "RESPONSE".
|
||||
The second part should be the result of completing your step.
|
||||
The second part should contain nothing except the result of completing your step.
|
||||
Only complete your part of the step. Do not extrapolate beyond the bounds of the step. Do not trample on the results of previous steps. Build on the results of previous steps, and use them to inform your work.
|
||||
Do not include any preamble or other text, only the result of completing your step.
|
||||
Do not use any markdown formatting, code formatting, or any other formatting.
|
||||
"""
|
||||
|
||||
def execute_step(step: str, model: str, results: List[str], context: str) -> tuple[str, str]:
|
||||
prompt = f"Current Step: {step}\n\nResults So Far: {results}\n\nCurrent Chain of Thought: {context}"
|
||||
logger.debug("Executing step", step=step, model=model, prompt=prompt)
|
||||
response = model_manager.generate_text(model, prompt, max_length=1024, system=EXECUTE_STEP_PROMPT)
|
||||
response_step = response.split("RESPONSE")[1].strip()
|
||||
response_thinking = response.split("RESPONSE")[0].strip()
|
||||
return response_step, response_thinking
|
||||
|
||||
def generate_final_response(user_input: str, plan: List[str], step_results: List[str]) -> tuple[str, str]:
|
||||
prompt = f"Question: {user_input}\n\nPlan:\n"
|
||||
for i, step in enumerate(plan):
|
||||
prompt += f"{i+1}. {step}\n"
|
||||
prompt += "\nResults:\n"
|
||||
for i, result in enumerate(step_results):
|
||||
prompt += f"Step {i+1} result: {result}\n"
|
||||
prompt += "\nBased on the above information, provide a comprehensive answer to the original question."
|
||||
logger.debug("Generating final response", prompt=prompt)
|
||||
response = model_manager.generate_text("qwen2.5:7b", prompt, max_length=500)
|
||||
return response, response
|
||||
PRIMARY_MODEL = "llama3.1:8b"
|
||||
|
||||
UPDATE_INTERVAL = 0.1 # 100ms, configurable
|
||||
|
||||
@ -292,4 +203,4 @@ def send_system_resources():
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting LLM Chat Server")
|
||||
threading.Thread(target=send_system_resources, daemon=True).start()
|
||||
socketio.run(app, debug=True, host="0.0.0.0", port=5000)
|
||||
socketio.run(app, debug=True, host="0.0.0.0", port=5001)
|
11
models.py
11
models.py
@ -6,9 +6,9 @@ logger = structlog.get_logger()
|
||||
class ModelManager:
|
||||
def __init__(self):
|
||||
self.model_capabilities = {
|
||||
"qwen2.5:7b": ["general_knowledge", "structured_output", "multilingual", "instruction_following", "structured_data"],
|
||||
"ajindal/llama3.1-storm:8b": ["general_knowledge", "reasoning", "tool_calling", "conversation", "multilingual", "instruction_following"],
|
||||
"llama3.1:8b": ["general_knowledge", "reasoning", "tool_calling", "conversation", "multilingual", "instruction_following"],
|
||||
"qwen2.5-coder:7b": ["code_generation", "code_analysis", "instruction_following", "math_reasoning"],
|
||||
"qwen2.5:7b": ["general_knowledge", "reasoning", "tool_calling", "conversation", "multilingual", "instruction_following"],
|
||||
"llama3.2:3b": ["summarization", "instruction_following", "tool_calling", "multilingual"],
|
||||
"llava:7b": ["visual_reasoning", "visual_conversation", "visual_tool_calling", "vision", "ocr", "multimodal"],
|
||||
}
|
||||
@ -25,8 +25,7 @@ class ModelManager:
|
||||
logger.info("Selected best model", required_capability=required_capability, selected_model=selected_model)
|
||||
return selected_model
|
||||
|
||||
def generate_text(self, model_name, prompt, max_length=100, system="You are a helpful assistant.", stream=False):
|
||||
logger.debug("Generating text", model=model_name, prompt=prompt, max_length=max_length)
|
||||
def generate_text(self, model_name, prompt, max_length=100, system="You are a helpful assistant.", tools=[]):
|
||||
# Check if model exists
|
||||
try:
|
||||
ollama.pull(model_name)
|
||||
@ -38,7 +37,9 @@ class ModelManager:
|
||||
else:
|
||||
logger.exception("Error pulling model", model=model_name, error=str(e))
|
||||
raise e
|
||||
response = ollama.generate(model=model_name, prompt=prompt, system=system, stream=stream)
|
||||
|
||||
|
||||
response = ollama.generate(model=model_name, prompt=prompt, system=system, tools=tools, max_tokens=max_length)
|
||||
logger.debug("Text generated", model=model_name, response=response['response'])
|
||||
return response['response']
|
||||
|
||||
|
63
tools.py
63
tools.py
@ -2,15 +2,18 @@ import duckduckgo_search
|
||||
import requests
|
||||
from readability.readability import Document
|
||||
from markdownify import markdownify as md
|
||||
|
||||
import sys
|
||||
import time
|
||||
import io
|
||||
import subprocess
|
||||
class Tool:
|
||||
def __init__(self, name: str, description: str, arguments: str, returns: str):
|
||||
def __init__(self, name: str, description: str, arguments: dict, returns: str):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.arguments = arguments
|
||||
self.returns = returns
|
||||
|
||||
def execute(self, arguments: str) -> str:
|
||||
def execute(self, arguments: dict) -> str:
|
||||
pass
|
||||
|
||||
|
||||
@ -30,6 +33,9 @@ class ToolManager:
|
||||
def get_tools_and_descriptions_for_prompt(self):
|
||||
return "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
|
||||
|
||||
def get_tools_for_ollama_dict(self):
|
||||
return [{'type': 'function', 'function': {'name': tool.name, 'description': tool.description, 'parameters': tool.arguments}} for tool in self.tools]
|
||||
|
||||
|
||||
class DefaultToolManager(ToolManager):
|
||||
def __init__(self):
|
||||
@ -42,11 +48,11 @@ class DefaultToolManager(ToolManager):
|
||||
|
||||
class SearchTool(Tool):
|
||||
def __init__(self):
|
||||
super().__init__("search_web", "Search the internet for information -- Takes a search query as an argument", "query:string", "results:list[string]")
|
||||
super().__init__("search_web", "Search the internet for information", {'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'The search query'}}}, "results:list[string]")
|
||||
|
||||
def execute(self, arg: str) -> str:
|
||||
res = duckduckgo_search.DDGS().text(arg, max_results=5)
|
||||
return [f"{r['title']}\n{r['body']}\n{r['href']}" for r in res]
|
||||
def execute(self, arg: dict) -> str:
|
||||
res = duckduckgo_search.DDGS().text(arg['query'], max_results=5)
|
||||
return '\n\n'.join([f"{r['title']}\n{r['body']}\n{r['href']}" for r in res])
|
||||
|
||||
|
||||
def get_readable_page_contents(url: str) -> str:
|
||||
@ -63,23 +69,48 @@ def get_readable_page_contents(url: str) -> str:
|
||||
|
||||
class GetReadablePageContentsTool(Tool):
|
||||
def __init__(self):
|
||||
super().__init__("get_readable_page_contents", "Get the contents of a web page in a readable format -- Takes a url as an argument", "url:string", "contents:string")
|
||||
super().__init__("get_readable_page_contents", "Get the contents of a web page in a readable format", {'type': 'object', 'properties': {'url': {'type': 'string', 'description': 'The url of the web page'}}}, "contents:string")
|
||||
|
||||
def execute(self, arg: str) -> str:
|
||||
return get_readable_page_contents(arg[0])
|
||||
def execute(self, arg: dict) -> str:
|
||||
return get_readable_page_contents(arg['url'])
|
||||
|
||||
|
||||
class CalculatorTool(Tool):
|
||||
def __init__(self):
|
||||
super().__init__("calculator", "Perform a calculation -- Takes a python mathematical expression as an argument", "expression:string", "result:string")
|
||||
super().__init__("calculator", "Perform a calculation", {'type': 'object', 'properties': {'expression': {'type': 'string', 'description': 'The mathematical expression to evaluate, should be a python mathematical expression'}}}, "result:string")
|
||||
|
||||
def execute(self, arg: str) -> str:
|
||||
return str(eval(arg[0]))
|
||||
def execute(self, arg: dict) -> str:
|
||||
try:
|
||||
return str(exec(arg["expression"]))
|
||||
except Exception as e:
|
||||
return f"Error executing code: {str(e)}"
|
||||
|
||||
|
||||
class PythonCodeTool(Tool):
|
||||
def __init__(self):
|
||||
super().__init__("python_code", "Execute python code -- Takes a python code as an argument, code must be a single line of valid python", "code:string", "result:string")
|
||||
super().__init__("python_code", "Execute python code", {'type': 'object', 'properties': {'code': {'type': 'string', 'description': 'The python code to execute, should be a single line of valid python'}}}, "result:string")
|
||||
|
||||
def execute(self, arg: str) -> str:
|
||||
return str(eval(arg[0]))
|
||||
def execute(self, arg: dict) -> str:
|
||||
try:
|
||||
start_time = time.time()
|
||||
process = subprocess.Popen(['python', '-c', arg['code']],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True)
|
||||
stdout, stderr = process.communicate(timeout=10) # 10 second timeout
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
|
||||
result = {
|
||||
'stdout': stdout,
|
||||
'stderr': stderr,
|
||||
'return_value': process.returncode,
|
||||
'execution_time': execution_time
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
return "Error: Code execution timed out after 10 seconds"
|
||||
except Exception as e:
|
||||
return f"Error executing code: {str(e)}"
|
||||
|
||||
return '\n'.join([f"{k}: {v}" for k, v in result.items()])
|
Reference in New Issue
Block a user