{"id":30769372,"url":"https://github.com/richardknop/ragserver","last_synced_at":"2025-09-04T22:09:58.072Z","repository":{"id":311066452,"uuid":"1038099668","full_name":"RichardKnop/ragserver","owner":"RichardKnop","description":"Simple Golang RAG Server ","archived":false,"fork":false,"pushed_at":"2025-08-28T23:39:05.000Z","size":445,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-29T02:51:59.956Z","etag":null,"topics":["ai","gemini","go","llm","rag","sqlite","weaviate"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RichardKnop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-14T16:07:37.000Z","updated_at":"2025-08-28T23:39:09.000Z","dependencies_parsed_at":"2025-08-21T23:59:16.526Z","dependency_job_id":null,"html_url":"https://github.com/RichardKnop/ragserver","commit_stats":null,"previous_names":["richardknop/ragserver"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/RichardKnop/ragserver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RichardKnop%2Fragserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RichardKnop%2Fragserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RichardKnop%2Fragserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RichardKnop%2Fragserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RichardKnop","download_url":"https://codeload.github.com/RichardKnop/ragserver/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RichardKnop%2Fragserver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273680236,"owners_count":25148763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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","gemini","go","llm","rag","sqlite","weaviate"],"created_at":"2025-09-04T22:09:54.120Z","updated_at":"2025-09-04T22:09:58.069Z","avatar_url":"https://github.com/RichardKnop.png","language":"Go","readme":"# RAG Server\n\n- [RAG Server](#rag-server)\n  - [Components](#components)\n    - [Extractor](#extractor)\n    - [Embedder](#embedder)\n    - [Retriever](#retriever)\n    - [GenerativeModel](#generativemodel)\n- [Examples](#examples)\n- [SQLite Database](#sqlite-database)\n- [Configuration](#configuration)\n- [API](#api)\n- [Adding Documents To Knowledge Base](#adding-documents-to-knowledge-base)\n- [Querying the LLM](#querying-the-llm)\n  - [Query Types](#query-types)\n  - [Query Request](#query-request)\n  - [Metric Query Example](#metric-query-example)\n  - [Boolean Query Example](#boolean-query-example)\n  - [Text Query Example](#text-query-example)\n  - [No Answer Example](#no-answer-example)\n- [Testing](#testing)\n\nThis project is a generic [RAG](https://cloud.google.com/use-cases/retrieval-augmented-generation?hl=en) server that can be used to answer questions using a knowledge base (corpus) refined from uploaded PDF documents. In the examples, I use ESG data about scope 1 and scope 2 emissions because that is what I have been testing the server with but it is built to be completely generic and flexible.\n\nWhen querying the server, you can specify a query type and provide files that should contain the answer. The server uses embedding model to get a vector representation of the question and retrieve documents from the knowledge base that are most similar to the question. It will then generate a structured JSON answer depending on a query type as well as list of evidences (files) and specific pages in PDFs referencing where the answer was extracted from.\n\nMain components of the RAG server are:\n\n-  **Extractor**\n-  **Embedder**\n-  **Retriever**\n-  **GenerativeModel**\n\nThese are defined as interfaces. You can implement your own components that implement these interface or use one of the provided implementations from `adapter/` folder.\n\n```go\n// Extractor extracts documents from various contents, optionally limited by relevant topics.\ntype Extractor interface {\n\tExtract(ctx context.Context, contents io.ReadSeeker, topics RelevantTopics) ([]Document, error)\n}\n\n// Embedder encodes document passages as vectors\ntype Embedder interface {\n\tName() string\n\tEmbedDocuments(ctx context.Context, documents []Document) ([]Vector, error)\n\tEmbedContent(ctx context.Context, content string) (Vector, error)\n}\n\n// Retriever that runs a question through the embeddings model and returns any encoded documents near the embedded question.\ntype Retriever interface {\n\tName() string\n\tSaveDocuments(ctx context.Context, documents []Document, vectors []Vector) error\n\tListDocumentsByFileID(ctx context.Context, id FileID) ([]Document, error)\n\tSearchDocuments(ctx context.Context, filter DocumentFilter, limit int) ([]Document, error)\n}\n\n// GenerativeModel uses generative AI to generate responses based on a query and relevant documents.\ntype GenerativeModel interface {\n\tGenerate(ctx context.Context, query Query, documents []Document) ([]Response, error)\n}\n```\n\nThen just simply create a new instance of RAG server and pass in adapters as inputs. You can hook up the REST adapter to any HTTP server, I just is the one from standard library:\n\n```go\nrs := ragserver.New(\n  extractor, \n  embebber, \n  retriever, \n  gm, \n  storeAdapter\n)\nrestAdapter := rest.New(rs)\nmux := http.NewServeMux()\nh := api.HandlerFromMux(restAdapter, mux)\nhttpServer := \u0026http.Server{\n\tReadHeaderTimeout: 10 * time.Second,\n\tIdleTimeout:       10 * time.Second,\n\tAddr:              \"localhost:8080\",\n\tHandler:           h,\n}\nhttpServer.ListenAndServe()\n```\n\n## Components\n\n### Extractor\n\nYou can either use `adapter/pdf` (which does not depend on any API, it will just try to extract sentences from PDFs locally in code) or `adapter/document` which uses Gemini document vision to extract sentences from PDFs\n\n### Embedder\n\nYou can use either the `adapter/google-genai` or `adapter/hugot` or implement your own.\n\n### Retriever\n\nYou can use either the `adapter/redis` or `adapter/weaviate` or implement your own.\n\n### GenerativeModel\n\nYou can use either the `adapter/google-genai` or `adapter/hugot` or implement your own.\n\n# Examples\n\nYou can look at `examples/` folder to see different types of adapters in use. I suggest you create your own command line entrypoint though as the `github.com/RichardKnop/ragserver/server` package used by the examples imports all of the adapters and you can slim down on dependencies by only using specific adapters you want.\n\nYou can choose between [weaviate](https://github.com/weaviate/weaviate) and [redis](https://redis.io/) as a vector database.\n\nFor a quick test drive, you can run one of the examples:\n\nRedis retriever backend, local PDF extractor, Gemini text embedding and generative model:\n\n```sh\ndocker compose -f examples/redis/docker-compose.yml up -d\n```\n\nWeaviate retriever backend, local PDF extractor, Gemini text embedding and generative model:\n\n```sh\ndocker compose -f examples/weaviate/docker-compose.yml up -d\n```\n\nRedis retriever backend, local PDF extractor, hugot open source text embedding and Gemini generative model:\n\n```sh\ndocker compose -f examples/redis-hugot/docker-compose.yml up -d\n```\n\nYou need to have `GEMINI_API_KEY` environment variable set for the examples to work.\n\n# SQLite Database\n\nThis project uses sqlite as simple embedded SQL database. It is used to store information about uploaded files including file size, hash, content type etc. UUIDs from the SQL database should be referenced in the weaviate database as a `file_id` property.\n\nWhen you ran the application, it will create a new `db.sqlite` database. You can change the database file by setting `DB_NAME` environment variable.\n\n# Configuration\n\nThis project requires a Gemini API key. Use `GEMINI_API_KEY` environment variable to set your API key.\n\nFor everything else, you can use whatever configuration method you prefer. If you use one of the examples, they rely on [viper](https://github.com/spf13/viper) to read configuration from a YAML config file.\n\nThe example `config.example.yaml` defines many different options:\n\n| Config                    | Meaning |\n| ------------------------- | --------|\n| adapter.extract.name      | Either try to extract context from PDF files locally in the code by using the `pdf` adapter or use Gemini's document vision capability by using the `document` adapter |\n| adapter.extract.model     | Only used if `adapter.extract.name` is set to `document`. Currently only supported model is `gemini-2.5-flash` |\n| adapter.embed.name        | Currently supported are `google-genai` and `hugot` |\n| adapter.embed.model       | Set to `text-embedding-004` for `google-genai` adapter and `sentence-transformers/all-MiniLM-L6-v2` for `hugot` adapter |\n| adapter.retrieve.name     | Supported adapters are `weaviate` and `redis` |\n| redis.vector_dim          | If you are using Redis, set to 768 for `text-embedding-004` or 384 for `sentence-transformers/all-MiniLM-L6-v2` |\n| adapter.generative.name   | Currently supported are `google-genai` and `hugot` |\n| adapters.generative.model | Set to `gemini-2.5-flash` for `google-genai` adapter and `onnx-community/gemma-3-270m-it-ONNX` for `hugot` adapter |\n| relevant_topics           | Limit scope only to relevant topics when extracting context from PDF files |\n\nYou can set any configuration value by using `_` as env key replacer. For example, a `http.host` can be set as environment variable `HTTP_HOST` and so on.\n\nFor local testing, I suggest switching `adapter.extract` from `document` to `pdf`. Document processing by Gemini model is a bit expensive so if you are uploading lots of files during development, using the `pdf` adapter and only doing final end to end checks with `document` adapter will be more cost efficient.\n\n# API\n\nSee the [OpenAPI spec](/api/api.yaml) for API reference.\n\n# Adding Documents To Knowledge Base\n\nUpload PDF files which will be used to extract documents:\n\n```sh\n./scripts/upload-file.sh '/Users/richardknop/Desktop/Statement on Emissions.pdf'\n./scripts/upload-file.sh '/Users/richardknop/Desktop/TCFD Report.pdf'\n```\n\nKeep track of file IDs because those are required to query the LLM for an answer.\n\nYou can list all current files:\n\n```sh\n./scripts/list-files.sh\n```\n\nYou can also list documents extracted from a specific file (currently limited to 100 documents, no pagination support):\n\n```sh\n./scripts/list-file-documents.sh bc4509b4-c156-4478-890d-8d98a44abf03\n```\n\n# Querying the LLM\n\n## Query Types\n\n| Type    | Meaning |\n| ------- | ------- |\n| metric  | Answer will be structured and provide a numeric value and a unit of measurement |\n| boolean | Answer will be a boolean value, either true (Yes) or false (No) | \n| text    | Answer will be simply be a text |\n\nMore types will be added later.\n\n## Query Request\n\nAn example query request looks like this:\n\n```json\n{\n  \"type\": \"metric\", \n  \"content\": \"What was the company's Scope 1 emissions value (in tCO2e)?\", \n  \"file_ids\": [\n    \"90d6f733-8a67-4cd9-875d-2a6ac5632fe1\",\n    \"65c77688-0f65-4e93-8069-48848e8a1e22\"\n  ]\n}\n```\n\n| Field    | Meaning |\n| -------- | ------- |\n| type     | Query type. |\n| content  | The question you want to ask the LLM. |\n| file_ids | Array of file IDs that you want to use as RAG context. |\n\nFor content, you could choose some of these example ESG related questions:\n\n1. *What was the company's location-based Scope 2 emissions value (in tCO2e) in 2022?*\n2. *What was the company's location-based Scope 2 emissions value (in tCO2e) in 2022?*\n3. *What was the company's market-based Scope 2 emissions value (in tCO2e) in 2022?*\n4. *What is the company's specified net zero target year in 2022?*\n\n## Metric Query Example\n\n```sh\n./scripts/query.sh \"$(\u003c\u003c 'EOF'\n{\n  \"type\": \"metric\", \n  \"content\": \"What was the company's Scope 1 emissions value (in tCO2e) in 2022?\", \n  \"file_ids\": [\n    \"3ce430fb-2519-4677-9e49-c8b748a529a4\",\n    \"78789e5c-b0a3-456b-8036-56dff3db934d\"\n  ]\n}\nEOF\n)\"\n```\n\nExample response:\n\n```json\n{\n  \"answers\": [\n    {\n      \"evidence\": [\n        {\n          \"file_id\": \"73ad3166-1627-4b7e-82a3-31427ad5444e\",\n          \"page\": 43,\n          \"text\": \"Total Scope 1 emissions: 86,602 (2019 baseline), 78,087 (2020), 73,319* (2021), 77,476* (2022).\"\n        },\n        {\n          \"file_id\": \"67224b92-bb64-457d-8cfc-584539292c5c\",\n          \"page\": 3,\n          \"text\": \"For Scope 1 and Scope 2 (location \u0026 market based) Emissions in 2022: Total Scope 1 was 77,476; Total Scope 2 (location) was 593,495; Total Scope 2 (market) was 4,424; Total Scope 1 and 2 (location) was 670,972; Total Scope 1 and 2 (market) was 81,901.\"\n        }\n      ],\n      \"metric\": {\n        \"unit\": \"tCO2e\",\n        \"value\": 77476\n      },\n      \"text\": \"The company's Scope 1 emissions value in 2022 was 77,476 tCO2e.\"\n    }\n  ],\n  \"question\": {\n    \"content\": \"What was the company's Scope 1 emissions value (in tCO2e) in 2022?\",\n    \"file_ids\": [\n      \"67224b92-bb64-457d-8cfc-584539292c5c\",\n      \"73ad3166-1627-4b7e-82a3-31427ad5444e\"\n    ],\n    \"type\": \"metric\"\n  }\n}\n```\n\n## Boolean Query Example\n\n```sh\n./scripts/query.sh \"$(\u003c\u003c 'EOF'\n{\n  \"type\": \"boolean\", \n  \"content\": \"Does the company have a net zero target year?\", \n  \"file_ids\": [\n    \"67224b92-bb64-457d-8cfc-584539292c5c\",\n    \"73ad3166-1627-4b7e-82a3-31427ad5444e\"\n  ]\n}\nEOF\n)\"\n```\n\nExample response:\n\n```json\n{\n  \"answers\": [\n    {\n      \"boolean\": true,\n      \"evidence\": [\n        {\n          \"file_id\": \"73ad3166-1627-4b7e-82a3-31427ad5444e\",\n          \"page\": 28,\n          \"text\": \"Since announcing its net-zero GHG emissions goal by 2050 (including financed emissions) in March 2021, the bank disclosed first interim targets in May 2022 for Oil \u0026 Gas and Power sectors, based on a 2019 baseline.\"\n        },\n        {\n          \"file_id\": \"73ad3166-1627-4b7e-82a3-31427ad5444e\",\n          \"page\": 18,\n          \"text\": \"These include: The Climate Steering Committee, chaired by the Chief Sustainability Officer, which provides strategic direction and monitors progress on climate-related goals (net-zero GHG emissions by 2050, $500 billion sustainable finance by 2030, Institute for Sustainable Finance establishment) and includes members from all principal lines of business and impacted functions.\"\n        },\n        {\n          \"file_id\": \"73ad3166-1627-4b7e-82a3-31427ad5444e\",\n          \"page\": 20,\n          \"text\": \"Core climate-related goals are: deploying $500 billion in sustainable finance by 2030 (environmental and social finance), and achieving net-zero GHG emissions by 2050 (including operational Scope 1 and 2, and financed Scope 3, Category 15 emissions).\"\n        },\n        {\n          \"file_id\": \"73ad3166-1627-4b7e-82a3-31427ad5444e\",\n          \"page\": 46,\n          \"text\": \"In May 2022, CO2eMission methodology was published, aligning financial portfolios with net-zero pathways by 2050 and setting interim targets for Oil \u0026 Gas and Power sectors.\"\n        }\n      ],\n      \"text\": \"Yes, the company has announced a net-zero GHG emissions goal by 2050.\"\n    }\n  ],\n  \"question\": {\n    \"content\": \"Does the company have a net zero target year?\",\n    \"file_ids\": [\n      \"67224b92-bb64-457d-8cfc-584539292c5c\",\n      \"73ad3166-1627-4b7e-82a3-31427ad5444e\"\n    ],\n    \"type\": \"boolean\"\n  }\n}\n```\n\n## Text Query Example\n\n```sh\n./scripts/query.sh \"$(\u003c\u003c 'EOF'\n{\n  \"type\": \"text\", \n  \"content\": \"What is the company's specified net zero target year?\", \n  \"file_ids\": [\n    \"67224b92-bb64-457d-8cfc-584539292c5c\",\n    \"73ad3166-1627-4b7e-82a3-31427ad5444e\"\n  ]\n}\nEOF\n)\"\n```\n\nExample response:\n\n```json\n{\n  \"answers\": [\n    {\n      \"evidence\": [\n        {\n          \"file_id\": \"dde50d1a-e474-43f3-843d-3f37dc81cc97\",\n          \"page\": 58,\n          \"text\": \"The company aims to continue working toward net-zero financed emissions by 2050, having already implemented carbon reduction strategies and offset its own Scope 1 and 2 (market-based) emissions.\"\n        },\n        {\n          \"file_id\": \"dde50d1a-e474-43f3-843d-3f37dc81cc97\",\n          \"page\": 31,\n          \"text\": \"They joined the \\\"Net-Zero Banking Alliance (NZBA)\\\" in October 2021, an industry-led group aiming to align bank financing with net-zero GHG emissions by mid-century.\"\n        },\n        {\n          \"file_id\": \"dde50d1a-e474-43f3-843d-3f37dc81cc97\",\n          \"page\": 46,\n          \"text\": \"In May 2022, they published CO2eMission, their methodology for aligning financial portfolios with net-zero pathways by 2050 and setting interim targets.\"\n        }\n      ],\n      \"text\": \"The company's specified net zero target year is 2050. This goal is for net-zero financed emissions and aligning financial portfolios with net-zero pathways, consistent with their membership in the Net-Zero Banking Alliance which aims for net-zero GHG emissions by mid-century.\"\n    }\n  ],\n  \"question\": {\n    \"content\": \"What is the company's specified net zero target year?\",\n    \"file_ids\": [\n      \"67224b92-bb64-457d-8cfc-584539292c5c\",\n      \"73ad3166-1627-4b7e-82a3-31427ad5444e\"\n    ],\n    \"type\": \"text\"\n  }\n}\n```\n\n## No Answer Example \n\nIf you ask a question model cannot answer from the provided context, it will simply return an empty answer\n\n```json\n{\n  \"answers\": [\n    {\n      \"evidence\": [],\n      \"metric\": {\n        \"unit\": \"\",\n        \"value\": 0\n      },\n      \"text\": \"\"\n    }\n  ],\n  \"question\": {\n    \"content\": \"What color is my hair?\",\n    \"file_ids\": [\n      \"5f354dd1-447b-4cb4-a07d-aebf4ee0a058\"\n    ],\n    \"type\": \"metric\"\n  }\n}\n```\n\n# Testing\n\nIn order to run unit and integration tests, just do:\n\n```sh\ngo test ./... -count=1\n```\n\nYou need to have docker running as some integration tests use [dockertest](https://github.com/ory/dockertest) to start containers (such as Redis).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardknop%2Fragserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichardknop%2Fragserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichardknop%2Fragserver/lists"}