mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 03:39:29 +02:00
Lot 6 : parité d'un tour complet avec le Go (journal d'affichage)
internal/loki/parity_turn_test.go passe sept tours par la vraie route /api/chat/send du Go contre un faux moteur — réponse simple, raisonnement, outil puis réponse, « pensé sans agir », erreur du moteur, et en mode Code un contrat déjà tenu et un contrat abandonné après deux corrections — et range dans rust/loki-rs/tests/parity_turn/ le journal d'affichage du tour (coalescé, mesures et horodatages masqués), l'historique rangé, les corps envoyés au moteur, ainsi que les réponses du Go que la façade demandera pendant ce tour (/turn/start, /turn/view, /turn/verifier). Ce que le prompt de la machine porte de propre au lancement (dossiers temporaires, discussion, date, hôte) est masqué des deux côtés. tests/lot6.rs (parite_du_tour_complet) rejoue les sept tours par chat::generate et retrouve journal, historique et requêtes à l'identique, numérotation du journal coalescé comprise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
10 files changed
+8298
-5
No files matched your search
@@ -0,0 +1,282 @@
|
||||
package loki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// parity_turn_test.go — journaux de référence d'un TOUR COMPLET, tel que le
|
||||
// voit l'interface (lot 6 de la migration Rust).
|
||||
//
|
||||
// Chaque scénario passe par la vraie route /api/chat/send du Go, contre un
|
||||
// faux moteur : le journal d'affichage du tour (deltas, une fois le tour fini
|
||||
// et coalescé), l'historique rangé et les corps envoyés au moteur sont rangés
|
||||
// dans rust/loki-rs/tests/parity_turn/<nom>.json, avec ce que la façade
|
||||
// demandera au Go pendant le même tour (réponses de /internal/v1/turn/start
|
||||
// et /turn/view). Le test Rust (tests/lot6.rs, parite_du_tour_complet) rejoue
|
||||
// le tour par chat::generate et doit retrouver ces journaux.
|
||||
//
|
||||
// LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteTourComplet
|
||||
|
||||
const parityTurnDir = "../../rust/loki-rs/tests/parity_turn"
|
||||
|
||||
type parityTurnScenario struct {
|
||||
Name string `json:"name"`
|
||||
Cfg map[string]string `json:"cfg"`
|
||||
// Agent : mode agent allumé (réglage de la machine, clé d'état « agent »).
|
||||
Agent bool `json:"agent,omitempty"`
|
||||
// Code : discussion en mode Code, avec ces critères d'acceptation.
|
||||
Code bool `json:"code,omitempty"`
|
||||
Criteria []Criterion `json:"criteria,omitempty"`
|
||||
Send map[string]any `json:"send"`
|
||||
Replies []parityReply `json:"replies"`
|
||||
ToolResults map[string]string `json:"tool_results"`
|
||||
}
|
||||
|
||||
type parityTurnExpect struct {
|
||||
Requests []any `json:"requests"`
|
||||
Log []map[string]any `json:"log"`
|
||||
Messages []Message `json:"messages"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type parityTurnFixture struct {
|
||||
parityTurnScenario
|
||||
// Réponses du Go à la façade pendant ce tour.
|
||||
Start any `json:"start"`
|
||||
View any `json:"view"`
|
||||
Verifier any `json:"verifier,omitempty"`
|
||||
Expect parityTurnExpect `json:"expect"`
|
||||
}
|
||||
|
||||
func parityTurnScenarios() []parityTurnScenario {
|
||||
send := func(msg string) map[string]any { return map[string]any{"message": msg} }
|
||||
base := func(name string, s map[string]any, replies ...parityReply) parityTurnScenario {
|
||||
s["cid"] = "c-" + name // un envoi par scénario : jamais pris pour un réessai
|
||||
return parityTurnScenario{
|
||||
Name: name, Cfg: map[string]string{"MEM_MODE": "off"}, Send: s, Replies: replies,
|
||||
ToolResults: map[string]string{"bash": "a.txt\nb.txt", "read": "1: ligne"},
|
||||
}
|
||||
}
|
||||
agent := func(sc parityTurnScenario) parityTurnScenario {
|
||||
sc.Agent = true
|
||||
return sc
|
||||
}
|
||||
code := func(sc parityTurnScenario, crit ...Criterion) parityTurnScenario {
|
||||
sc.Agent, sc.Code, sc.Criteria = true, true, crit
|
||||
return sc
|
||||
}
|
||||
return []parityTurnScenario{
|
||||
base("reponse_simple", send("Bonjour"),
|
||||
pOK(pContent("Bon"), pContent("jour !"), pFinish("stop"))),
|
||||
base("raisonnement", send("Combien ?"),
|
||||
pOK(pReasoning("Je compte."), pContent("Quatre."), pFinish("stop"))),
|
||||
agent(base("outil_puis_reponse", send("Liste les fichiers"),
|
||||
pOK(pToolCall(0, "c1", "bash", `{"command":"ls"}`), pFinish("tool_calls")),
|
||||
pOK(pContent("Deux fichiers."), pFinish("stop")))),
|
||||
agent(base("pense_sans_agir", send("Cherche"),
|
||||
pOK(pReasoning("Il faudrait lister."), pFinish("stop")),
|
||||
pOK(pContent("Voilà."), pFinish("stop")))),
|
||||
code(base("code_contrat_deja_tenu", send("Corrige le test"),
|
||||
pOK(pContent("Corrigé."), pFinish("stop"))),
|
||||
Criterion{ID: 1, Text: "les tests passent", Status: "passed"}),
|
||||
code(base("code_contrat_abandonne", send("Construis le jeu"),
|
||||
pOK(pContent("r0"), pFinish("stop")), pOK(pContent("r1"), pFinish("stop")),
|
||||
pOK(pContent("r2"), pFinish("stop")), pOK(pContent("v1"), pFinish("stop")),
|
||||
pOK(pContent("r3"), pFinish("stop")), pOK(pContent("v2"), pFinish("stop")),
|
||||
pOK(pContent("r4"), pFinish("stop")), pOK(pContent("v3"), pFinish("stop"))),
|
||||
Criterion{ID: 1, Text: "le build passe", Status: "pending"}),
|
||||
base("erreur_moteur", send("Bonjour"),
|
||||
parityReply{Status: 400, Lines: []string{`{"error":{"code":400,"message":"requête invalide","type":"invalid_request_error"}}`}}),
|
||||
}
|
||||
}
|
||||
|
||||
// parityTurnLog : le journal du tour, mesures et horaires masqués.
|
||||
func parityTurnLog(evs []LogEvent) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(evs))
|
||||
for _, e := range evs {
|
||||
d := map[string]any{}
|
||||
for k, v := range e.Delta {
|
||||
switch k {
|
||||
case "stats":
|
||||
v = "…"
|
||||
case "ts0":
|
||||
continue // horodatage du premier fragment coalescé
|
||||
}
|
||||
d[k] = v
|
||||
}
|
||||
out = append(out, d)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func runParityTurn(t *testing.T, sc parityTurnScenario) parityTurnFixture {
|
||||
t.Helper()
|
||||
testHome(t)
|
||||
t.Setenv(internalTokenEnv, jetonTest)
|
||||
var (
|
||||
mu sync.Mutex
|
||||
requests []any
|
||||
next int
|
||||
)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/health" {
|
||||
io.WriteString(w, `{"status":"ok"}`)
|
||||
return
|
||||
}
|
||||
if !strings.HasSuffix(r.URL.Path, "/chat/completions") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
raw, _ := io.ReadAll(r.Body)
|
||||
var body any
|
||||
_ = json.Unmarshal(raw, &body)
|
||||
mu.Lock()
|
||||
requests = append(requests, body)
|
||||
i := next
|
||||
next++
|
||||
mu.Unlock()
|
||||
if i >= len(sc.Replies) {
|
||||
t.Errorf("%s : requête %d non scriptée", sc.Name, i+1)
|
||||
http.Error(w, "non scripté", http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
rep := sc.Replies[i]
|
||||
if rep.Status == 200 {
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
}
|
||||
w.WriteHeader(rep.Status)
|
||||
var b strings.Builder
|
||||
for _, l := range rep.Lines {
|
||||
b.WriteString(l + "\n\n")
|
||||
}
|
||||
io.WriteString(w, b.String())
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
u, _ := url.Parse(srv.URL)
|
||||
cfg := map[string]string{"PORT": u.Port()}
|
||||
for k, v := range sc.Cfg {
|
||||
cfg[k] = v
|
||||
}
|
||||
if err := WriteConfig(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := setAgentEnabled(sc.Agent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
execToolHook = func(_ context.Context, name string, _ map[string]any, _ string, _ Caps) toolExecResult {
|
||||
if r, ok := sc.ToolResults[name]; ok {
|
||||
return toolExecResult{Result: r}
|
||||
}
|
||||
return toolExecResult{Result: "[erreur] outil inconnu : " + name}
|
||||
}
|
||||
t.Cleanup(func() { execToolHook = nil })
|
||||
|
||||
conv.Reset()
|
||||
if sc.Code {
|
||||
id := convEnsureActive()
|
||||
if err := setConvMode(id, "code"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
critSave(id, sc.Criteria)
|
||||
}
|
||||
body, _ := json.Marshal(sc.Send)
|
||||
// Ce que la façade obtiendra du Go pour ce même envoi.
|
||||
start := turnJSON(t, "/internal/v1/turn/start", string(body), 200)
|
||||
caps, _ := json.Marshal(map[string]any{"caps": start["turn_caps"]})
|
||||
view := turnJSON(t, "/internal/v1/turn/view", string(caps), 200)
|
||||
var verifier any
|
||||
if sc.Code {
|
||||
verifier = turnJSON(t, "/internal/v1/turn/verifier", string(caps), 200)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handleChatSend(w, httptest.NewRequest("POST", "/api/chat/send", bytes.NewReader(body)))
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
conv.mu.Lock()
|
||||
done := !conv.Generating
|
||||
conv.mu.Unlock()
|
||||
if done || time.Now().After(deadline) {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
persistQ.flush()
|
||||
conv.mu.Lock()
|
||||
exp := parityTurnExpect{
|
||||
Requests: requests,
|
||||
Log: parityTurnLog(conv.Log),
|
||||
Messages: append([]Message{}, conv.Messages...),
|
||||
Status: w.Code,
|
||||
}
|
||||
conv.mu.Unlock()
|
||||
if exp.Requests == nil {
|
||||
exp.Requests = []any{}
|
||||
}
|
||||
return parityTurnFixture{parityTurnScenario: sc, Start: start, View: view, Verifier: verifier, Expect: exp}
|
||||
}
|
||||
|
||||
// parityMasks : ce que le prompt de la machine porte de propre à CE lancement
|
||||
// (dossiers temporaires, discussion, date, hôte). La façade reçoit ce prompt
|
||||
// tel quel par /turn/view : masqué ici, il l'est des deux côtés.
|
||||
var parityMasks = []struct {
|
||||
re *regexp.Regexp
|
||||
with string
|
||||
}{
|
||||
{regexp.MustCompile(`(/private)?` + regexp.QuoteMeta(filepath.Clean(os.TempDir())) + `/Test[^/"\\]*/[0-9]+`), "{{TMP}}"},
|
||||
{regexp.MustCompile(`\bc[0-9]{15,}\b`), "{{CONV}}"},
|
||||
{regexp.MustCompile(`Date: [0-9]{4}-[0-9]{2}-[0-9]{2}`), "Date: {{DATE}}"},
|
||||
{regexp.MustCompile(`Machine: host=[^,]*, [a-z0-9]+/[a-z0-9]+(, user=[^,.]*)?`), "Machine: host={{HOST}}"},
|
||||
}
|
||||
|
||||
func parityMask(b []byte) []byte {
|
||||
for _, m := range parityMasks {
|
||||
b = m.re.ReplaceAll(b, []byte(m.with))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestPariteTourComplet(t *testing.T) {
|
||||
update := os.Getenv("LOKI_PARITY_UPDATE") == "1"
|
||||
for _, sc := range parityTurnScenarios() {
|
||||
t.Run(sc.Name, func(t *testing.T) {
|
||||
got, err := parityJSON(runParityTurn(t, sc))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = parityMask(got)
|
||||
path := filepath.Join(parityTurnDir, sc.Name+".json")
|
||||
if update {
|
||||
if err := os.MkdirAll(parityTurnDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, got, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
want, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("journal de référence absent (%v) : LOKI_PARITY_UPDATE=1 pour le créer", err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("le tour Go ne produit plus le journal rangé dans %s.\n"+
|
||||
"Si c'est voulu : LOKI_PARITY_UPDATE=1 go test ./internal/loki -run TestPariteTourComplet,\n"+
|
||||
"puis cargo test --test lot6 parite_du_tour_complet dit si le port Rust suit.\n--- obtenu ---\n%s", path, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,12 @@ pub struct FakeGo {
|
||||
pub tool_log: Mutex<Vec<String>>,
|
||||
/// Le Go garde la conversation (raison), sinon la façade la tient.
|
||||
pub go_keeps_chat: Mutex<Option<String>>,
|
||||
/// Réponses imposées de /turn/start et /turn/view (journaux de référence
|
||||
/// du Go), et résultat par outil.
|
||||
pub turn_start: Mutex<Option<serde_json::Value>>,
|
||||
pub turn_view: Mutex<Option<serde_json::Value>>,
|
||||
pub turn_verifier: Mutex<Option<serde_json::Value>>,
|
||||
pub tool_results: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl FakeGo {
|
||||
@@ -219,6 +225,9 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
if msg.trim().is_empty() {
|
||||
return resp(400, r#"{"error":"message vide"}"#);
|
||||
}
|
||||
if let Some(v) = st.turn_start.lock().unwrap().clone() {
|
||||
return resp(200, v.to_string());
|
||||
}
|
||||
let code = *st.code.lock().unwrap();
|
||||
let caps = serde_json::json!({"Agent": true, "Mem": "off", "Code": code, "Role": ""});
|
||||
let out = serde_json::json!({
|
||||
@@ -229,6 +238,9 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
}
|
||||
"/internal/v1/turn/view" => {
|
||||
st.turn_log.lock().unwrap().push("view".into());
|
||||
if let Some(v) = st.turn_view.lock().unwrap().clone() {
|
||||
return resp(200, v.to_string());
|
||||
}
|
||||
let out = serde_json::json!({
|
||||
"head": [{"role": "system", "content": "Tu es Loki."}],
|
||||
"prefix": "PRÉAMBULE",
|
||||
@@ -258,6 +270,9 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||
let v: serde_json::Value = serde_json::from_slice(&body).unwrap_or_default();
|
||||
st.turn_log.lock().unwrap().push("verifier".into());
|
||||
if let Some(out) = st.turn_verifier.lock().unwrap().clone() {
|
||||
return resp(200, out.to_string());
|
||||
}
|
||||
let mut caps = v["caps"].clone();
|
||||
caps["Role"] = "verifier".into();
|
||||
let out = serde_json::json!({
|
||||
@@ -317,11 +332,9 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
||||
*val = serde_json::to_vec(&list).unwrap();
|
||||
}
|
||||
}
|
||||
let result = st
|
||||
.tool_result
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
let by_name = st.tool_results.lock().unwrap().get(name).cloned();
|
||||
let result = by_name
|
||||
.or_else(|| st.tool_result.lock().unwrap().clone())
|
||||
.unwrap_or_else(|| format!("exec:{}", v["name"].as_str().unwrap()));
|
||||
let out = serde_json::json!({
|
||||
"label": v["args"]["command"],
|
||||
|
||||
@@ -554,6 +554,17 @@ async fn fake_engine_gated(
|
||||
g.acquire().await.unwrap().forget();
|
||||
}
|
||||
let out = replies.lock().unwrap().pop_front().unwrap_or_default();
|
||||
// « STATUS 400\n<corps> » : refus du moteur.
|
||||
if let Some(rest) = out.strip_prefix("STATUS ")
|
||||
&& let Some((code, body)) = rest.split_once('\n')
|
||||
{
|
||||
let mut r = hyper::Response::new(Full::new(bytes::Bytes::from(
|
||||
body.to_string(),
|
||||
)));
|
||||
*r.status_mut() =
|
||||
hyper::StatusCode::from_u16(code.parse().unwrap()).unwrap();
|
||||
return Ok(r);
|
||||
}
|
||||
ok(out)
|
||||
}
|
||||
},
|
||||
@@ -1443,3 +1454,117 @@ async fn deverrouillage_recharge_la_discussion_chargee_vide() {
|
||||
// Déjà rempli : jamais rechargé par-dessus.
|
||||
assert_eq!(s.reload_if_empty().await, Ok(false));
|
||||
}
|
||||
|
||||
/// Parité d'un tour COMPLET avec le Go (`tests/parity_turn/*.json`, produits
|
||||
/// par `TestPariteTourComplet` en passant par la vraie route /api/chat/send) :
|
||||
/// le Go de test sert les mêmes réponses de /turn/start et /turn/view que le
|
||||
/// vrai, le moteur les mêmes flux, les outils les mêmes résultats. Doivent se
|
||||
/// retrouver : le journal d'affichage du tour (mesures masquées), l'historique
|
||||
/// rangé, et les corps envoyés au moteur.
|
||||
#[tokio::test]
|
||||
async fn parite_du_tour_complet() {
|
||||
let dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/parity_turn");
|
||||
let mut paths: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.map(|e| e.unwrap().path())
|
||||
.filter(|p| p.extension().is_some_and(|x| x == "json"))
|
||||
.collect();
|
||||
paths.sort();
|
||||
assert!(!paths.is_empty());
|
||||
let mut failures = Vec::new();
|
||||
for p in &paths {
|
||||
let fx: serde_json::Value = serde_json::from_slice(&std::fs::read(p).unwrap()).unwrap();
|
||||
let name = fx["name"].as_str().unwrap().to_string();
|
||||
let replies: Vec<String> = fx["replies"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let body: String = r["lines"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|l| format!("{}\n\n", l.as_str().unwrap()))
|
||||
.collect();
|
||||
match r["status"].as_u64().unwrap() {
|
||||
200 => body,
|
||||
s => format!("STATUS {s}\n{body}"),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let (url, seen) = fake_engine(replies).await;
|
||||
let (svc, go, _stop, _t) = service(Some(&url)).await;
|
||||
*go.turn_start.lock().unwrap() = Some(fx["start"].clone());
|
||||
*go.turn_view.lock().unwrap() = Some(fx["view"].clone());
|
||||
*go.tool_results.lock().unwrap() =
|
||||
serde_json::from_value(fx["tool_results"].clone()).unwrap();
|
||||
if !fx["verifier"].is_null() {
|
||||
*go.turn_verifier.lock().unwrap() = Some(fx["verifier"].clone());
|
||||
}
|
||||
if let Some(c) = fx.get("criteria") {
|
||||
let id = svc.sessions.conv().id();
|
||||
go.set("chat", &format!("crit:{id}"), &c.to_string());
|
||||
}
|
||||
let sent = svc.send(fx["send"].clone()).await;
|
||||
let status = if sent.is_ok() { 200 } else { 0 };
|
||||
until_done(&svc, 1).await;
|
||||
until(|| go.turn_log.lock().unwrap().contains(&"done".to_string())).await;
|
||||
|
||||
let exp = &fx["expect"];
|
||||
let mut diffs = Vec::new();
|
||||
if status != exp["status"].as_u64().unwrap() {
|
||||
diffs.push(format!("statut {status}, attendu {}", exp["status"]));
|
||||
}
|
||||
let requests: Vec<serde_json::Value> = seen
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(_, b)| b.clone())
|
||||
.collect();
|
||||
if serde_json::Value::Array(requests.clone()) != exp["requests"] {
|
||||
diffs.push(format!(
|
||||
"requêtes :\n obtenu {}\n attendu {}",
|
||||
serde_json::Value::Array(requests),
|
||||
exp["requests"]
|
||||
));
|
||||
}
|
||||
let log: Vec<serde_json::Value> = svc.sessions.conv().with_state(|st| {
|
||||
st.log
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let mut d = e.delta.clone();
|
||||
d.remove("ts0");
|
||||
if d.contains_key("stats") {
|
||||
d.insert("stats".into(), serde_json::json!("…"));
|
||||
}
|
||||
serde_json::Value::Object(d)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
if serde_json::Value::Array(log.clone()) != exp["log"] {
|
||||
diffs.push(format!(
|
||||
"journal :\n obtenu {}\n attendu {}",
|
||||
serde_json::Value::Array(log),
|
||||
exp["log"]
|
||||
));
|
||||
}
|
||||
let msgs = svc.sessions.conv().with_state(|st| st.messages.clone());
|
||||
let msgs = serde_json::to_value(&msgs).unwrap();
|
||||
if msgs != exp["messages"] {
|
||||
diffs.push(format!(
|
||||
"historique :\n obtenu {msgs}\n attendu {}",
|
||||
exp["messages"]
|
||||
));
|
||||
}
|
||||
if !diffs.is_empty() {
|
||||
failures.push(format!("{name} :\n{}", diffs.join("\n")));
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"{} tour(s) sur {} divergent du Go :\n\n{}",
|
||||
failures.len(),
|
||||
paths.len(),
|
||||
failures.join("\n\n")
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "erreur_moteur",
|
||||
"cfg": {
|
||||
"MEM_MODE": "off"
|
||||
},
|
||||
"send": {
|
||||
"cid": "c-erreur_moteur",
|
||||
"message": "Bonjour"
|
||||
},
|
||||
"replies": [
|
||||
{
|
||||
"status": 400,
|
||||
"lines": [
|
||||
"{\"error\":{\"code\":400,\"message\":\"requête invalide\",\"type\":\"invalid_request_error\"}}"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tool_results": {
|
||||
"bash": "a.txt\nb.txt",
|
||||
"read": "1: ligne"
|
||||
},
|
||||
"start": {
|
||||
"caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
},
|
||||
"code_hint": false,
|
||||
"content": "Bonjour",
|
||||
"files": [],
|
||||
"model": "",
|
||||
"turn_caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"head": [],
|
||||
"prefix": "",
|
||||
"tools": []
|
||||
},
|
||||
"expect": {
|
||||
"requests": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "Bonjour",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
{
|
||||
"user": "Bonjour"
|
||||
},
|
||||
{
|
||||
"error": "llama-server a renvoyé 400 : {\"error\":{\"code\":400,\"message\":\"requête invalide\",\"type\":\"invalid_request_error\"}}"
|
||||
},
|
||||
{
|
||||
"ctx_used": 5
|
||||
},
|
||||
{
|
||||
"turn_done": true
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Bonjour"
|
||||
}
|
||||
],
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,870 @@
|
||||
{
|
||||
"name": "outil_puis_reponse",
|
||||
"cfg": {
|
||||
"MEM_MODE": "off"
|
||||
},
|
||||
"agent": true,
|
||||
"send": {
|
||||
"cid": "c-outil_puis_reponse",
|
||||
"message": "Liste les fichiers"
|
||||
},
|
||||
"replies": [
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"function\":{\"arguments\":\"{\\\"command\\\":\\\"ls\\\"}\",\"name\":\"bash\"},\"id\":\"c1\",\"index\":0,\"type\":\"function\"}]}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"Deux fichiers.\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tool_results": {
|
||||
"bash": "a.txt\nb.txt",
|
||||
"read": "1: ligne"
|
||||
},
|
||||
"start": {
|
||||
"caps": {
|
||||
"Agent": true,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
},
|
||||
"code_hint": false,
|
||||
"content": "Liste les fichiers",
|
||||
"files": [],
|
||||
"model": "",
|
||||
"turn_caps": {
|
||||
"Agent": true,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"head": [],
|
||||
"prefix": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expect": {
|
||||
"requests": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"role": "system"
|
||||
},
|
||||
{
|
||||
"content": "Liste les fichiers",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"parallel_tool_calls": false,
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7,
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"role": "system"
|
||||
},
|
||||
{
|
||||
"content": "Liste les fichiers",
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"arguments": "{\"command\":\"ls\"}",
|
||||
"name": "bash"
|
||||
},
|
||||
"id": "c1",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "a.txt\nb.txt",
|
||||
"role": "tool",
|
||||
"tool_call_id": "c1"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"parallel_tool_calls": false,
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7,
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
{
|
||||
"user": "Liste les fichiers"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"tool_used": {
|
||||
"done": true,
|
||||
"label": "ls",
|
||||
"name": "bash",
|
||||
"result": "a.txt\nb.txt",
|
||||
"result_chars": 11,
|
||||
"typing": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": "Deux fichiers.",
|
||||
"seq0": 8,
|
||||
"toks": 1
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"turn_done": true
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Liste les fichiers"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "c1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "bash",
|
||||
"arguments": "{\"command\":\"ls\"}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"content": "a.txt\nb.txt",
|
||||
"tool_call_id": "c1"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Deux fichiers."
|
||||
}
|
||||
],
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
{
|
||||
"name": "pense_sans_agir",
|
||||
"cfg": {
|
||||
"MEM_MODE": "off"
|
||||
},
|
||||
"agent": true,
|
||||
"send": {
|
||||
"cid": "c-pense_sans_agir",
|
||||
"message": "Cherche"
|
||||
},
|
||||
"replies": [
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"reasoning_content\":\"Il faudrait lister.\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"Voilà.\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tool_results": {
|
||||
"bash": "a.txt\nb.txt",
|
||||
"read": "1: ligne"
|
||||
},
|
||||
"start": {
|
||||
"caps": {
|
||||
"Agent": true,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
},
|
||||
"code_hint": false,
|
||||
"content": "Cherche",
|
||||
"files": [],
|
||||
"model": "",
|
||||
"turn_caps": {
|
||||
"Agent": true,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"head": [],
|
||||
"prefix": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expect": {
|
||||
"requests": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"role": "system"
|
||||
},
|
||||
{
|
||||
"content": "Cherche",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"parallel_tool_calls": false,
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7,
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "You are Loki, an expert assistant operating directly on this machine with real tools.\n\nThe shell is bash: use its syntax.\nFor anything about the system or files, use bash instead of guessing. Act immediately — call the right tool, then answer. Never end your turn after only thinking. Be concise.\nTo give the user a file, link it in Markdown with its path relative to your working directory — [le rapport](rapport.pdf) — which downloads it. A raw server path is useless: they read you in a browser.\nYou can schedule work for yourself with task_create — only for what must recur or happen later, never a one-off you can do now. A script from your scripts folder runs as a task with no model at all.\n\nDate: {{DATE}}\n\nMachine: host={{HOST}}, cwd={{TMP}}/workspace/discussions/{{CONV}}. This is your working folder: relative paths in write/edit/bash resolve here, and it is the DEFAULT place for scratch work — notes, outputs, a clone, a test. But it is DISPOSABLE: deleting the discussion wipes it. Any script you want to KEEP (or schedule), write it into your scripts folder {{TMP}}/scripts instead — a separate folder a workspace wipe won't touch; you write and run scripts there normally. Do NOT install or write files into system directories such as /usr/local/bin, /usr, /bin or /etc: those need root and are not yours. Only use an absolute path outside this folder (except your scripts folder) when the user explicitly named that location.",
|
||||
"role": "system"
|
||||
},
|
||||
{
|
||||
"content": "Cherche",
|
||||
"role": "user"
|
||||
},
|
||||
{
|
||||
"content": "You reasoned but did not call a tool or answer. Act NOW: call the appropriate tool directly (e.g. mem_search/mem_read/bash), or give your final answer if you already have the info. Don't explain, act.",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"parallel_tool_calls": false,
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7,
|
||||
"tools": [
|
||||
{
|
||||
"function": {
|
||||
"description": "Run a shell command (bash syntax) and return stdout, stderr and exit code: inspect the system, read files and logs, run scripts. To CREATE or REWRITE a file use write instead — never echo/cat/python -c. Avoid destructive commands unless asked.",
|
||||
"name": "bash",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "The command",
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"description": "Timeout s (default 30, max 300)",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Create or replace a file with the exact content given (parent dirs created, content verbatim, no escaping). ALWAYS use this for a script or any text file — NEVER build one through the shell with echo, cat, python -c or Set-Content: quoting breaks.",
|
||||
"name": "write",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Full content",
|
||||
"type": "string"
|
||||
},
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Patch a file by exact replacement: old → new. old must appear EXACTLY once (add context to make it unique). Prefer this over rewriting a whole file.",
|
||||
"name": "edit",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"new": {
|
||||
"description": "Replacement",
|
||||
"type": "string"
|
||||
},
|
||||
"old": {
|
||||
"description": "Exact text to replace (unique)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"old",
|
||||
"new"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Bring back verbatim an earlier block archived when this conversation was compacted, from the id shown as recall:rN in the summary.",
|
||||
"name": "recall",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Block id, e.g. \"r7\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Find an archived block and its recall id by keywords, when it is not listed in the current summary. Then call recall(id).",
|
||||
"name": "recall_search",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Default 5, max 20",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Keywords describing the block.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"query"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "List your scheduled tasks with their state, next run, and last result. Pass an id to get that task's full last report.",
|
||||
"name": "task_list",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Show only this task, with its full last report",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Schedule work to run later, once or repeating. Give 'prompt' for an AI task, OR 'script' (a saved script name) for a script task that runs with no model and no tokens.",
|
||||
"name": "task_create",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "One-time task at this local time: \"HH:MM\" (next occurrence) or \"YYYY-MM-DD HH:MM\"",
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Start active (default true)",
|
||||
"type": "boolean"
|
||||
},
|
||||
"in_minutes": {
|
||||
"description": "One-time task, N minutes from now",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"description": "AI task: self-contained instruction to run each time (omit if using script)",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"description": "Repeating: \"@every 2h\" (interval), \"@every 1d@09:00\" (daily time), or a 5-field cron \"min hour dom mon dow\". Omit for a one-time task (use in_minutes or at)",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script task: name of a script file you put in the scripts folder, run each time with no AI involved",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Change one or more fields of a scheduled task by id.",
|
||||
"name": "task_update",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "false pauses, true resumes",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"description": "Delete a scheduled task by id.",
|
||||
"name": "task_delete",
|
||||
"parameters": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
{
|
||||
"user": "Cherche"
|
||||
},
|
||||
{
|
||||
"reasoning_content": "Il faudrait lister.",
|
||||
"seq0": 2,
|
||||
"toks": 1
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"drop_reasoning": true
|
||||
},
|
||||
{
|
||||
"content": "Voilà.",
|
||||
"seq0": 7,
|
||||
"toks": 1
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"turn_done": true
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Cherche"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Voilà."
|
||||
}
|
||||
],
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"name": "raisonnement",
|
||||
"cfg": {
|
||||
"MEM_MODE": "off"
|
||||
},
|
||||
"send": {
|
||||
"cid": "c-raisonnement",
|
||||
"message": "Combien ?"
|
||||
},
|
||||
"replies": [
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"reasoning_content\":\"Je compte.\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"Quatre.\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tool_results": {
|
||||
"bash": "a.txt\nb.txt",
|
||||
"read": "1: ligne"
|
||||
},
|
||||
"start": {
|
||||
"caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
},
|
||||
"code_hint": false,
|
||||
"content": "Combien ?",
|
||||
"files": [],
|
||||
"model": "",
|
||||
"turn_caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"head": [],
|
||||
"prefix": "",
|
||||
"tools": []
|
||||
},
|
||||
"expect": {
|
||||
"requests": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "Combien ?",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
{
|
||||
"user": "Combien ?"
|
||||
},
|
||||
{
|
||||
"reasoning_content": "Je compte.",
|
||||
"seq0": 2,
|
||||
"toks": 1
|
||||
},
|
||||
{
|
||||
"content": "Quatre.",
|
||||
"seq0": 3,
|
||||
"toks": 1
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"turn_done": true
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Combien ?"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Quatre."
|
||||
}
|
||||
],
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"name": "reponse_simple",
|
||||
"cfg": {
|
||||
"MEM_MODE": "off"
|
||||
},
|
||||
"send": {
|
||||
"cid": "c-reponse_simple",
|
||||
"message": "Bonjour"
|
||||
},
|
||||
"replies": [
|
||||
{
|
||||
"status": 200,
|
||||
"lines": [
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"Bon\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{\"content\":\"jour !\"}}]}",
|
||||
"data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\"}],\"usage\":{\"completion_tokens\":5,\"prompt_tokens\":100}}",
|
||||
"data: [DONE]"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tool_results": {
|
||||
"bash": "a.txt\nb.txt",
|
||||
"read": "1: ligne"
|
||||
},
|
||||
"start": {
|
||||
"caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
},
|
||||
"code_hint": false,
|
||||
"content": "Bonjour",
|
||||
"files": [],
|
||||
"model": "",
|
||||
"turn_caps": {
|
||||
"Agent": false,
|
||||
"Code": false,
|
||||
"ComputerUse": false,
|
||||
"Internet": false,
|
||||
"Mem": "off",
|
||||
"Role": ""
|
||||
}
|
||||
},
|
||||
"view": {
|
||||
"head": [],
|
||||
"prefix": "",
|
||||
"tools": []
|
||||
},
|
||||
"expect": {
|
||||
"requests": [
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"content": "Bonjour",
|
||||
"role": "user"
|
||||
}
|
||||
],
|
||||
"model": "loki",
|
||||
"stream": true,
|
||||
"stream_options": {
|
||||
"include_usage": true
|
||||
},
|
||||
"temperature": 0.7
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
{
|
||||
"user": "Bonjour"
|
||||
},
|
||||
{
|
||||
"content": "Bonjour !",
|
||||
"seq0": 2,
|
||||
"toks": 2
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"stats": "…"
|
||||
},
|
||||
{
|
||||
"turn_done": true
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Bonjour"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Bonjour !"
|
||||
}
|
||||
],
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user