diff --git a/backend/src/agent.py b/backend/src/agent.py index 59deec2..bf2c172 100644 --- a/backend/src/agent.py +++ b/backend/src/agent.py @@ -1,5 +1,4 @@ import re -import json import inspect import time import asyncio diff --git a/backend/src/server.py b/backend/src/server.py index c9fe86d..6e59385 100644 --- a/backend/src/server.py +++ b/backend/src/server.py @@ -18,8 +18,10 @@ frontend_path = '../../frontend/dist' def encode_json(value): def encode_unknown(value): - if value is ToolSpec: + if isinstance(value, ToolSpec): return value.to_openai_dict() + elif isinstance(value, Stream): + return {'streamId': value.id} else: return repr(value) return json.dumps(value, default=encode_unknown) diff --git a/backend/src/tool.py b/backend/src/tool.py index b0b98e0..fd4cd8c 100644 --- a/backend/src/tool.py +++ b/backend/src/tool.py @@ -115,6 +115,30 @@ class ToolSpec: def __repr__(self): return f"ToolSpec(name={self.name!r}, params={[p.name for p in self.parameters]})" +class Tool: + """Base class for tools that implement their logic in a 'run' method. + + Subclasses should override 'run' to provide the tool's functionality. + """ + + def __init__(self): + pass + + def run(self, **kwargs): + """Execute the tool. To be overridden by subclasses.""" + raise NotImplementedError("Tool subclasses must override the 'run' method.") + + def to_spec(self): + """Generate a ToolSpec from this tool instance.""" + return ToolSpec( + name=None, # TODO: Computed from class name, or from class attribute + description=None, # TODO: From class doc comment + parameters=None, # TODO: Class attribute + func=self.run, + ) + + def __repr__(self): + return f"Tool(name={self.name!r}, params={[p.name for p in self.parameters]})" class ToolCall: """Represents a single tool invocation request emitted by the model. @@ -123,7 +147,7 @@ class ToolCall: id: Unique call identifier (used by OpenAI / llama-cpp protocol). tool_name: Name of the tool to invoke. arguments: Dict of argument name → value. - result: String content returned by the tool. + result: String content returned by the tool. is_error: Whether the result represents an error. """ @@ -136,10 +160,10 @@ class ToolCall: def __repr__(self): return f"ToolCall(tool={self.tool_name!r}, args={self.arguments!r})" - + def call_message(self): raise Exception("TODO: Llama message for tool call") - + def result_message(self): from inference import LlamaMessage return LlamaMessage("tool", self.result) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b7040c8..ead496c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,14 +7,11 @@ import ContextMenu from './components/main/ContextMenu.vue' import * as ui from './ui'; import * as backend from './backend'; +import * as runs from './runs'; // possible states: loading, login, signup, logged const loginState = ref('loading'); -function triggerAlert () { - ui.alert("This is an alert"); -} - function handleAuthResult (success) { loginState.value = success ? 'logged' : 'login'; } @@ -42,6 +39,11 @@ function signupSubmit (formElem) { backend.signup(username, email, password).then(handleAuthResult); } +const runsListener = runs.onCreated(run => { + console.log("new run", run); +}); +runsListener.allRuns = true; // hack? + backend.authResult().then(handleAuthResult); diff --git a/frontend/src/runs.js b/frontend/src/runs.js index 95c0c4d..f683ae9 100644 --- a/frontend/src/runs.js +++ b/frontend/src/runs.js @@ -6,15 +6,18 @@ let unsubscribeRunEvents = null; // Event listeners for UI components const listeners = { + created: [], runs: [], chats: {}, }; class RunListener { - constructor() { + constructor() { this.runIds = new Set(); this.callbacks = []; - this.cancelled = false; + this.cancelled = false; + + this.allRuns = false; } setIdList(runIds) { @@ -35,7 +38,7 @@ class RunListener { } hasRunId(runId) { - return this.runIds.has(runId); + return this.allRuns || this.runIds.has(runId); } emitChange(data) { @@ -56,6 +59,9 @@ function updateRun(event) { if (!run) { run = Object.assign({}, data, {id: runId, events: []}); runs.push(run); + for (let cb of listeners.created) { + cb(run); + } } else { console.warn("Created but existing?", run, data); } @@ -71,7 +77,7 @@ function updateRun(event) { } if (run) { - run.events.push(event); + run.events.push({name, runId, data}); for (let listener of listeners.runs) { if (listener.hasRunId(runId)) { listener.emitChange(run); @@ -118,6 +124,16 @@ export async function cancelRun(runId) { } // Subscribe to runs changes +export function onCreated(callback) { + listeners.created.push(callback); + return () => { + const index = listeners.created.indexOf(callback); + if (index > -1) { + listeners.created.splice(index, 1); + } + }; +} + export function addRunListener(callback) { const listener = new RunListener(); listener.addCallback(callback); diff --git a/frontend/src/views/RunsView.vue b/frontend/src/views/RunsView.vue index 1016534..c845c5e 100644 --- a/frontend/src/views/RunsView.vue +++ b/frontend/src/views/RunsView.vue @@ -2,10 +2,15 @@ import { ref, onMounted, onUnmounted } from 'vue'; import TitleBar from '../components/TitleBar.vue'; import * as runs from '../runs'; + import * as backend from '../backend'; const runList = ref([]); const expandedRunId = ref(null); - const runListener = runs.addRunListener(onRunEvent); + const expandEvents = ref(false); + + let cancelCreateListener = null; + let runListener = runs.addRunListener(onRunEvent); + let streamCache = {}; const toggleRun = (id) => { expandedRunId.value = expandedRunId.value == id ? null : id; @@ -21,7 +26,7 @@ }; function onRunEvent (newRun) { - let run = runList.value.findOne(r => r.id == newRun.id); + let run = runList.value.find(r => r.id == newRun.id); Object.assign(run, newRun); } @@ -30,10 +35,31 @@ run => Object.assign({}, run) ); runListener.setIdList(runList.value.map(r => r.id)); + cancelCreateListener = runs.onCreated(run => { + runList.value.push(Object.assign({}, run)); + }); }); + function isStreamEvent(event) { + return typeof event.data == "object" && event.data.streamId != null; + } + + function getStreamRef(event) { + let streamId = event.data.streamId; + let refObject = streamCache[streamId]; + if (refObject) return refObject; + + refObject = ref(""); + let stream = backend.readStream(streamId); + stream.on("data", chunk => refObject.value += chunk); + + streamCache[streamId] = refObject; + return refObject; + } + onUnmounted(() => { runListener.setIdList([]); + cancelCreateListener(); }); @@ -75,9 +101,13 @@
Input
{{ JSON.stringify(run.input, null, 2) }}
-
-
Events ({{ run.events.length }})
-
{{ JSON.stringify(run.events, null, 2) }}
+
+
event: {{ event.name }}
+ +
{{ JSON.stringify(event.data, null, 2) }}
Output