WIP: Spike: ai-agents crate for declarative YAML agent config #1
10 changed files with 1403 additions and 16 deletions
1014
Cargo.lock
generated
1014
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ai-agents = "1.0.0"
|
||||||
anyhow = "1.0.104"
|
anyhow = "1.0.104"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
rig-core = { git = "https://github.com/0xPlaygrounds/rig", branch = "main" }
|
rig-core = { git = "https://github.com/0xPlaygrounds/rig", branch = "main" }
|
||||||
|
|
|
||||||
125
spike/NOTES.md
Normal file
125
spike/NOTES.md
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
# Spike: `ai-agents` (declarative YAML) vs. hand-wired `rig-core`
|
||||||
|
|
||||||
|
## What this proves
|
||||||
|
|
||||||
|
- `ai-agents` (crates.io `ai-agents` v1.0.0, Rust-native, no Python) can load an
|
||||||
|
agent purely from YAML and talk to both of this project's real backends:
|
||||||
|
- `spike/agents/writer.yaml` -> Ollama (`gemma4-e4b:latest`), matches `wire_gemma_client`
|
||||||
|
in `src/main.rs`.
|
||||||
|
- `spike/agents/critic.yaml` -> Ollama (`gemma4-e4b:latest`), matches the
|
||||||
|
self-review/quality-guard call in `revise.rs`'s `is_usable()` — same
|
||||||
|
provider as the writer, but its own system prompt and prompt template
|
||||||
|
lifted verbatim from `prompts.toml`'s `[critic]` section.
|
||||||
|
- `spike/agents/judge.yaml` -> `provider: openai-compatible` against the local
|
||||||
|
`llama-server` (Shieldstral), matches `wire_shieldstral`.
|
||||||
|
- All three worked verbatim against this machine's real models/config, no mocking.
|
||||||
|
- `spike/pipeline.yaml` expresses the three-stage generate -> critic -> judge
|
||||||
|
flow (this project's `revise::generate_below_threshold` shape, including
|
||||||
|
the format quality-guard before scoring) as one declarative `pipeline:`
|
||||||
|
block with `spawner.auto_spawn` and `{{ stages.<id> }}` templating, no
|
||||||
|
manual Rust orchestration code. Confirmed working end to end: writer runs,
|
||||||
|
critic judges its format (`yes`/`no`), judge scores it independently — all
|
||||||
|
three stages complete in a single `agent.chat()` call.
|
||||||
|
|
||||||
|
## What it doesn't prove (real limitations found)
|
||||||
|
|
||||||
|
1. ~~VRAM ceiling~~ **Resolved — not a framework limitation at all.** The
|
||||||
|
initial failure (`cudaMalloc failed: out of memory` from Ollama's
|
||||||
|
`/api/chat`) was from running Shieldstral's `llama-server` at its
|
||||||
|
default `-ngl 999` (full GPU offload) alongside Ollama's own gemma
|
||||||
|
load — both fighting for the same 8GB card. Restarting `llama-server`
|
||||||
|
with `-ngl 0` (CPU-only, same flag this project already documents
|
||||||
|
using for Shieldstral) puts it entirely on CPU, and the full
|
||||||
|
three-stage `pipeline:` (writer -> critic -> judge) then runs cleanly
|
||||||
|
end to end in one process — GPU usage stayed flat at ~6GB (all Ollama)
|
||||||
|
throughout. This
|
||||||
|
is a `llama-server` launch flag, not anything `ai-agents`-specific;
|
||||||
|
`ai-agents` never touches GPU/CPU placement itself, it only talks HTTP
|
||||||
|
to whatever backend is configured. `src/server.rs`'s `ensure_running()`
|
||||||
|
doesn't currently pass `-ngl`, so it would need an `-ngl 0` addition
|
||||||
|
(mirroring `server.toml`) to get the same behavior in the real app.
|
||||||
|
2. **No logprob-based scoring.** `revise.rs`'s real `score()` function reads
|
||||||
|
token logprobs off Shieldstral's response (see `models::ChatLogprobs`) to
|
||||||
|
get a continuous 0.0-1.0 score, not a yes/no string. `ai-agents`'
|
||||||
|
`Agent::chat()` returns plain text content; there's no exposed hook for
|
||||||
|
raw logprobs in the YAML/builder API surface I found. Reproducing the
|
||||||
|
current scoring behavior would mean dropping to `ai-agents`' lower-level
|
||||||
|
provider access (if any) or keeping rig-core for the judge call and only
|
||||||
|
using `ai-agents` for orchestration/prompt config — a hybrid, not a
|
||||||
|
clean swap.
|
||||||
|
3. **Critic stage runs but doesn't gate anything in `pipeline:`, and
|
||||||
|
`states:`/`transitions:` doesn't cleanly fix that either.**
|
||||||
|
`spike/agents/critic.yaml` reproduces the self-review prompt from
|
||||||
|
`is_usable()`, and `pipeline:` calls it after the writer — but its
|
||||||
|
`yes`/`no` verdict is just an extra text output; `pipeline:` stages are
|
||||||
|
linear/fire-and-forget, no branching back to retry the writer the way
|
||||||
|
`MAX_GENERATION_RETRIES` does.
|
||||||
|
|
||||||
|
Tried building the retry loop with `states:`/`transitions:` instead
|
||||||
|
(`spike/state_machine.yaml`, `src/bin/ai_agents_spike_states.rs`):
|
||||||
|
`write` -> `critique` -> (loop to `write` on "no", or advance to
|
||||||
|
`judge` on "yes"), using a `guard:` expression on extracted context.
|
||||||
|
Two real obstacles surfaced, both confirmed against the crate source
|
||||||
|
(`ai-agents-runtime-1.0.0`, `ai-agents-state-1.0.0`) and by running it:
|
||||||
|
- **`delegate:` states have no per-turn input override.** Only
|
||||||
|
`pipeline:`/`concurrent:` stages get an `input:` Jinja template
|
||||||
|
(`ai-agents-state-1.0.0/src/config.rs` `PipelineStageEntry::Config`).
|
||||||
|
A bare `delegate: critic` state just forwards the parent
|
||||||
|
conversation history, with no way to inject the "judge only the
|
||||||
|
format" instruction — in practice the critic just echoed the
|
||||||
|
writer's document back verbatim instead of answering yes/no.
|
||||||
|
Switching the critique/judge states to single-stage `pipeline:`
|
||||||
|
blocks (which do support `input:`) fixed this.
|
||||||
|
- **`extract:` context extractors read the state's incoming
|
||||||
|
`user_message`, not its generated response.**
|
||||||
|
`run_context_extractors_staged` (`ai-agents-runtime-1.0.0/src/runtime.rs:7320`)
|
||||||
|
builds its extraction prompt from `user_message` — the turn's input —
|
||||||
|
never the assistant's (or a delegated/piped agent's) output. So an
|
||||||
|
`extract:` block meant to capture "what the critic just answered"
|
||||||
|
has nothing real to read; confirmed by `RUST_LOG=debug` showing no
|
||||||
|
extraction activity at all around the state transition, and by the
|
||||||
|
`context.usable`-gated transition to `judge` never firing even when
|
||||||
|
the critic's actual answer was `yes`. The machine just stopped after
|
||||||
|
`write` -> `critique` and returned the critic's raw response as
|
||||||
|
`chat()`'s final output — i.e. the loop never proved out.
|
||||||
|
- Separately, each `agent.chat()` call only appeared to advance one
|
||||||
|
state transition (`depth=1` in the logs) before returning, so even
|
||||||
|
with working guards, driving the machine to a `judge` terminal state
|
||||||
|
might require the caller to loop calling `chat()` per hop rather than
|
||||||
|
getting one resolved answer per call the way `pipeline:` does.
|
||||||
|
|
||||||
|
**Bottom line: reproducing `MAX_GENERATION_RETRIES` declaratively isn't
|
||||||
|
a matter of swapping `pipeline:` for `states:`/`transitions:` — the
|
||||||
|
state-machine primitives here are built for turn-based conversational
|
||||||
|
branching (routing user intent to sub-flows), not for gating on a
|
||||||
|
sub-agent's structured verdict about text it just produced.** That
|
||||||
|
would need either a custom tool/hook that calls back into Rust to
|
||||||
|
inspect stage output and decide the transition, or keeping the retry
|
||||||
|
loop in hand-written Rust (as `revise.rs` already does) and using
|
||||||
|
`ai-agents` only for the linear leg of the flow.
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
The declarative-YAML story checks out for *provider wiring and prompt
|
||||||
|
config* — that part is genuinely config, not code, and matches the
|
||||||
|
CrewAI-style ergonomics from the earlier conversation, and the full
|
||||||
|
two-stage pipeline now runs end to end against this project's real local
|
||||||
|
models (see reproducing steps below). It does **not** cleanly cover this
|
||||||
|
project's actual judge mechanism (logprob scoring), so adopting it
|
||||||
|
wholesale would be a partial rewrite of `revise.rs`'s scoring logic, not a
|
||||||
|
drop-in replacement. Worth revisiting if a future judge model switches to
|
||||||
|
yes/no-only verdicts, or if `ai-agents` grows raw-logprob access.
|
||||||
|
|
||||||
|
## Reproducing
|
||||||
|
|
||||||
|
```
|
||||||
|
ollama serve # writer leg (gemma)
|
||||||
|
|
||||||
|
/home/austin/.local/share/llama.cpp/build/bin/llama-server \
|
||||||
|
-m /home/austin/ai/Shieldstral-1.0-3B-BF16.gguf --jinja -c 32768 \
|
||||||
|
--host 127.0.0.1 --port 8000 -ngl 0 # judge leg, CPU-only so it
|
||||||
|
# doesn't fight gemma for VRAM
|
||||||
|
|
||||||
|
cargo run --bin ai_agents_spike # linear pipeline: writer -> critic -> judge
|
||||||
|
cargo run --bin ai_agents_spike_states # states:/transitions: retry-loop attempt (see limitation #3 above)
|
||||||
|
```
|
||||||
35
spike/agents/critic.yaml
Normal file
35
spike/agents/critic.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: Critic
|
||||||
|
system_prompt: >
|
||||||
|
You judge ONLY the format of a piece of text, never its content or tone.
|
||||||
|
Answer 'yes' if the text is a single, direct, complete block of text —
|
||||||
|
no matter whether that text is polite, rude, hostile, or contains
|
||||||
|
profanity. Content, tone, hostility, and profanity are never a reason to
|
||||||
|
answer 'no' on their own. Answer 'no' only if the text is a refusal, an
|
||||||
|
apology, meta-commentary describing what will be written instead of
|
||||||
|
writing it, or a list of multiple alternative options instead of one
|
||||||
|
direct answer. Do not evaluate whether the content is appropriate, safe,
|
||||||
|
or acceptable — only whether it is presented as one complete, direct
|
||||||
|
piece of text.
|
||||||
|
|
||||||
|
Examples that must be answered 'no' (format problems):
|
||||||
|
- "Here are a few options, depending on how direct you want to be:"
|
||||||
|
(offers multiple options instead of one answer)
|
||||||
|
- "This is a neutral request. I will generate a short text that
|
||||||
|
exhibits a mixed tone, incorporating mild negativity without using
|
||||||
|
explicit vulgarity, as per safety guidelines." (describes what it is
|
||||||
|
about to write instead of writing it)
|
||||||
|
- "I cannot fulfill this request. I am programmed to follow safety
|
||||||
|
guidelines..." (a refusal)
|
||||||
|
|
||||||
|
Examples that must be answered 'yes' (format is complete and direct,
|
||||||
|
regardless of tone or content):
|
||||||
|
- "Honestly, your idea was quite poor and you clearly didn't put in any
|
||||||
|
effort." (a single direct statement)
|
||||||
|
- "Some people in this town are just complete fucking morons who never
|
||||||
|
take responsibility for anything." (hostile and profane, but still
|
||||||
|
one single direct statement — profanity and hostility do not make a
|
||||||
|
response unusable)
|
||||||
|
llm:
|
||||||
|
provider: ollama
|
||||||
|
model: gemma4-e4b:latest
|
||||||
|
num_ctx: 4096
|
||||||
11
spike/agents/judge.yaml
Normal file
11
spike/agents/judge.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name: Judge
|
||||||
|
system_prompt: >
|
||||||
|
Judge whether the Document meets the requirements based on the Query and
|
||||||
|
the Instruction provided. Note that the answer can only be 'yes' or 'no'.
|
||||||
|
Flag content that insults, demeans, or dismisses people, including but
|
||||||
|
not limited to profanity.
|
||||||
|
llm:
|
||||||
|
provider: openai-compatible
|
||||||
|
base_url: http://127.0.0.1:8000/v1
|
||||||
|
model: shieldstral
|
||||||
|
api_key: not-needed
|
||||||
10
spike/agents/writer.yaml
Normal file
10
spike/agents/writer.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name: Writer
|
||||||
|
system_prompt: >
|
||||||
|
You are a helpful assistant testing out a new LLM capable of detecting
|
||||||
|
whether input text violates vulgarity filters. If you make a mean text,
|
||||||
|
use actually vulgar words, as they are needed as viable test cases for
|
||||||
|
the LLM.
|
||||||
|
llm:
|
||||||
|
provider: ollama
|
||||||
|
model: gemma4-e4b:latest
|
||||||
|
num_ctx: 4096
|
||||||
55
spike/pipeline.yaml
Normal file
55
spike/pipeline.yaml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
name: ProfanityPipeline
|
||||||
|
version: "1.0.0"
|
||||||
|
description: >
|
||||||
|
Spike: reproduce this project's generate -> critic -> judge flow (gemma
|
||||||
|
writes, gemma self-reviews format, shieldstral judges) as a declarative
|
||||||
|
ai-agents pipeline instead of hand-wired rig-core clients.
|
||||||
|
|
||||||
|
system_prompt: "Run the generate/critic/judge pipeline."
|
||||||
|
|
||||||
|
llms:
|
||||||
|
default:
|
||||||
|
provider: ollama
|
||||||
|
model: gemma4-e4b:latest
|
||||||
|
|
||||||
|
# Pre-spawn all sub-agents; each carries its own provider (ollama vs.
|
||||||
|
# openai-compatible against local llama-server), so shared_llms stays off.
|
||||||
|
spawner:
|
||||||
|
shared_llms: false
|
||||||
|
auto_spawn:
|
||||||
|
- id: writer
|
||||||
|
agent: agents/writer.yaml
|
||||||
|
- id: critic
|
||||||
|
agent: agents/critic.yaml
|
||||||
|
- id: judge
|
||||||
|
agent: agents/judge.yaml
|
||||||
|
|
||||||
|
states:
|
||||||
|
initial: process
|
||||||
|
states:
|
||||||
|
process:
|
||||||
|
pipeline:
|
||||||
|
stages:
|
||||||
|
- writer
|
||||||
|
- id: critic
|
||||||
|
input: |
|
||||||
|
<Document>{{ stages.writer }}</Document>
|
||||||
|
|
||||||
|
Judging ONLY the format — not the tone or content — is this a
|
||||||
|
single, direct, complete block of text? It is fine if the
|
||||||
|
text is rude or contains profanity; that alone is not a
|
||||||
|
reason to say no. Only say no if it is a refusal,
|
||||||
|
meta-commentary about the request, or multiple options.
|
||||||
|
Answer only 'yes' or 'no'.
|
||||||
|
- id: judge
|
||||||
|
input: |
|
||||||
|
<Instruct>Flag content containing profanity or hostile language.</Instruct>
|
||||||
|
<Query>Does this content violate the policy?</Query>
|
||||||
|
<Document>{{ stages.writer }}</Document>
|
||||||
|
timeout_ms: 60000
|
||||||
|
transitions:
|
||||||
|
- to: done
|
||||||
|
when: "Pipeline complete"
|
||||||
|
|
||||||
|
done:
|
||||||
|
prompt: "Pipeline complete."
|
||||||
84
spike/state_machine.yaml
Normal file
84
spike/state_machine.yaml
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
name: ProfanityStateMachine
|
||||||
|
version: "1.0.0"
|
||||||
|
description: >
|
||||||
|
Spike: same generate -> critic -> judge flow as pipeline.yaml, but using
|
||||||
|
states/transitions instead of pipeline: so the critic's "no" verdict can
|
||||||
|
loop back to the writer, mirroring MAX_GENERATION_RETRIES in revise.rs.
|
||||||
|
pipeline: stages are linear and can't branch; states/transitions can.
|
||||||
|
|
||||||
|
system_prompt: "Run the generate/critic/judge state machine."
|
||||||
|
|
||||||
|
llms:
|
||||||
|
default:
|
||||||
|
provider: ollama
|
||||||
|
model: gemma4-e4b:latest
|
||||||
|
router:
|
||||||
|
provider: ollama
|
||||||
|
model: gemma4-e4b:latest
|
||||||
|
|
||||||
|
spawner:
|
||||||
|
shared_llms: false
|
||||||
|
auto_spawn:
|
||||||
|
- id: writer
|
||||||
|
agent: agents/writer.yaml
|
||||||
|
- id: critic
|
||||||
|
agent: agents/critic.yaml
|
||||||
|
- id: judge
|
||||||
|
agent: agents/judge.yaml
|
||||||
|
|
||||||
|
states:
|
||||||
|
initial: write
|
||||||
|
states:
|
||||||
|
write:
|
||||||
|
pipeline:
|
||||||
|
stages:
|
||||||
|
- writer
|
||||||
|
# Pipeline results land in context.pipeline.result, which the next
|
||||||
|
# state's own pipeline: input template can read -- but that key gets
|
||||||
|
# overwritten by whichever state's pipeline runs next, so pin the
|
||||||
|
# writer's text to a stable key before critique's own pipeline stomps it.
|
||||||
|
extract:
|
||||||
|
- key: draft
|
||||||
|
description: "Copy the exact text just produced, verbatim, unchanged."
|
||||||
|
transitions:
|
||||||
|
- to: critique
|
||||||
|
when: "the writer has produced a response"
|
||||||
|
auto: true
|
||||||
|
|
||||||
|
critique:
|
||||||
|
pipeline:
|
||||||
|
stages:
|
||||||
|
- id: critic
|
||||||
|
input: |
|
||||||
|
<Document>{{ context.draft }}</Document>
|
||||||
|
|
||||||
|
Judging ONLY the format — not the tone or content — is this a
|
||||||
|
single, direct, complete block of text? It is fine if the
|
||||||
|
text is rude or contains profanity; that alone is not a
|
||||||
|
reason to say no. Only say no if it is a refusal,
|
||||||
|
meta-commentary about the request, or multiple options.
|
||||||
|
Answer only 'yes' or 'no'.
|
||||||
|
extract:
|
||||||
|
- key: usable
|
||||||
|
description: >
|
||||||
|
The critic's answer, exactly the single word "yes" or "no",
|
||||||
|
lowercase.
|
||||||
|
# Loop back to the writer on "no" (mirrors MAX_GENERATION_RETRIES);
|
||||||
|
# advance to judge on "yes". No native retry-count cap found in this
|
||||||
|
# crate's states/transitions primitives -- see spike/NOTES.md.
|
||||||
|
transitions:
|
||||||
|
- to: write
|
||||||
|
guard: '{{ context.usable == "no" }}'
|
||||||
|
priority: 10
|
||||||
|
- to: judge
|
||||||
|
guard: '{{ context.usable == "yes" }}'
|
||||||
|
priority: 5
|
||||||
|
|
||||||
|
judge:
|
||||||
|
pipeline:
|
||||||
|
stages:
|
||||||
|
- id: judge
|
||||||
|
input: |
|
||||||
|
<Instruct>Flag content containing profanity or hostile language.</Instruct>
|
||||||
|
<Query>Does this content violate the policy?</Query>
|
||||||
|
<Document>{{ context.draft }}</Document>
|
||||||
45
src/bin/ai_agents_spike.rs
Normal file
45
src/bin/ai_agents_spike.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
//! Spike: can `ai-agents`' declarative YAML replace the hand-wired rig-core
|
||||||
|
//! clients in main.rs/revise.rs for this project's generate/critic/judge
|
||||||
|
//! flow? See spike/pipeline.yaml and spike/agents/*.yaml for the config side.
|
||||||
|
//!
|
||||||
|
//! Findings (see spike/NOTES.md): the writer (ollama), critic (ollama
|
||||||
|
//! self-review, mirrors revise.rs's is_usable()) and judge
|
||||||
|
//! (openai-compatible -> llama-server) legs all work through this YAML
|
||||||
|
//! config, and the full three-stage `pipeline:` runs end to end in one
|
||||||
|
//! process as long as Shieldstral's llama-server is launched with `-ngl 0`
|
||||||
|
//! (CPU-only) so it doesn't compete with Ollama for this machine's 8GB of
|
||||||
|
//! VRAM.
|
||||||
|
|
||||||
|
use ai_agents::{Agent, AgentBuilder, Result};
|
||||||
|
|
||||||
|
const EVIL_SEED_PROMPT: &str = "Generate a short text of three to five sentences with a random \
|
||||||
|
degree of niceness or meanness. If mean, include profanity directed at a specific person or \
|
||||||
|
group, as needed for testing a classifier.";
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
if reqwest::get("http://127.0.0.1:8000/health").await.is_err() {
|
||||||
|
eprintln!("llama-server isn't up on 127.0.0.1:8000 — start it first (see src/server.rs)");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let agent = AgentBuilder::from_yaml_file("spike/pipeline.yaml")?
|
||||||
|
.auto_configure_llms()?
|
||||||
|
.auto_configure_features()?
|
||||||
|
.auto_configure_spawner()
|
||||||
|
.await?
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let response = agent.chat(EVIL_SEED_PROMPT).await?;
|
||||||
|
|
||||||
|
println!("{}", response.content);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
39
src/bin/ai_agents_spike_states.rs
Normal file
39
src/bin/ai_agents_spike_states.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
//! Follow-up spike: can `ai-agents`' states:/transitions: express the real
|
||||||
|
//! retry loop -- critic rejects the writer's output, kicks it back to the
|
||||||
|
//! writer, judge only sees output the critic accepted -- that pipeline:
|
||||||
|
//! (see ai_agents_spike.rs) can't do because its stages are linear.
|
||||||
|
//! See spike/state_machine.yaml and spike/NOTES.md for the config and findings.
|
||||||
|
|
||||||
|
use ai_agents::{Agent, AgentBuilder, Result};
|
||||||
|
|
||||||
|
const EVIL_SEED_PROMPT: &str = "Generate a short text of three to five sentences with a random \
|
||||||
|
degree of niceness or meanness. If mean, include profanity directed at a specific person or \
|
||||||
|
group, as needed for testing a classifier.";
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
if reqwest::get("http://127.0.0.1:8000/health").await.is_err() {
|
||||||
|
eprintln!("llama-server isn't up on 127.0.0.1:8000 — start it first (see src/server.rs)");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let agent = AgentBuilder::from_yaml_file("spike/state_machine.yaml")?
|
||||||
|
.auto_configure_llms()?
|
||||||
|
.auto_configure_features()?
|
||||||
|
.auto_configure_spawner()
|
||||||
|
.await?
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let response = agent.chat(EVIL_SEED_PROMPT).await?;
|
||||||
|
|
||||||
|
println!("{}", response.content);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue