This commit is contained in:
lda committed 2026-04-18 17:39:16 +07:00 Verified
1 parent b1817108cd
commit 1840ab3f30
2 files changed
+65 -34

No files matched your search

+57 -30
View File
@@ -7,15 +7,21 @@ import uuid
from dataclasses import dataclass from dataclasses import dataclass
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import TYPE_CHECKING, Any, cast
from openai import OpenAI from openai import OpenAI
if TYPE_CHECKING:
from openai.types.chat import (
ChatCompletionMessageFunctionToolCall,
ChatCompletionMessageParam,
ChatCompletionToolUnionParam,
)
DEFAULT_PROMPT_PATH = Path("prompt.md") DEFAULT_PROMPT_PATH = Path("prompt.md")
DEFAULT_SESSION_PATH = Path("session/default.jsonl") DEFAULT_SESSION_PATH = Path("session/default.jsonl")
DEFAULT_MODEL_ENV = "GROQ_MODEL_STRING" MODEL_ENV_NAMES = ("CEREBRAS_MODEL_STRING", "GROQ_MODEL_STRING")
DEFAULT_BASE_URL_ENV = "GROQ_BASE_URL" BASE_URL_ENV_NAMES = ("CEREBRAS_BASE_URL", "GROQ_BASE_URL")
DEFAULT_API_KEY_ENV = "GROQ_API_KEY" API_KEY_ENV_NAMES = ("CEREBRAS_API_KEY", "GROQ_API_KEY")
def utc_now_iso() -> str: def utc_now_iso() -> str:
@@ -78,7 +84,7 @@ def append_message(path: Path, role: str, content: str, **extra: Any) -> None:
append_jsonl(path, payload) append_jsonl(path, payload)
def shell_tool_schema() -> list[dict[str, Any]]: def shell_tool_schema() -> "list[ChatCompletionToolUnionParam]":
return [ return [
{ {
"type": "function", "type": "function",
@@ -103,12 +109,20 @@ def shell_tool_schema() -> list[dict[str, Any]]:
def build_client() -> OpenAI: def build_client() -> OpenAI:
load_dotenv() load_dotenv()
base_url = os.getenv(DEFAULT_BASE_URL_ENV) base_url = next(
api_key = os.getenv(DEFAULT_API_KEY_ENV) (os.getenv(name) for name in BASE_URL_ENV_NAMES if os.getenv(name)), None
)
api_key = next(
(os.getenv(name) for name in API_KEY_ENV_NAMES if os.getenv(name)), None
)
if not base_url: if not base_url:
raise RuntimeError(f"Missing {DEFAULT_BASE_URL_ENV} in environment or .env") raise RuntimeError(
f"Missing one of {', '.join(BASE_URL_ENV_NAMES)} in environment or .env"
)
if not api_key: if not api_key:
raise RuntimeError(f"Missing {DEFAULT_API_KEY_ENV} in environment or .env") raise RuntimeError(
f"Missing one of {', '.join(API_KEY_ENV_NAMES)} in environment or .env"
)
return OpenAI(base_url=base_url, api_key=api_key) return OpenAI(base_url=base_url, api_key=api_key)
@@ -147,7 +161,9 @@ def load_prompt(prompt_path: Path) -> str:
return prompt_path.read_text(encoding="utf-8").strip() return prompt_path.read_text(encoding="utf-8").strip()
def messages_from_session(session_path: Path, prompt_text: str) -> list[dict[str, Any]]: def messages_from_session(
session_path: Path, prompt_text: str
) -> list[ChatCompletionMessageParam]:
messages: list[dict[str, Any]] = [] messages: list[dict[str, Any]] = []
if prompt_text: if prompt_text:
messages.append({"role": "system", "content": prompt_text}) messages.append({"role": "system", "content": prompt_text})
@@ -171,7 +187,7 @@ def messages_from_session(session_path: Path, prompt_text: str) -> list[dict[str
} }
) )
return messages return cast(list[ChatCompletionMessageParam], messages)
def tool_result_content(payload: dict[str, Any]) -> str: def tool_result_content(payload: dict[str, Any]) -> str:
@@ -195,9 +211,14 @@ class TuffHarness:
self.session_path = session_path self.session_path = session_path
self.prompt_path = prompt_path self.prompt_path = prompt_path
self.client = build_client() self.client = build_client()
self.model_name = os.getenv(DEFAULT_MODEL_ENV) model_name = next(
if not self.model_name: (os.getenv(name) for name in MODEL_ENV_NAMES if os.getenv(name)), None
raise RuntimeError(f"Missing {DEFAULT_MODEL_ENV} in environment or .env") )
if not model_name:
raise RuntimeError(
f"Missing one of {', '.join(MODEL_ENV_NAMES)} in environment or .env"
)
self.model_name = model_name
def run_turn(self, max_steps: int = 8) -> TurnResult: def run_turn(self, max_steps: int = 8) -> TurnResult:
prompt_text = load_prompt(self.prompt_path) prompt_text = load_prompt(self.prompt_path)
@@ -236,7 +257,10 @@ class TuffHarness:
assistant = response.choices[0].message assistant = response.choices[0].message
assistant_content = assistant.content or "" assistant_content = assistant.content or ""
assistant_tool_calls = list(assistant.tool_calls or []) assistant_tool_calls = cast(
"list[ChatCompletionMessageFunctionToolCall]",
list(assistant.tool_calls or []),
)
append_message( append_message(
self.session_path, self.session_path,
@@ -256,21 +280,24 @@ class TuffHarness:
) )
messages.append( messages.append(
{ cast(
"role": "assistant", "ChatCompletionMessageParam",
"content": assistant_content, {
"tool_calls": [ "role": "assistant",
{ "content": assistant_content,
"id": tool_call.id, "tool_calls": [
"type": tool_call.type, {
"function": { "id": tool_call.id,
"name": tool_call.function.name, "type": tool_call.type,
"arguments": tool_call.function.arguments, "function": {
}, "name": tool_call.function.name,
} "arguments": tool_call.function.arguments,
for tool_call in assistant_tool_calls },
], }
} for tool_call in assistant_tool_calls
],
},
)
) )
if not assistant_tool_calls: if not assistant_tool_calls:
+8 -4
View File
@@ -1,10 +1,11 @@
from __future__ import annotations from __future__ import annotations
import json import json
import os
import time import time
from argparse import ArgumentParser from argparse import ArgumentParser
from pathlib import Path from pathlib import Path
from typing import Callable from typing import TYPE_CHECKING, Callable
from watchdog.events import FileSystemEvent, FileSystemEventHandler from watchdog.events import FileSystemEvent, FileSystemEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
@@ -17,6 +18,9 @@ from harness import (
read_jsonl, read_jsonl,
) )
if TYPE_CHECKING:
from watchdog.observers.api import BaseObserver
def build_cli() -> ArgumentParser: def build_cli() -> ArgumentParser:
cli = ArgumentParser(description="tuff harness") cli = ArgumentParser(description="tuff harness")
@@ -133,7 +137,7 @@ class SessionHandler(FileSystemEventHandler):
def on_any_event(self, event: FileSystemEvent) -> None: def on_any_event(self, event: FileSystemEvent) -> None:
if event.is_directory: if event.is_directory:
return return
src = Path(event.src_path).resolve() src = Path(os.fsdecode(event.src_path)).resolve()
if src in self.target_paths: if src in self.target_paths:
self.on_change() self.on_change()
@@ -157,7 +161,7 @@ def chat_loop(
print("chat mode on. type and press enter. /exit to quit, /run to force a turn.") print("chat mode on. type and press enter. /exit to quit, /run to force a turn.")
while True: while True:
try: try:
raw = input("you> ") raw = input("USER: ")
except EOFError: except EOFError:
break break
except KeyboardInterrupt: except KeyboardInterrupt:
@@ -200,7 +204,7 @@ def main() -> None:
# Print any existing transcript first. # Print any existing transcript first.
print_new_results(session_path, seen_ids) print_new_results(session_path, seen_ids)
observer: Observer | None = None observer: "BaseObserver | None" = None
if args.watch: if args.watch:
observer = Observer() observer = Observer()
handler = SessionHandler( handler = SessionHandler(