{"id":47705238,"url":"https://github.com/urmzd/saige","last_synced_at":"2026-04-05T05:03:30.228Z","repository":{"id":345460156,"uuid":"1185960398","full_name":"urmzd/saige","owner":"urmzd","description":"saige — Super AI Graph Ecosystem. A unified Go SDK and CLI for streaming AI agents, knowledge graphs, and RAG pipelines.","archived":false,"fork":false,"pushed_at":"2026-04-01T01:58:43.000Z","size":5670,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-01T04:27:50.346Z","etag":null,"topics":["ai-agents","ai-sdk","anthropic","cli","conversation-tree","go","golang","knowledge-graph","llm","ollama","openai","pgvector","rag","retrieval-augmented-generation","rlhf","streaming","tool-use","tui","vector-search"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/urmzd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-19T05:42:31.000Z","updated_at":"2026-04-01T01:58:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/urmzd/saige","commit_stats":null,"previous_names":["urmzd/graph-agent-dev-kit","urmzd/saige"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/urmzd/saige","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fsaige","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fsaige/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fsaige/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fsaige/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/urmzd","download_url":"https://codeload.github.com/urmzd/saige/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urmzd%2Fsaige/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","ai-sdk","anthropic","cli","conversation-tree","go","golang","knowledge-graph","llm","ollama","openai","pgvector","rag","retrieval-augmented-generation","rlhf","streaming","tool-use","tui","vector-search"],"created_at":"2026-04-02T17:53:48.884Z","updated_at":"2026-04-02T17:53:51.737Z","avatar_url":"https://github.com/urmzd.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003esaige\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n    \u003cstrong\u003eSuper Artificial Intelligence Graph Environment\u003c/strong\u003e\n    \u003cbr /\u003e\n    A unified Go SDK for streaming AI agents, knowledge graphs, and RAG pipelines.\n    \u003cbr /\u003e\u003cbr /\u003e\n    \u003ca href=\"https://pkg.go.dev/github.com/urmzd/saige\"\u003eInstall\u003c/a\u003e\n    \u0026middot;\n    \u003ca href=\"https://github.com/urmzd/saige/issues\"\u003eReport Bug\u003c/a\u003e\n    \u0026middot;\n    \u003ca href=\"https://pkg.go.dev/github.com/urmzd/saige\"\u003eGo Docs\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/urmzd/saige/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/urmzd/saige/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/urmzd/saige\"\u003e\u003cimg src=\"https://pkg.go.dev/badge/github.com/urmzd/saige.svg\" alt=\"Go Reference\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache%202.0-blue.svg\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Showcase\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"showcase/agent-basic.png\" alt=\"Basic agent demo\" width=\"600\"\u003e\n\u003c/p\u003e\n\n## Features\n\n- **Streaming-first agent loop** with 15 typed delta events and parallel tool execution\n- **Functional options** — compose agents incrementally with `AgentOption` functions\n- **Conversation tree** with branching, checkpoints, rewind, and RLHF feedback — all context-aware\n- **Sub-agent delegation** — stateless child agents as tools, deltas forwarded with attribution\n- **Human-in-the-loop markers** — gate tool execution pending approval\n- **Structured tool errors** — `IsError` flag on tool results, distinguishable from successful output\n- **Knowledge graph construction** — LLM-powered entity extraction, fuzzy dedup, temporal tracking\n- **Multi-retriever RAG** — vector + BM25 + graph retrieval fused via Reciprocal Rank Fusion\n- **Reranking** — MMR diversity and cross-encoder scoring built in\n- **4 LLM providers** (Ollama, OpenAI, Anthropic, Google) behind one `Provider` interface\n- **Provider resilience** — retry + fallback composition out of the box\n- **Structured output** — constrain LLM responses to JSON schema\n\n### Why one SDK?\n\nAgent orchestration, knowledge graphs, and RAG pipelines are deeply interconnected — RAG benefits from graph retrieval, agents need both for grounded responses, and all three share providers and embedders. **saige** unifies them under shared `Provider`, `Embedder`, and `Tool` interfaces, eliminating the wiring complexity of combining separate libraries.\n\n## Quick Start\n\n```bash\ngo get github.com/urmzd/saige\n```\n\n### CLI\n\nThe `saige` CLI provides two interaction modes plus standalone RAG/KG operations:\n\n```bash\n# Interactive multi-turn chat (Bubble Tea TUI)\nsaige chat\nsaige chat --provider anthropic --model claude-sonnet-4-6-20250514\nsaige chat --verbose  # plain-text mode for pipes/CI\n\n# Single-shot question (pipe-friendly)\nsaige ask \"What is retrieval-augmented generation?\"\necho \"Explain transformers\" | saige ask --raw\n\n# With RAG/KG tools attached to the agent\nsaige chat --rag-db \"postgres://localhost/mydb\" --kg-db \"postgres://localhost/mydb\"\nsaige ask --rag-db \"$SAIGE_RAG_DB\" \"What does the paper say about attention?\"\n\n# Standalone RAG operations (JSON output)\nsaige rag ingest --db \"$SAIGE_RAG_DB\" --file paper.pdf --mime application/pdf\nsaige rag search --db \"$SAIGE_RAG_DB\" --query \"attention mechanism\"\nsaige rag lookup --db \"$SAIGE_RAG_DB\" --uuid \u003cvariant-uuid\u003e\nsaige rag delete --db \"$SAIGE_RAG_DB\" --uuid \u003cdoc-uuid\u003e\n\n# Standalone KG operations (JSON output)\nsaige kg ingest --db \"$SAIGE_KG_DB\" --name \"meeting\" --text \"Alice presented the roadmap.\"\nsaige kg search --db \"$SAIGE_KG_DB\" --query \"Who presented?\"\nsaige kg graph  --db \"$SAIGE_KG_DB\" --limit 50\nsaige kg node   --db \"$SAIGE_KG_DB\" --id \u003centity-uuid\u003e --depth 2\n```\n\n**Provider auto-detection:** The CLI checks for `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY` in order, falling back to Ollama (no key needed). Override with `--provider` or `SAIGE_PROVIDER`.\n\n### Build an Agent\n\n```go\nimport (\n    \"github.com/urmzd/saige/agent\"\n    \"github.com/urmzd/saige/agent/types\"\n    \"github.com/urmzd/saige/agent/provider/ollama\"\n)\n\nclient := ollama.NewClient(\"http://localhost:11434\", \"qwen2.5\", \"nomic-embed-text\")\na := agent.NewAgent(agent.AgentConfig{\n    Name:         \"assistant\",\n    SystemPrompt: \"You are a helpful assistant.\",\n    Provider:     ollama.NewAdapter(client),\n    Tools:        types.NewToolRegistry(myTool),\n})\n\n// Or compose incrementally with functional options:\na := agent.NewAgent(agent.AgentConfig{\n    Name:         \"assistant\",\n    SystemPrompt: \"You are a helpful assistant.\",\n    Provider:     ollama.NewAdapter(client),\n    Tools:        types.NewToolRegistry(myTool),\n},\n    agent.WithMaxIter(20),\n    agent.WithLogger(slog.Default()),\n    agent.WithMetrics(myMetrics),\n)\n\nstream := a.Invoke(ctx, []types.Message{types.NewUserMessage(\"Hello!\")})\nfor delta := range stream.Deltas() {\n    switch d := delta.(type) {\n    case types.TextContentDelta:\n        fmt.Print(d.Content)\n    }\n}\n```\n\n### Build a Knowledge Graph\n\n```go\nimport (\n    \"github.com/urmzd/saige/knowledge\"\n    \"github.com/urmzd/saige/knowledge/types\"\n    \"github.com/urmzd/saige/postgres\"\n    \"github.com/urmzd/saige/agent/provider/ollama\"\n)\n\n// Connect to PostgreSQL (requires pgvector extension).\npool, _ := postgres.NewPool(ctx, postgres.Config{URL: \"postgres://localhost:5432/mydb\"})\npostgres.RunMigrations(ctx, pool, postgres.MigrationOptions{})\n\nclient := ollama.NewClient(\"http://localhost:11434\", \"qwen2.5\", \"nomic-embed-text\")\ngraph, _ := knowledge.NewGraph(ctx,\n    knowledge.WithPostgres(pool),\n    knowledge.WithExtractor(knowledge.NewOllamaExtractor(client)),\n    knowledge.WithEmbedder(knowledge.NewOllamaEmbedder(client)),\n)\ndefer graph.Close(ctx)\n\ngraph.IngestEpisode(ctx, \u0026types.EpisodeInput{\n    Name: \"meeting-notes\",\n    Body: \"Alice presented the Q4 roadmap. Bob raised concerns about the timeline.\",\n})\n\nresults, _ := graph.SearchFacts(ctx, \"Who presented the roadmap?\")\n```\n\n### Build a RAG Pipeline\n\n```go\nimport (\n    \"github.com/urmzd/saige/rag\"\n    \"github.com/urmzd/saige/rag/types\"\n    \"github.com/urmzd/saige/rag/pgstore\"\n    \"github.com/urmzd/saige/postgres\"\n)\n\n// Reuse the same PostgreSQL pool (or create a new one).\npool, _ := postgres.NewPool(ctx, postgres.Config{URL: \"postgres://localhost:5432/mydb\"})\npostgres.RunMigrations(ctx, pool, postgres.MigrationOptions{})\n\npipe, _ := rag.NewPipeline(\n    rag.WithStore(pgstore.NewStore(pool, nil)),\n    rag.WithContentExtractor(myExtractor),\n    rag.WithEmbedders(myEmbedderRegistry),\n    rag.WithRecursiveChunker(512, 50),\n    rag.WithBM25(nil),\n    rag.WithMMR(0.7),\n)\ndefer pipe.Close(ctx)\n\npipe.Ingest(ctx, \u0026types.RawDocument{\n    SourceURI: \"https://example.com/paper.pdf\",\n    Data:      pdfBytes,\n})\n\nresult, _ := pipe.Search(ctx, \"attention mechanism\", types.WithLimit(5))\nfmt.Println(result.AssembledContext.Prompt) // context with citations\n```\n\n---\n\n## Table of Contents\n\n- [CLI](#cli)\n- [agent — AI Agent Framework](#agent--ai-agent-framework) (providers, deltas, tools, sub-agents, markers, feedback/RLHF, compaction, tree, TUI)\n- [kg — Knowledge Graph SDK](#kg--knowledge-graph-sdk)\n- [rag — RAG Pipeline SDK](#rag--rag-pipeline-sdk)\n- [Examples](#examples)\n- [Agent Skill](#agent-skill)\n\n---\n\n## agent — AI Agent Framework\n\nStreaming-first agent loop with parallel tool execution, sub-agent delegation, human-in-the-loop markers, conversation tree persistence, and multi-provider resilience.\n\n### Provider Interface\n\nImplement one method to integrate any LLM backend:\n\n```go\ntype Provider interface {\n    ChatStream(ctx context.Context, messages []Message, tools []ToolDef) (\u003c-chan Delta, error)\n}\n```\n\n**Built-in providers:**\n\n| Provider | Package | Structured Output | Content Negotiation | Embedder |\n|----------|---------|:-:|:-:|:-:|\n| Ollama | `agent/provider/ollama` | yes | JPEG, PNG | yes |\n| OpenAI | `agent/provider/openai` | yes | JPEG, PNG, GIF, WebP, PDF | yes |\n| Anthropic | `agent/provider/anthropic` | yes | JPEG, PNG, GIF, WebP, PDF | — |\n| Google | `agent/provider/google` | yes | JPEG, PNG, GIF, WebP, PDF | yes |\n\n### Messages\n\nThree roles. Tool results are content blocks, not a separate role.\n\n| Type | Role | Content Types |\n|------|------|---------------|\n| `SystemMessage` | system | `TextContent`, `ToolResultContent`, `ConfigContent` |\n| `UserMessage` | user | `TextContent`, `ToolResultContent`, `ConfigContent`, `FileContent` |\n| `AssistantMessage` | assistant | `TextContent`, `ToolUseContent` |\n\n`ToolResultContent` carries an `IsError` field that signals whether the text represents an error or a successful result. This distinction is preserved through to the LLM — Anthropic passes it natively, Google uses an `error` key in the function response, and OpenAI/Ollama prefix the text with `[TOOL ERROR]`.\n\n### Deltas\n\n15 concrete types across five categories — LLM-side, execution-side, marker, feedback, and metadata:\n\n| Type | Category | Purpose |\n|------|----------|---------|\n| `TextStartDelta` | LLM | Text block opened |\n| `TextContentDelta` | LLM | Text chunk |\n| `TextEndDelta` | LLM | Text block closed |\n| `ToolCallStartDelta` | LLM | Tool call generation started |\n| `ToolCallArgumentDelta` | LLM | JSON argument chunk |\n| `ToolCallEndDelta` | LLM | Tool call complete |\n| `ToolExecStartDelta` | Execution | Tool began executing |\n| `ToolExecDelta` | Execution | Streaming delta from tool/sub-agent |\n| `ToolExecEndDelta` | Execution | Tool finished |\n| `MarkerDelta` | Marker | Tool gated pending approval |\n| `FeedbackDelta` | Feedback | RLHF rating recorded on a node |\n| `UsageDelta` | Metadata | Token usage + wall-clock timing |\n| `ErrorDelta` | Terminal | Provider or tool error |\n| `DoneDelta` | Terminal | Stream complete |\n\n### Tools\n\n```go\ntool := \u0026types.ToolFunc{\n    Def: types.ToolDef{\n        Name:        \"greet\",\n        Description: \"Greet a person\",\n        Parameters: types.ParameterSchema{\n            Type:     \"object\",\n            Required: []string{\"name\"},\n            Properties: map[string]types.PropertyDef{\n                \"name\": {Type: \"string\", Description: \"Person's name\"},\n            },\n        },\n    },\n    Fn: func(ctx context.Context, args map[string]any) (string, error) {\n        return fmt.Sprintf(\"Hello, %s!\", args[\"name\"]), nil\n    },\n}\n```\n\nWhen the LLM requests multiple tool calls, all tools execute **concurrently**.\n\n### Sub-Agents\n\nSub-agents are registered as tools and execute within parallel tool dispatch. Their deltas are forwarded through the parent's stream. **Sub-agents are stateless** — a fresh agent is constructed for each delegation, so conversation history is not preserved between calls. This is intentional: sub-agents are task executors, not persistent conversational partners.\n\n```go\na := agent.NewAgent(agent.AgentConfig{\n    Provider: adapter,\n    SubAgents: []agent.SubAgentDef{\n        {\n            Name:         \"researcher\",\n            Description:  \"Searches the web for information\",\n            SystemPrompt: \"You are a research assistant.\",\n            Provider:     adapter,\n            Tools:        types.NewToolRegistry(searchTool),\n        },\n    },\n})\n```\n\n### Markers (Human-in-the-Loop)\n\nGate tool execution pending consumer approval:\n\n```go\nsafeTool := types.WithMarkers(myTool,\n    types.Marker{Kind: \"human_approval\", Message: \"This modifies production data.\"},\n)\n\n// Consumer resolves:\nstream.ResolveMarker(d.ToolCallID, approved, nil)\n```\n\n### Structured Output\n\nConstrain LLM responses to a JSON schema:\n\n```go\nschema := types.SchemaFrom[MyResponse]()\na := agent.NewAgent(agent.AgentConfig{\n    Provider: adapter,\n}, agent.WithResponseSchema(schema))\n```\n\n### Provider Resilience\n\n```go\nimport (\n    \"github.com/urmzd/saige/agent/provider/retry\"\n    \"github.com/urmzd/saige/agent/provider/fallback\"\n)\n\nprovider := fallback.New(\n    retry.New(primary, retry.DefaultConfig()),\n    retry.New(backup, retry.DefaultConfig()),\n)\n```\n\n### Compaction\n\nData-driven context management:\n\n| Strategy | Behavior |\n|----------|----------|\n| `CompactNone` | No compaction |\n| `CompactSlidingWindow` | Keep system prompt + last N messages |\n| `CompactSummarize` | Summarize older messages via the provider |\n\n### Conversation Tree\n\nPersistent branching conversation graph with checkpoints, rewind, and archive. All mutation methods (`AddChild`, `Branch`, `UpdateUserMessage`, `AddFeedback`) accept a `context.Context` for cancellation, deadlines, and tracing — including WAL writes:\n\n```go\ntr := a.Tree()\ntr.AddChild(ctx, parentID, msg)\ntr.Branch(ctx, nodeID, \"experiment\", msg)\ntr.UpdateUserMessage(ctx, nodeID, newMsg)\ntr.Checkpoint(branchID, \"before-refactor\")\ntr.Rewind(checkpointID)\n```\n\n### Feedback (RLHF)\n\nAttach positive/negative ratings and comments to any node in the conversation tree. Feedback is stored as permanent leaf nodes branching off the target — never sent to the LLM, available for post-analysis and training.\n\n```go\n// Rate an assistant response.\ntip, _ := a.Tree().Tip(a.Tree().Active())\na.Feedback(ctx, tip.ID, types.RatingPositive, \"Clear and helpful\")\na.Feedback(ctx, tip.ID, types.RatingNegative, \"Too verbose\")\n\n// Collect all feedback across the tree.\nfor _, entry := range a.FeedbackSummary() {\n    fmt.Printf(\"node=%s rating=%d comment=%q\\n\",\n        entry.TargetNodeID, entry.Rating, entry.Comment)\n}\n```\n\nFeedback nodes have `NodeFeedback` state — they cannot have children added, forming dead-end branches that don't interfere with the conversation flow. During `Replay`, feedback emits `FeedbackDelta` for consumers that track ratings.\n\n### File Pipeline\n\nAutomatic URI resolution and content negotiation for multi-modal input:\n\n```go\na := agent.NewAgent(agent.AgentConfig{\n    Provider: adapter,\n},\n    agent.WithResolvers(map[string]types.Resolver{\n        \"file\": myFileResolver,\n        \"s3\":   myS3Resolver,\n    }),\n    agent.WithExtractors(map[types.MediaType]types.Extractor{\n        types.MediaPDF: myPDFExtractor,\n    }),\n)\n```\n\n### TUI\n\nThree modes for streaming agent interaction:\n\n```go\nimport \"github.com/urmzd/saige/agent/tui\"\n\n// Non-interactive (works in pipes/CI)\nresult := tui.StreamVerbose(header, stream.Deltas(), os.Stdout)\n\n// Interactive single-stream (bubbletea)\nmodel := tui.NewStreamModel(header, stream.Deltas())\ntea.NewProgram(model).Run()\n\n// Multi-turn conversation loop (reads input, resolves markers, loops until /quit)\nrunner := \u0026tui.Runner{Title: \"My Agent\"}\nrunner.Run(ctx, myAgent)\n```\n\n### Testing\n\n```go\nimport \"github.com/urmzd/saige/agent/agenttest\"\n\nprovider := \u0026agenttest.ScriptedProvider{\n    Responses: [][]types.Delta{\n        agenttest.ToolCallResponse(\"id-1\", \"greet\", map[string]any{\"name\": \"Alice\"}),\n        agenttest.TextResponse(\"Hello, Alice!\"),\n    },\n}\n```\n\n---\n\n## knowledge — Knowledge Graph SDK\n\nBuild and query knowledge graphs with LLM-powered entity extraction, fuzzy deduplication, and hybrid search.\n\n### Graph Interface\n\n```go\ntype Graph interface {\n    ApplyOntology(ctx, ontology) error\n    IngestEpisode(ctx, episode) (*IngestResult, error)\n    GetEntity(ctx, uuid) (*Entity, error)\n    SearchFacts(ctx, query, opts...) (*SearchFactsResult, error)\n    GetGraph(ctx) (*GraphData, error)\n    GetNode(ctx, uuid, depth) (*NodeDetail, error)\n    GetFactProvenance(ctx, factID) ([]Episode, error)\n    Close(ctx) error\n}\n```\n\n### Core Types\n\n| Type | Purpose |\n|------|---------|\n| `Entity` | Node — UUID, Name, Type, Summary, Embedding |\n| `Relation` | Edge — Source/Target UUID, Type, Fact, ValidAt/InvalidAt |\n| `Fact` | Relation with resolved source/target entities |\n| `Episode` | Text input with Name, Body, Source, GroupID, Metadata |\n| `Ontology` | Schema constraints — EntityTypes, RelationTypes |\n\n### Hybrid Search\n\nCombines vector similarity (HNSW) and full-text (BM25) via **Reciprocal Rank Fusion**:\n\n```go\nresults, _ := graph.SearchFacts(ctx, \"Who works at Acme?\",\n    types.WithLimit(10),\n    types.WithGroupID(\"project-alpha\"),\n)\nfor _, fact := range knowledge.FactsToStrings(results.Facts) {\n    fmt.Println(fact) // \"Alice -\u003e Acme Corp: works at\"\n}\n```\n\n### Deduplication\n\n- **Exact match** by (name, type) pair\n- **Fuzzy match** via Levenshtein distance (threshold 0.8)\n- **Relation dedup** by text similarity (threshold 0.92)\n\n### Graph Traversal\n\n```go\ndetail, _ := graph.GetNode(ctx, entityUUID, 2) // BFS to depth 2\nsub := knowledge.Subgraph(detail)                      // extract visualization data\n```\n\n### PostgreSQL Backend\n\nAutomatic schema provisioning via `postgres.RunMigrations` with pgvector HNSW index (configurable dimension, cosine distance), tsvector fulltext search, pg_trgm fuzzy matching, unique constraints, and temporal relation tracking.\n\n---\n\n## rag — RAG Pipeline SDK\n\nMulti-modal document ingestion with pluggable chunking, retrieval, reranking, and context assembly.\n\n### Data Model\n\n```\nDocument (fingerprint for dedup, metadata, source URI)\n  └── Section[] (ordered by index, optional heading)\n        └── ContentVariant[] (text, image, table, audio — each with bytes, embedding, MIME)\n```\n\nEvery `ContentVariant` has a `.Text` field that is always populated, enabling uniform search and entity extraction.\n\n### Pipeline Interface\n\n```go\ntype Pipeline interface {\n    Ingest(ctx, raw) (*IngestResult, error)\n    Search(ctx, query, opts...) (*SearchPipelineResult, error)\n    Lookup(ctx, variantUUID) (*SearchHit, error)\n    Update(ctx, documentUUID, raw) (*IngestResult, error)\n    Delete(ctx, documentUUID) error\n    Reconstruct(ctx, documentUUID) (*Document, error)\n    Close(ctx) error\n}\n```\n\n### Chunking\n\n| Strategy | Description |\n|----------|-------------|\n| Recursive | Tries separators (`\\n\\n`, `\\n`, `. `, ` `) with configurable overlap |\n| Semantic | Splits where embedding similarity drops below threshold |\n\n```go\nrag.WithRecursiveChunker(512, 50)     // maxSize, overlap\nrag.WithSemanticChunker(0.1, 100, 1000) // threshold, minSize, maxSize\n```\n\n### Retrieval\n\n| Retriever | Description |\n|-----------|-------------|\n| Vector | Embed query, cosine similarity search |\n| BM25 | In-memory inverted index with configurable K1/B |\n| Graph | Knowledge graph facts resolved to document variants via episode provenance |\n| Parent | Wraps any retriever, expands hits to full parent section context |\n\nMultiple retrievers are combined via **Reciprocal Rank Fusion**.\n\n```go\nrag.WithBM25(nil)          // default K1=1.2, B=0.75\nrag.WithParentContext()    // expand to parent sections\n```\n\n### Reranking\n\n| Reranker | Description |\n|----------|-------------|\n| MMR | Maximal Marginal Relevance — balances relevance and diversity |\n| Cross-Encoder | Pair-wise scoring via custom `Scorer` interface |\n\n```go\nrag.WithMMR(0.7)                    // lambda=0.7\nrag.WithCrossEncoder(myScorer)      // custom scorer\n```\n\n### Context Assembly\n\nBuilt-in citation support:\n\n```go\n// Default: numbered citations with source URIs\n// Compressing: LLM-based extraction of relevant sentences\nrag.WithCompression(myLLM)\n```\n\n### Query Transformation\n\n**HyDE** (Hypothetical Document Embeddings) — generates hypothetical documents via LLM for better retrieval:\n\n```go\nrag.WithHyDE(myLLM, 3) // generate 3 hypothetical docs\n```\n\n### Evaluation Metrics\n\n9 metrics across retrieval, generation, and end-to-end evaluation:\n\n| Metric | Type | Description |\n|--------|------|-------------|\n| `ContextPrecision` | Retrieval | Average Precision over relevant UUIDs |\n| `ContextRecall` | Retrieval | Fraction of relevant UUIDs in results |\n| `NDCG` | Retrieval | Normalized Discounted Cumulative Gain at rank k |\n| `MRR` | Retrieval | Reciprocal Rank of first relevant result |\n| `HitRate` | Retrieval | Binary: any relevant doc in top-k? |\n| `Faithfulness` | Generation | Claim decomposition + verification against context |\n| `AnswerRelevancy` | Generation | RAGAS-style synthetic question similarity |\n| `AnswerCorrectness` | Generation | LLM-judged comparison to ground truth |\n| `LLMJudge` | Generation | Pointwise scoring with custom rubric |\n\n```go\nimport \"github.com/urmzd/saige/rag/eval\"\n\n// Retrieval metrics (pure functions, no LLM needed).\nprecision := eval.ContextPrecision(hits, relevantUUIDs)\nrecall := eval.ContextRecall(hits, relevantUUIDs)\nndcg := eval.NDCG(hits, relevantUUIDs, 10)\nmrr := eval.MRR(hits, relevantUUIDs)\nhitRate := eval.HitRate(hits, relevantUUIDs, 10)\n\n// Generation metrics (require LLM and/or embedders).\nfaith, detail, _ := eval.Faithfulness(ctx, response, contextText, llm)\nrelevancy, _ := eval.AnswerRelevancy(ctx, query, response, llm, embedders, 3)\ncorrectness, _ := eval.AnswerCorrectness(ctx, response, groundTruth, llm)\nscore, reason, _ := eval.LLMJudge(ctx, query, response, contextText, rubric, llm)\n\n// Full evaluation pipeline with functional options.\nresults, _ := eval.Evaluate(ctx, cases, pipeline,\n    eval.WithLLM(llm),\n    eval.WithEmbedders(embedders),\n    eval.WithK(10),\n    eval.WithJudgeRubric(\"Score helpfulness, accuracy, and completeness.\"),\n)\n```\n\n### Agent Tool Bindings\n\n5 RAG tools and 2 KG tools for integrating into agent workflows:\n\n```go\nimport (\n    ragtool \"github.com/urmzd/saige/rag/tool\"\n    kgtool \"github.com/urmzd/saige/knowledge/tool\"\n)\n\nragTools := ragtool.NewTools(pipeline)\n// rag_search, rag_lookup, rag_update, rag_delete, rag_reconstruct\n\nkgTools := kgtool.NewTools(graph)\n// kg_search, kg_ingest\n```\n\n---\n\n## Examples\n\n| Example | Path | Description |\n|---------|------|-------------|\n| Basic Agent | `examples/agent/basic/` | Single tool with Ollama |\n| Sub-agents | `examples/agent/subagents/` | Parent delegating to researcher |\n| Resilient | `examples/agent/resilient/` | Retry + fallback composition |\n| Streaming | `examples/agent/streaming/` | All delta types with ANSI output |\n| Multimodal | `examples/agent/multimodal/` | File pipeline with `file://` resolver |\n| TUI | `examples/agent/tui/` | Interactive and verbose modes |\n| Runner | `examples/agent/runner/` | Multi-turn conversation loop |\n| Concurrent | `examples/agent/concurrent-subagents/` | Parallel sub-agent execution |\n| Knowledge Graph | `examples/knowledge/basic/` | Build and query a knowledge graph |\n| RAG | `examples/rag/arxiv/` | Full pipeline with arXiv papers |\n\n```bash\ngo run ./examples/agent/basic/\ngo run ./examples/knowledge/basic/\ngo run ./examples/rag/arxiv/\n```\n\n## Agent Skill\n\nThis repo's conventions are available as portable agent skills in [`skills/`](skills/).\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furmzd%2Fsaige","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Furmzd%2Fsaige","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furmzd%2Fsaige/lists"}