Artifacts (Canvas)
Artifacts (also known as a “Canvas”) give the agent a persistent work surface separate from the chat. Use artifacts to hold substantive deliverables (documents, specs, code), while keeping chat replies brief and status‑oriented.
You can also check out the Console App to see how artifacts are integrated into a chat interface.

Implementation
Section titled “Implementation”The examples below implement a simple artifacts workflow using tools:
artifact_create
— create a new documentartifact_update
— replace content and print a color diffartifact_get
/artifact_list
— fetch and list documentsartifact_delete
— remove a document
import { Agent, type AgentItem } from "@hoangvvo/llm-agent";import { zodTool } from "@hoangvvo/llm-agent/zod";import { diffLines } from "diff";import { z } from "zod";import { getModel } from "./get-model.ts";
// Artifacts/Canvas feature example: the agent maintains named deliverables// (documents/code/specs) separate from the chat by using tools to create,// update, retrieve, list, and delete artifacts.
type ArtifactKind = "markdown" | "text" | "code";
interface Artifact { id: string; title: string; kind: ArtifactKind; content: string; version: number; updated_at: string;}
class InMemoryArtifactStore { #map = new Map<string, Artifact>();
create(a: { id?: string; title: string; kind: ArtifactKind; content: string; }): Artifact { const id = (a.id?.trim() ? a.id : Math.random().toString(36).slice(2, 11))!; const now = new Date().toISOString(); const artifact: Artifact = { id, title: a.title, kind: a.kind, content: a.content, version: 1, updated_at: now, }; this.#map.set(id, artifact); return artifact; }
update(a: { id: string; content: string }): Artifact { const existing = this.#map.get(a.id); if (!existing) throw new Error(`Artifact not found: ${a.id}`); const next: Artifact = { ...existing, content: a.content, version: existing.version + 1, updated_at: new Date().toISOString(), }; this.#map.set(a.id, next); return next; }
get(id: string): Artifact { const a = this.#map.get(id); if (!a) throw new Error(`Artifact not found: ${id}`); return a; }
list(): Artifact[] { return [...this.#map.values()].sort((a, b) => a.id.localeCompare(b.id)); }
delete(id: string): { success: boolean } { const existed = this.#map.delete(id); return { success: existed }; }}
const store = new InMemoryArtifactStore();const model = getModel("openai", "gpt-4o");
const overviewPrompt = `Use artifacts (documents/canvases) for substantive deliverables like documents, plans, specs, or code. Keep chat replies brief and status-oriented; put the full content into an artifact via the tools. Always reference artifacts by id.`;
const rulesPrompt = `- Prefer creating/updating artifacts instead of pasting large content into chat- When asked to revise or extend prior work, read/update the relevant artifact- Keep the chat response short: what changed, where it lives (artifact id), and next steps`;
// Minimal colored diff rendering (single dep: diff)const color = (s: string, code: number) => `\x1b[${code}m${s}\x1b[0m`;const green = (s: string) => color(s, 32);const red = (s: string) => color(s, 31);const dim = (s: string) => color(s, 2);function renderDiff(oldText: string, newText: string): string { const parts = diffLines(oldText, newText); const lines: string[] = []; for (const p of parts) { const valLines = p.value.replace(/\n$/, "").split("\n"); for (const ln of valLines) { if (p.added) lines.push(green(`+ ${ln}`)); else if (p.removed) lines.push(red(`- ${ln}`)); else lines.push(dim(` ${ln}`)); } } return lines.join("\n");}
const artifactsAgent = new Agent<void>({ name: "artifacts", model, instructions: [overviewPrompt, rulesPrompt], tools: [ zodTool({ name: "artifact_create", description: "Create a new artifact (document/canvas) and return the created artifact", parameters: z.object({ title: z.string(), kind: z.enum(["markdown", "text", "code"]), content: z.string(), }), async execute(args) { console.log( `[artifacts.create] id=(auto) title=${args.title} kind=${args.kind}`, ); const artifact = store.create(args as any); return { content: [{ type: "text", text: JSON.stringify({ artifact }) }], is_error: false, }; }, }), zodTool({ name: "artifact_update", description: "Replace the content of an existing artifact and return it", parameters: z.object({ id: z.string(), content: z.string() }), async execute({ id, content }) { const before = store.get(id).content; console.log(`[artifacts.update] id=${id} len=${content.length}`); const artifact = store.update({ id, content }); console.log( "\n=== Diff (old → new) ===\n" + renderDiff(before, artifact.content) + "\n========================\n", ); return { content: [{ type: "text", text: JSON.stringify({ artifact }) }], is_error: false, }; }, }), zodTool({ name: "artifact_get", description: "Fetch a single artifact by id", parameters: z.object({ id: z.string() }), async execute({ id }) { console.log(`[artifacts.get] id=${id}`); const artifact = store.get(id); return { content: [{ type: "text", text: JSON.stringify({ artifact }) }], is_error: false, }; }, }), zodTool({ name: "artifact_list", description: "List all artifacts", parameters: z.object({}), async execute() { console.log(`[artifacts.list]`); const artifacts = store.list(); return { content: [{ type: "text", text: JSON.stringify({ artifacts }) }], is_error: false, }; }, }), zodTool({ name: "artifact_delete", description: "Delete an artifact by id", parameters: z.object({ id: z.string() }), async execute({ id }) { console.log(`[artifacts.delete] id=${id}`); const result = store.delete(id); return { content: [{ type: "text", text: JSON.stringify(result) }], is_error: false, }; }, }), ],});
// Demo: ask the agent to create an artifact, then revise it.const items1: AgentItem[] = [ { type: "message", role: "user", content: [ { type: "text", text: `We need a product requirements document for a new Todo app.Please draft it in markdown with sections: Overview, Goals, Non-Goals, Requirements.Keep your chat reply short and save the full document to a separate document we can keep iterating on.`, }, ], },];
const res1 = await artifactsAgent.run({ context: undefined, input: items1 });console.dir(res1.content, { depth: null });console.log("Artifacts after creation:");console.dir(store.list(), { depth: null });
const items2: AgentItem[] = [ { type: "message", role: "user", content: [ { type: "text", text: `Please revise the document: expand the Goals section with 3 concrete goals and add a Milestones section. Keep your chat reply brief.`, }, ], },];
const res2 = await artifactsAgent.run({ context: undefined, input: items2 });console.dir(res2.content, { depth: null });console.log("Artifacts after update:");console.dir(store.list(), { depth: null });
use std::{ collections::HashMap, sync::{Arc, Mutex},};
use dotenvy::dotenv;use llm_agent::{Agent, AgentItem, AgentRequest, AgentTool, AgentToolResult};use llm_sdk::{JSONSchema, Message, Part};use serde::{Deserialize, Serialize};use serde_json::json;
#[derive(Clone)]enum ArtifactKind { Markdown, Text, Code,}
#[derive(Clone, Serialize, Debug)]struct Artifact { id: String, title: String, kind: String, content: String, version: u32, updated_at: String,}
#[derive(Default, Clone)]struct Store { m: Arc<Mutex<HashMap<String, Artifact>>>,}
impl Store { fn create(&self, title: String, kind: String, content: String) -> Artifact { let id = format!( "{:x}", chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0) ); let a = Artifact { id: id.clone(), title, kind, content, version: 1, updated_at: chrono::Utc::now().to_rfc3339(), }; self.m.lock().unwrap().insert(id.clone(), a.clone()); a } fn update(&self, id: &str, content: String) -> (Artifact, String) { let mut map = self.m.lock().unwrap(); let a = map.get_mut(id).expect("artifact not found"); let before = a.content.clone(); a.content = content; a.version += 1; a.updated_at = chrono::Utc::now().to_rfc3339(); (a.clone(), before) } fn get(&self, id: &str) -> Artifact { self.m .lock() .unwrap() .get(id) .expect("artifact not found") .clone() } fn list(&self) -> Vec<Artifact> { self.m.lock().unwrap().values().cloned().collect() } fn delete(&self, id: &str) -> bool { self.m.lock().unwrap().remove(id).is_some() }}
// Minimal colored line diff using similarfn render_diff(old_text: &str, new_text: &str) -> String { use similar::{ChangeTag, TextDiff}; let diff = TextDiff::from_lines(old_text, new_text); let mut out = String::new(); for change in diff.iter_all_changes() { let (sign, color) = match change.tag() { ChangeTag::Delete => ("- ", "\x1b[31m"), ChangeTag::Insert => ("+ ", "\x1b[32m"), ChangeTag::Equal => (" ", "\x1b[2m"), }; out.push_str(color); out.push_str(sign); out.push_str(change.to_string().as_str()); out.push_str("\x1b[0m"); } out}
// No contexttype Ctx = ();
struct ArtifactCreate { store: Store,}#[async_trait::async_trait]impl AgentTool<Ctx> for ArtifactCreate { fn name(&self) -> String { "artifact_create".into() } fn description(&self) -> String { "Create a new document and return it".into() } fn parameters(&self) -> JSONSchema { json!({ "type":"object", "properties":{ "title":{"type":"string"}, "kind":{"type":"string", "enum":["markdown","text","code"]}, "content":{"type":"string"} }, "required":["title","kind","content"], "additionalProperties":false }) } async fn execute( &self, args: serde_json::Value, _ctx: &Ctx, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, Box<dyn std::error::Error + Send + Sync>> { #[derive(Deserialize)] struct In { title: String, kind: String, content: String, } let p: In = serde_json::from_value(args)?; println!("[artifacts.create] title={} kind={}", p.title, p.kind); let a = self.store.create(p.title, p.kind, p.content); Ok(AgentToolResult { content: vec![Part::text(json!({"artifact":a}).to_string())], is_error: false, }) }}
struct ArtifactUpdate { store: Store,}#[async_trait::async_trait]impl AgentTool<Ctx> for ArtifactUpdate { fn name(&self) -> String { "artifact_update".into() } fn description(&self) -> String { "Replace the content of a document and return it".into() } fn parameters(&self) -> JSONSchema { json!({ "type":"object", "properties":{ "id":{"type":"string"}, "content":{"type":"string"} }, "required":["id","content"], "additionalProperties":false }) } async fn execute( &self, args: serde_json::Value, _ctx: &Ctx, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, Box<dyn std::error::Error + Send + Sync>> { #[derive(Deserialize)] struct In { id: String, content: String, } let p: In = serde_json::from_value(args)?; let before = self.store.get(&p.id).content; println!("[artifacts.update] id={} len={}", p.id, p.content.len()); let (a, _before) = self.store.update(&p.id, p.content); println!( "\n=== Diff (old → new) ===\n{}========================\n", render_diff(&before, &a.content) ); Ok(AgentToolResult { content: vec![Part::text(json!({"artifact":a}).to_string())], is_error: false, }) }}
struct ArtifactGet { store: Store,}#[async_trait::async_trait]impl AgentTool<Ctx> for ArtifactGet { fn name(&self) -> String { "artifact_get".into() } fn description(&self) -> String { "Fetch a document by id".into() } fn parameters(&self) -> JSONSchema { json!({"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}) } async fn execute( &self, args: serde_json::Value, _ctx: &Ctx, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, Box<dyn std::error::Error + Send + Sync>> { #[derive(Deserialize)] struct In { id: String, } let p: In = serde_json::from_value(args)?; println!("[artifacts.get] id={}", p.id); let a = self.store.get(&p.id); Ok(AgentToolResult { content: vec![Part::text(json!({"artifact":a}).to_string())], is_error: false, }) }}
struct ArtifactList { store: Store,}#[async_trait::async_trait]impl AgentTool<Ctx> for ArtifactList { fn name(&self) -> String { "artifact_list".into() } fn description(&self) -> String { "List all documents".into() } fn parameters(&self) -> JSONSchema { json!({"type":"object","properties":{},"additionalProperties":false}) } async fn execute( &self, _args: serde_json::Value, _ctx: &Ctx, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, Box<dyn std::error::Error + Send + Sync>> { println!("[artifacts.list]"); let list = self.store.list(); Ok(AgentToolResult { content: vec![Part::text(json!({"artifacts":list}).to_string())], is_error: false, }) }}
struct ArtifactDelete { store: Store,}#[async_trait::async_trait]impl AgentTool<Ctx> for ArtifactDelete { fn name(&self) -> String { "artifact_delete".into() } fn description(&self) -> String { "Delete a document by id".into() } fn parameters(&self) -> JSONSchema { json!({"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}) } async fn execute( &self, args: serde_json::Value, _ctx: &Ctx, _state: &llm_agent::RunState, ) -> Result<AgentToolResult, Box<dyn std::error::Error + Send + Sync>> { #[derive(Deserialize)] struct In { id: String, } let p: In = serde_json::from_value(args)?; println!("[artifacts.delete] id={}", p.id); let ok = self.store.delete(&p.id); Ok(AgentToolResult { content: vec![Part::text(json!({"success":ok}).to_string())], is_error: false, }) }}
#[tokio::main]async fn main() { dotenv().ok(); let model = Arc::new(llm_sdk::openai::OpenAIModel::new( "gpt-4o", llm_sdk::openai::OpenAIModelOptions { api_key: std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set"), ..Default::default() }, ));
let store = Store::default(); let overview = "Use documents (artifacts/canvases) for substantive deliverables like \ documents, plans, specs, or code. Keep chat replies brief and \ status-oriented; put the full content into a document via the tools. Always \ reference documents by id."; let rules = "- Prefer creating/updating documents instead of pasting large content into \ chat\n- When asked to revise or extend prior work, read/update the relevant \ document\n- Keep the chat response short: what changed, where it lives (document \ id), and next steps\n";
let agent = Agent::new( llm_agent::AgentParams::new("artifacts", model) .add_instruction(overview) .add_instruction(rules) .add_tool(ArtifactCreate { store: store.clone(), }) .add_tool(ArtifactUpdate { store: store.clone(), }) .add_tool(ArtifactGet { store: store.clone(), }) .add_tool(ArtifactList { store: store.clone(), }) .add_tool(ArtifactDelete { store: store.clone(), }), );
let items1: Vec<AgentItem> = vec![AgentItem::Message(Message::user(vec![Part::text( "We need a product requirements document for a new Todo app. Please draft it in markdown \ with sections: Overview, Goals, Non-Goals, Requirements. Keep your chat reply short and \ save the full document to a separate document we can keep iterating on.", )]))]; let res1 = agent .run(AgentRequest { context: (), input: items1, }) .await .expect("run failed"); println!("{:?}", res1.content); println!("Documents after creation:\n{:?}", store.list());
let items2: Vec<AgentItem> = vec![AgentItem::Message(Message::user(vec![Part::text( "Please revise the document: expand the Goals section with 3 concrete goals and add a \ Milestones section. Keep your chat reply brief.", )]))]; let res2 = agent .run(AgentRequest { context: (), input: items2, }) .await .expect("run failed"); println!("{:?}", res2.content); println!("Documents after update:\n{:?}", store.list());}
package main
import ( "context" "encoding/json" "fmt" "log" "os" "strings" "time"
dmp "github.com/sergi/go-diff/diffmatchpatch"
llmagent "github.com/hoangvvo/llm-sdk/agent-go" llmsdk "github.com/hoangvvo/llm-sdk/sdk-go" "github.com/hoangvvo/llm-sdk/sdk-go/openai" "github.com/joho/godotenv" "github.com/sanity-io/litter")
// Artifacts/Canvas feature in Go: maintain named documents separate from chat.
type ArtifactKind string
const ( KindMarkdown ArtifactKind = "markdown" KindText ArtifactKind = "text" KindCode ArtifactKind = "code")
type Artifact struct { ID string `json:"id"` Title string `json:"title"` Kind ArtifactKind `json:"kind"` Content string `json:"content"` Version int `json:"version"` UpdatedAt string `json:"updated_at"`}
type Store struct{ m map[string]*Artifact }
func NewStore() *Store { return &Store{m: map[string]*Artifact{}} }
func (s *Store) Create(title string, kind ArtifactKind, content string) *Artifact { id := randID() a := &Artifact{ID: id, Title: title, Kind: kind, Content: content, Version: 1, UpdatedAt: time.Now().UTC().Format(time.RFC3339)} s.m[id] = a return a}
func (s *Store) Update(id, content string) (*Artifact, string) { a, ok := s.m[id] if !ok { panic("artifact not found: " + id) } before := a.Content a.Content = content a.Version++ a.UpdatedAt = time.Now().UTC().Format(time.RFC3339) return a, before}
func (s *Store) Get(id string) *Artifact { a, ok := s.m[id] if !ok { panic("artifact not found: " + id) } return a}
func (s *Store) List() []*Artifact { out := make([]*Artifact, 0, len(s.m)) for _, a := range s.m { out = append(out, a) } return out}
func (s *Store) Delete(id string) bool { _, ok := s.m[id]; delete(s.m, id); return ok }
// Minimal colored line diff using go-difffunc renderDiff(oldText, newText string) string { d := dmp.New() diffs := d.DiffMain(oldText, newText, false) // Coalesce tiny changes to lines d.DiffCleanupSemantic(diffs) var b strings.Builder lines := func(s string) []string { return strings.Split(strings.TrimSuffix(s, "\n"), "\n") } for _, df := range diffs { switch df.Type { case dmp.DiffInsert: for _, ln := range lines(df.Text) { b.WriteString("\x1b[32m+ " + ln + "\x1b[0m\n") } case dmp.DiffDelete: for _, ln := range lines(df.Text) { b.WriteString("\x1b[31m- " + ln + "\x1b[0m\n") } default: for _, ln := range lines(df.Text) { b.WriteString("\x1b[2m " + ln + "\x1b[0m\n") } } } return b.String()}
// No contexttype Ctx = struct{}
// Toolstype CreateParams struct { Title string `json:"title"` Kind string `json:"kind"` Content string `json:"content"`}type UpdateParams struct { ID string `json:"id"` Content string `json:"content"`}type GetParams struct { ID string `json:"id"`}type DeleteParams struct { ID string `json:"id"`}
type ArtifactCreateTool struct{ S *Store }
func (t *ArtifactCreateTool) Name() string { return "artifact_create" }func (t *ArtifactCreateTool) Description() string { return "Create a new document and return it" }func (t *ArtifactCreateTool) Parameters() llmsdk.JSONSchema { m := llmsdk.JSONSchema{} m["type"] = "object" props := map[string]any{ "title": map[string]any{"type": "string"}, "kind": map[string]any{"type": "string", "enum": []string{"markdown", "text", "code"}}, "content": map[string]any{"type": "string"}, } m["properties"] = props m["required"] = []string{"title", "kind", "content"} m["additionalProperties"] = false return m}func (t *ArtifactCreateTool) Execute(_ context.Context, params json.RawMessage, _ Ctx, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var p CreateParams if err := json.Unmarshal(params, &p); err != nil { return llmagent.AgentToolResult{}, err } fmt.Printf("[artifacts.create] title=%s kind=%s\n", p.Title, p.Kind) a := t.S.Create(p.Title, ArtifactKind(p.Kind), p.Content) body, _ := json.Marshal(map[string]interface{}{"artifact": a}) return llmagent.AgentToolResult{Content: []llmsdk.Part{llmsdk.NewTextPart(string(body))}, IsError: false}, nil}
type ArtifactUpdateTool struct{ S *Store }
func (t *ArtifactUpdateTool) Name() string { return "artifact_update" }func (t *ArtifactUpdateTool) Description() string { return "Replace the content of a document and return it"}func (t *ArtifactUpdateTool) Parameters() llmsdk.JSONSchema { m := llmsdk.JSONSchema{} m["type"] = "object" m["properties"] = map[string]any{"id": map[string]any{"type": "string"}, "content": map[string]any{"type": "string"}} m["required"] = []string{"id", "content"} m["additionalProperties"] = false return m}func (t *ArtifactUpdateTool) Execute(_ context.Context, params json.RawMessage, _ Ctx, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var p UpdateParams if err := json.Unmarshal(params, &p); err != nil { return llmagent.AgentToolResult{}, err } before := t.S.Get(p.ID).Content fmt.Printf("[artifacts.update] id=%s len=%d\n", p.ID, len(p.Content)) a, _ := t.S.Update(p.ID, p.Content) fmt.Printf("\n=== Diff (old → new) ===\n%s========================\n\n", renderDiff(before, a.Content)) body, _ := json.Marshal(map[string]interface{}{"artifact": a}) return llmagent.AgentToolResult{Content: []llmsdk.Part{llmsdk.NewTextPart(string(body))}, IsError: false}, nil}
type ArtifactGetTool struct{ S *Store }
func (t *ArtifactGetTool) Name() string { return "artifact_get" }func (t *ArtifactGetTool) Description() string { return "Fetch a document by id" }func (t *ArtifactGetTool) Parameters() llmsdk.JSONSchema { m := llmsdk.JSONSchema{} m["type"] = "object" m["properties"] = map[string]any{"id": map[string]any{"type": "string"}} m["required"] = []string{"id"} m["additionalProperties"] = false return m}func (t *ArtifactGetTool) Execute(_ context.Context, params json.RawMessage, _ Ctx, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var p GetParams if err := json.Unmarshal(params, &p); err != nil { return llmagent.AgentToolResult{}, err } fmt.Printf("[artifacts.get] id=%s\n", p.ID) a := t.S.Get(p.ID) body, _ := json.Marshal(map[string]interface{}{"artifact": a}) return llmagent.AgentToolResult{Content: []llmsdk.Part{llmsdk.NewTextPart(string(body))}, IsError: false}, nil}
type ArtifactListTool struct{ S *Store }
func (t *ArtifactListTool) Name() string { return "artifact_list" }func (t *ArtifactListTool) Description() string { return "List all documents" }func (t *ArtifactListTool) Parameters() llmsdk.JSONSchema { m := llmsdk.JSONSchema{} m["type"] = "object" m["properties"] = map[string]any{} m["additionalProperties"] = false return m}func (t *ArtifactListTool) Execute(_ context.Context, _ json.RawMessage, _ Ctx, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { fmt.Println("[artifacts.list]") body, _ := json.Marshal(map[string]interface{}{"artifacts": t.S.List()}) return llmagent.AgentToolResult{Content: []llmsdk.Part{llmsdk.NewTextPart(string(body))}, IsError: false}, nil}
type ArtifactDeleteTool struct{ S *Store }
func (t *ArtifactDeleteTool) Name() string { return "artifact_delete" }func (t *ArtifactDeleteTool) Description() string { return "Delete a document by id" }func (t *ArtifactDeleteTool) Parameters() llmsdk.JSONSchema { m := llmsdk.JSONSchema{} m["type"] = "object" m["properties"] = map[string]any{"id": map[string]any{"type": "string"}} m["required"] = []string{"id"} m["additionalProperties"] = false return m}func (t *ArtifactDeleteTool) Execute(_ context.Context, params json.RawMessage, _ Ctx, _ *llmagent.RunState) (llmagent.AgentToolResult, error) { var p DeleteParams if err := json.Unmarshal(params, &p); err != nil { return llmagent.AgentToolResult{}, err } fmt.Printf("[artifacts.delete] id=%s\n", p.ID) ok := t.S.Delete(p.ID) body, _ := json.Marshal(map[string]interface{}{"success": ok}) return llmagent.AgentToolResult{Content: []llmsdk.Part{llmsdk.NewTextPart(string(body))}, IsError: false}, nil}
func main() { godotenv.Load("../.env") apiKey := os.Getenv("OPENAI_API_KEY") if apiKey == "" { log.Fatal("OPENAI_API_KEY must be set") } model := openai.NewOpenAIModel("gpt-4o", openai.OpenAIModelOptions{APIKey: apiKey})
store := NewStore()
overview := "Use documents (artifacts/canvases) for substantive deliverables like documents, plans, specs, or code. Keep chat replies brief and status-oriented; put the full content into a document via the tools. Always reference documents by id." rules := "- Prefer creating/updating documents instead of pasting large content into chat\n- When asked to revise or extend prior work, read/update the relevant document\n- Keep the chat response short: what changed, where it lives (document id), and next steps\n"
agent := llmagent.NewAgent("artifacts", model, llmagent.WithInstructions( llmagent.InstructionParam[Ctx]{String: &overview}, llmagent.InstructionParam[Ctx]{String: &rules}, ), llmagent.WithTools( &ArtifactCreateTool{S: store}, &ArtifactUpdateTool{S: store}, &ArtifactGetTool{S: store}, &ArtifactListTool{S: store}, &ArtifactDeleteTool{S: store}, ), )
// Demo: create then revise a product requirements document ctx := context.Background() items1 := []llmagent.AgentItem{ llmagent.NewAgentItemMessage(llmsdk.NewUserMessage(llmsdk.NewTextPart( "We need a product requirements document for a new Todo app. Please draft it in markdown with sections: Overview, Goals, Non-Goals, Requirements. Keep your chat reply short and save the full document to a separate document we can keep iterating on.", ))), } res1, err := agent.Run(ctx, llmagent.AgentRequest[Ctx]{Context: Ctx{}, Input: items1}) if err != nil { log.Fatal(err) } litter.Dump(res1.Content) fmt.Println("Documents after creation:") litter.Dump(store.List())
items2 := []llmagent.AgentItem{ llmagent.NewAgentItemMessage(llmsdk.NewUserMessage(llmsdk.NewTextPart( "Please revise the document: expand the Goals section with 3 concrete goals and add a Milestones section. Keep your chat reply brief.", ))), } res2, err := agent.Run(ctx, llmagent.AgentRequest[Ctx]{Context: Ctx{}, Input: items2}) if err != nil { log.Fatal(err) } litter.Dump(res2.Content) fmt.Println("Documents after update:") litter.Dump(store.List())}
func randID() string { return fmt.Sprintf("%x", time.Now().UnixNano())}