chore: import upstream snapshot with attribution
Auto Bump and Release / auto-bump-and-release (push) Waiting to run
style-check / pre-commit (push) Waiting to run
unit-test / unit testing with python (push) Waiting to run
unit-test / unit testing with python 3.10 (push) Waiting to run
unit-test / unit testing with python 3.11 (push) Waiting to run
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"],
|
||||
"defaultIgnores": true,
|
||||
"rules": {
|
||||
"body-leading-blank": [1, "always"],
|
||||
"body-max-line-length": [2, "always", 100],
|
||||
"footer-leading-blank": [1, "always"],
|
||||
"footer-max-line-length": [2, "always", 10000],
|
||||
"header-max-length": [2, "always", 200],
|
||||
"subject-case": [
|
||||
2,
|
||||
"never",
|
||||
[]
|
||||
],
|
||||
"subject-empty": [2, "never"],
|
||||
"subject-full-stop": [2, "never", "."],
|
||||
"type-case": [2, "always", "lower-case"],
|
||||
"type-empty": [2, "never"],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"build",
|
||||
"chore",
|
||||
"ci",
|
||||
"docs",
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
"revert",
|
||||
"style",
|
||||
"test"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.github/
|
||||
.git/
|
||||
.mypy_cache/
|
||||
__pycache__/
|
||||
ktem_app_data/
|
||||
env/
|
||||
.pre-commit-config.yaml
|
||||
.commitlintrc
|
||||
.gitignore
|
||||
.gitattributes
|
||||
README.md
|
||||
*.zip
|
||||
*.sh
|
||||
|
||||
!/launch.sh
|
||||
@@ -0,0 +1,64 @@
|
||||
# this is an example .env file, use it to create your own .env file and place it in the root of the project
|
||||
|
||||
# settings for OpenAI
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_API_KEY=<YOUR_OPENAI_KEY>
|
||||
OPENAI_CHAT_MODEL=gpt-4o-mini
|
||||
OPENAI_EMBEDDINGS_MODEL=text-embedding-3-large
|
||||
|
||||
# settings for Azure OpenAI
|
||||
AZURE_OPENAI_ENDPOINT=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
OPENAI_API_VERSION=2024-02-15-preview
|
||||
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo
|
||||
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT=text-embedding-ada-002
|
||||
|
||||
# settings for Cohere
|
||||
COHERE_API_KEY=<COHERE_API_KEY>
|
||||
|
||||
# settings for Mistral
|
||||
# MISTRAL_API_KEY=placeholder
|
||||
|
||||
# settings for VoyageAI
|
||||
VOYAGE_API_KEY=<VOYAGE_API_KEY>
|
||||
|
||||
# settings for local models
|
||||
LOCAL_MODEL=qwen2.5:7b
|
||||
LOCAL_MODEL_EMBEDDINGS=nomic-embed-text
|
||||
|
||||
# settings for GraphRAG
|
||||
GRAPHRAG_API_KEY=<YOUR_OPENAI_KEY>
|
||||
GRAPHRAG_LLM_MODEL=gpt-4o-mini
|
||||
GRAPHRAG_EMBEDDING_MODEL=text-embedding-3-small
|
||||
|
||||
# set to true if you want to use customized GraphRAG config file
|
||||
USE_CUSTOMIZED_GRAPHRAG_SETTING=false
|
||||
|
||||
# settings for Azure DI
|
||||
AZURE_DI_ENDPOINT=
|
||||
AZURE_DI_CREDENTIAL=
|
||||
|
||||
# settings for Adobe API
|
||||
# get free credential at https://acrobatservices.adobe.com/dc-integration-creation-app-cdn/main.html?api=pdf-extract-api
|
||||
# also install pip install "pdfservices-sdk@git+https://github.com/niallcm/pdfservices-python-sdk.git@bump-and-unfreeze-requirements"
|
||||
PDF_SERVICES_CLIENT_ID=
|
||||
PDF_SERVICES_CLIENT_SECRET=
|
||||
|
||||
# settings for PDF.js
|
||||
PDFJS_VERSION_DIST="pdfjs-4.0.379-dist"
|
||||
|
||||
# settings for PaddleOCR
|
||||
PADDLE_DEVICE=gpu
|
||||
|
||||
# variable for authentication method selection
|
||||
# for authentication with google leave empty
|
||||
# for authentication with keycloak :
|
||||
# AUTHENTICATION_METHOD="KEYCLOAK"
|
||||
|
||||
AUTHENTICATION_METHOD=
|
||||
|
||||
# settings for keycloak
|
||||
KEYCLOAK_SERVER_URL=
|
||||
KEYCLOAK_CLIENT_ID=
|
||||
KEYCLOAK_REALM=
|
||||
KEYCLOAK_CLIENT_SECRET=
|
||||
@@ -0,0 +1 @@
|
||||
*.bat text eol=crlf
|
||||
@@ -0,0 +1,85 @@
|
||||
name: "Bug Report"
|
||||
description: Report something that is not working as expected
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please fill this form with as much information as possible.*
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Description"
|
||||
description: Please enter an explicit description of your issue
|
||||
placeholder: Short and explicit description of your incident...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reprod
|
||||
attributes:
|
||||
label: "Reproduction steps"
|
||||
description: Please enter an explicit description of your issue
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
render: bash
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshot
|
||||
attributes:
|
||||
label: "Screenshots"
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
value: |
|
||||

|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: "Logs"
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: bash
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: "Browsers"
|
||||
description: What browsers are you seeing the problem on ?
|
||||
multiple: true
|
||||
options:
|
||||
- Firefox
|
||||
- Chrome
|
||||
- Safari
|
||||
- Microsoft Edge
|
||||
- Opera
|
||||
- Brave
|
||||
- Other
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: "OS"
|
||||
description: What is the impacted environment ?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- MacOS
|
||||
- Linux
|
||||
- Other
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional_information
|
||||
attributes:
|
||||
label: "Additional information"
|
||||
description: Add any relevant information or context.
|
||||
placeholder:
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,49 @@
|
||||
name: "Feature Request"
|
||||
description: Brainstorm and propose new features for the project
|
||||
title: "[REQUEST] "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please fill this form with as much information as possible.*
|
||||
- type: textarea
|
||||
id: reference_issues
|
||||
attributes:
|
||||
label: "Reference Issues"
|
||||
description: Common issues
|
||||
placeholder: "#Issues IDs"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: "Summary"
|
||||
description: Provide a brief explanation of the feature
|
||||
placeholder: Describe in a few lines your feature request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: basic_example
|
||||
attributes:
|
||||
label: "Basic Example"
|
||||
description: Indicate here some basic examples of your feature.
|
||||
placeholder: A few specific words about your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: drawbacks
|
||||
attributes:
|
||||
label: "Drawbacks"
|
||||
description: What are the drawbacks/impacts of your feature request ?
|
||||
placeholder: Identify the drawbacks and impacts while being neutral on your feature request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional_information
|
||||
attributes:
|
||||
label: "Additional information"
|
||||
description: Add any additional information that you think is important for your feature request
|
||||
placeholder:
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,18 @@
|
||||
## Description
|
||||
|
||||
- Please include a summary of the changes and the related issue.
|
||||
- Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
- [ ] New features (non-breaking change).
|
||||
- [ ] Bug fix (non-breaking change).
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected).
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have performed a self-review of my code.
|
||||
- [ ] I have added thorough tests if it is a core feature.
|
||||
- [ ] There is a reference to the original bug report and related work.
|
||||
- [ ] I have commented on my code, particularly in hard-to-understand areas.
|
||||
- [ ] The feature is well documented.
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Auto Bump and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
auto-bump-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Update Application Version
|
||||
id: update-version
|
||||
uses: anothrNick/github-tag-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WITH_V: true
|
||||
DEFAULT_BUMP: patch
|
||||
MAJOR_STRING_TOKEN: "bump:major"
|
||||
MINOR_STRING_TOKEN: "bump:minor"
|
||||
PATCH_STRING_TOKEN: "bump:patch"
|
||||
- name: Create release for ${{ steps.update-version.outputs.new_tag }}
|
||||
# need to repeat this if statement because Github Action doesn't support early
|
||||
# stopping for steps
|
||||
if: ${{ steps.update-version.outputs.new_tag != steps.update-version.outputs.old_tag }}
|
||||
run: |
|
||||
echo Create release folder
|
||||
mkdir kotaemon-app
|
||||
echo ${{ steps.update-version.outputs.new_tag }} > kotaemon-app/VERSION
|
||||
cp LICENSE.txt kotaemon-app/
|
||||
cp flowsettings.py kotaemon-app/
|
||||
cp app.py kotaemon-app/
|
||||
cp .env.example kotaemon-app/.env
|
||||
cp -r scripts kotaemon-app/
|
||||
mkdir -p kotaemon-app/libs/ktem/ktem/
|
||||
cp -r libs/ktem/ktem/assets kotaemon-app/libs/ktem/ktem/
|
||||
|
||||
tree kotaemon-app
|
||||
zip -r kotaemon-app.zip kotaemon-app
|
||||
- name: Release ${{ steps.update-version.outputs.new_tag }}
|
||||
if: ${{ steps.update-version.outputs.new_tag != steps.update-version.outputs.old_tag }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: kotaemon-app.zip
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
generate_release_notes: true
|
||||
tag_name: ${{ steps.update-version.outputs.new_tag }}
|
||||
make_latest: true
|
||||
- name: Setup latest branch locally without switching current branch
|
||||
if: ${{ steps.update-version.outputs.new_tag != steps.update-version.outputs.old_tag }}
|
||||
run: git fetch origin latest:latest
|
||||
- name: Update latest branch
|
||||
if: ${{ steps.update-version.outputs.new_tag != steps.update-version.outputs.old_tag }}
|
||||
run: |
|
||||
git branch -f latest tags/${{ steps.update-version.outputs.new_tag }}
|
||||
git checkout latest
|
||||
git push -f -u origin latest
|
||||
@@ -0,0 +1,106 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and push container
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- lite
|
||||
- full
|
||||
- ollama
|
||||
# The maximum number of jobs that can run simultaneously
|
||||
max-parallel: 1
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Set repository and image name
|
||||
run: |
|
||||
echo "FULL_IMAGE_NAME=${{ env.REGISTRY }}/${IMAGE_NAME,,}" >>${GITHUB_ENV}
|
||||
env:
|
||||
IMAGE_NAME: "${{ github.repository }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
platforms: arm64,arm
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.FULL_IMAGE_NAME }}
|
||||
tags: |
|
||||
# branch
|
||||
type=ref,event=branch,suffix=-${{ matrix.target }}
|
||||
# semver with suffix for lite/full targets
|
||||
type=semver,pattern={{version}},suffix=-${{ matrix.target }}
|
||||
# latest tag with suffix for lite/full targets
|
||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'pre') }},suffix=-${{ matrix.target }}
|
||||
flavor: |
|
||||
# This is disabled here so we can use the raw form above
|
||||
latest=false
|
||||
# Suffix is not used here since there's no way to disable it above
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64, linux/arm64
|
||||
tags: |
|
||||
${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
target: ${{ matrix.target }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
@@ -0,0 +1,77 @@
|
||||
name: "Lint PR"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
id: lint_pr_title
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
# When the previous steps fails, the workflow would stop. By adding this
|
||||
# condition you can continue the execution with the populated error message.
|
||||
if: always() && (steps.lint_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
Hey there and thank you for opening this pull request! 👋🏼
|
||||
|
||||
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
|
||||
Details:
|
||||
```
|
||||
${{ steps.lint_pr_title.outputs.error_message }}
|
||||
```
|
||||
|
||||
# Delete a previous comment when the issue has been resolved
|
||||
- if: ${{ steps.lint_pr_title.outputs.error_message == null }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
|
||||
commitlint:
|
||||
if: false # Disable this job for now
|
||||
name: Validate commit messages
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: wagoid/commitlint-github-action@v6
|
||||
id: commitlint
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
configFile: ./.commitlintrc
|
||||
- uses: buildingcash/json-to-markdown-table-action@v1
|
||||
if: always() && (steps.commitlint.outcome != 'success')
|
||||
id: table
|
||||
with:
|
||||
json: ${{ steps.commitlint.outputs.results }}
|
||||
- uses: marocchino/sticky-pull-request-comment@v2
|
||||
if: always() && (steps.commitlint.outcome != 'success')
|
||||
with:
|
||||
header: commitlint-error
|
||||
message: |
|
||||
**All commits** in this PR need to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and [.commitlintrc](${{ github.server_url }}/${{ github.repository }}/blob/${{ github.head_ref || github.ref_name }}/.commitlintrc).
|
||||
Details:
|
||||
${{ steps.table.outputs.table }}
|
||||
|
||||
- if: ${{ steps.commitlint.outcome == 'success' }}
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
with:
|
||||
header: commitlint-error
|
||||
delete: true
|
||||
@@ -0,0 +1,20 @@
|
||||
name: style-check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
push:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: run pre-commit
|
||||
uses: pre-commit/action@v3.0.0
|
||||
@@ -0,0 +1,114 @@
|
||||
name: unit-test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
THEFLOW_TEMP_PATH: ./tmp
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
# if: false # temporary disable this job due to legacy interface
|
||||
#TODO: enable this job after the new interface is ready
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11"]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
shell: bash
|
||||
ACTIVATE_ENV: ". env/bin/activate"
|
||||
GITHUB_OUTPUT: "$GITHUB_OUTPUT"
|
||||
# - os: windows-latest
|
||||
# shell: pwsh
|
||||
# ACTIVATE_ENV: env/Scripts/activate.ps1
|
||||
# GITHUB_OUTPUT: "$env:GITHUB_OUTPUT"
|
||||
|
||||
name: unit testing with python ${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Get Head Commit Message
|
||||
id: get-head-commit-message
|
||||
run: echo "message=$(git show -s --format=%s)" | tee -a ${{ matrix.GITHUB_OUTPUT }}
|
||||
|
||||
- name: Check ignore caching
|
||||
id: check-ignore-cache
|
||||
run: |
|
||||
ignore_cache=${{ contains(steps.get-head-commit-message.outputs.message, '[ignore cache]') }}
|
||||
echo "check=$ignore_cache" | tee -a ${{ matrix.GITHUB_OUTPUT }}
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }} on ${{ runner.os }}
|
||||
uses: actions/setup-python@v4
|
||||
id: setup_python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: x64
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
enable-cache: true
|
||||
|
||||
- name: Get cache key
|
||||
id: get-cache-key
|
||||
run: |
|
||||
pip install "setuptools-git-versioning>=2.0,<3"
|
||||
package_version=$(setuptools-git-versioning)
|
||||
cache_key="${{ runner.os }}-py${{ matrix.python-version }}-v${package_version}"
|
||||
echo "key=$cache_key" | tee -a ${{ matrix.GITHUB_OUTPUT }}
|
||||
|
||||
- name: Try to restore dependencies from ${{ steps.get-cache-key.outputs.key }}
|
||||
id: restore-dependencies
|
||||
if: steps.check-ignore-cache.outputs.check != 'true'
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ steps.get-cache-key.outputs.key }}
|
||||
# could using cache of previous ver to reuse unchanged packages
|
||||
restore-keys: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||
|
||||
- name: Check cache hit
|
||||
id: check-cache-hit
|
||||
run: |
|
||||
echo "cache-hit=${{ steps.restore-dependencies.outputs.cache-hit }}"
|
||||
echo "cache-matched-key=${{ steps.restore-dependencies.outputs.cache-matched-key }}"
|
||||
cache_hit=${{ steps.restore-dependencies.outputs.cache-primary-key == steps.restore-dependencies.outputs.cache-matched-key }}
|
||||
echo "check=$cache_hit" | tee -a ${{ matrix.GITHUB_OUTPUT }}
|
||||
|
||||
- name: Install additional dependencies (if any)
|
||||
run: |
|
||||
uv sync --frozen --no-cache
|
||||
|
||||
- name: New dependencies cache for key ${{ steps.restore-dependencies.outputs.cache-primary-key }}
|
||||
if: |
|
||||
steps.check-ignore-cache.outputs.check != 'true' &&
|
||||
steps.check-cache-hit.outputs.check != 'true'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: ${{ env.pythonLocation }}
|
||||
key: ${{ steps.restore-dependencies.outputs.cache-primary-key }}
|
||||
|
||||
- name: Install OS-based packages
|
||||
run: |
|
||||
sudo apt update -qqy
|
||||
sudo apt install -y poppler-utils libpoppler-dev tesseract-ocr
|
||||
|
||||
- name: Test kotaemon with pytest
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
uv pip show pytest
|
||||
cd libs/kotaemon
|
||||
pytest
|
||||
@@ -0,0 +1,479 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/python,linux,macos,windows,vim,emacs,visualstudiocode,pycharm
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux,macos,windows,vim,emacs,visualstudiocode,pycharm
|
||||
|
||||
activate*
|
||||
activate/*
|
||||
kotaemon-env*
|
||||
.env
|
||||
|
||||
### Emacs ###
|
||||
# -*- mode: gitignore; -*-
|
||||
*~
|
||||
\#*\#
|
||||
/.emacs.desktop
|
||||
/.emacs.desktop.lock
|
||||
*.elc
|
||||
auto-save-list
|
||||
tramp
|
||||
.\#*
|
||||
|
||||
# Org-mode
|
||||
.org-id-locations
|
||||
*_archive
|
||||
|
||||
# flymake-mode
|
||||
*_flymake.*
|
||||
|
||||
# eshell files
|
||||
/eshell/history
|
||||
/eshell/lastdir
|
||||
|
||||
# elpa packages
|
||||
/elpa/
|
||||
|
||||
# reftex files
|
||||
*.rel
|
||||
|
||||
# AUCTeX auto folder
|
||||
/auto/
|
||||
|
||||
# cask packages
|
||||
.cask/
|
||||
dist/
|
||||
|
||||
# Flycheck
|
||||
flycheck_*.el
|
||||
|
||||
# server auth directory
|
||||
/server/
|
||||
|
||||
# projectiles files
|
||||
.projectile
|
||||
|
||||
# directory configuration
|
||||
.dir-locals.el
|
||||
|
||||
# network security
|
||||
/network-security.data
|
||||
|
||||
### Linux ###
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### macOS Patch ###
|
||||
# iCloud generated files
|
||||
*.icloud
|
||||
|
||||
### PyCharm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### PyCharm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Python ###
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
!*.svg # comment out if you don't need vector files
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-rt-v][a-z]
|
||||
[._]ss[a-gi-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
Sessionx.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# PDF files
|
||||
*.pdf
|
||||
!libs/kotaemon/tests/resources/*.pdf
|
||||
|
||||
.theflow/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/python,linux,macos,windows,vim,emacs,visualstudiocode,pycharm
|
||||
*.py[coid]
|
||||
|
||||
logs/
|
||||
.gitsecret/keys/random_seed
|
||||
!*.secret
|
||||
.envrc
|
||||
.env
|
||||
|
||||
S.gpg-agent*
|
||||
.vscode/settings.json
|
||||
examples/example1/assets
|
||||
storage/*
|
||||
|
||||
# Conda and env storages
|
||||
*install_dir/
|
||||
doc_env/
|
||||
|
||||
# application data
|
||||
ktem_app_data/
|
||||
gradio_tmp/
|
||||
@@ -0,0 +1,71 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
args: ["--unsafe"]
|
||||
- id: check-toml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: mixed-line-ending
|
||||
- id: detect-aws-credentials
|
||||
args: ["--allow-missing-credentials"]
|
||||
- id: detect-private-key
|
||||
- id: check-added-large-files
|
||||
args: ["--maxkb=750"]
|
||||
- id: debug-statements
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
language_version: python3.10
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 7.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ["--max-line-length", "88", "--extend-ignore", "E203"]
|
||||
- repo: https://github.com/myint/autoflake
|
||||
rev: v1.4
|
||||
hooks:
|
||||
- id: autoflake
|
||||
additional_dependencies: [setuptools]
|
||||
args:
|
||||
[
|
||||
"--in-place",
|
||||
"--remove-unused-variables",
|
||||
"--remove-all-unused-imports",
|
||||
"--ignore-init-module-imports",
|
||||
"--exclude=tests/*",
|
||||
]
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.7.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [markdown, yaml]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: "v1.7.1"
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
[
|
||||
types-PyYAML==6.0.12.11,
|
||||
"types-requests",
|
||||
"sqlmodel",
|
||||
"types-Markdown",
|
||||
"types-cachetools",
|
||||
types-tzlocal,
|
||||
]
|
||||
args: ["--check-untyped-defs", "--ignore-missing-imports"]
|
||||
exclude: "^templates/"
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.4
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
@@ -0,0 +1 @@
|
||||
3.10
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -0,0 +1,115 @@
|
||||
# Contributing to Kotaemon
|
||||
|
||||
Welcome 👋 to the Kotaemon project! We're thrilled that you're interested in contributing. Whether you're fixing bugs, adding new features, or improving documentation, your efforts are highly appreciated. This guide aims to help you get started with contributing to Kotaemon.
|
||||
|
||||
<a href="https://github.com/Cinnamon/kotaemon/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cinnamon/kotaemon" />
|
||||
</a>
|
||||
|
||||
### Table of Contents
|
||||
|
||||
1. [📖 Code of Conduct](#code-of-conduct)
|
||||
2. [🔁 Contributing via Pull Requests](#contributing-via-pull-requests)
|
||||
3. [📥 Opening an Issue](#-opening-an-issue)
|
||||
4. [📝 Commit Messages](#-commit-messages)
|
||||
5. [🧾 License](#-license)
|
||||
|
||||
## 📖 Code of Conduct
|
||||
|
||||
Please review our [code of conduct](./CODE_OF_CONDUCT.md), which is in effect at all times. We expect everyone who contributes to this project to honor it.
|
||||
|
||||
## 🔁 Contributing via Pull Requests
|
||||
|
||||
1. [**Fork the repository**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo): Click on the [Fork](https://github.com/Cinnamon/kotaemon/fork) button on the repository's page to create a copy of Kotaemon under your GitHub account.
|
||||
|
||||
2. [**Clone your code**](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository): Clone your forked repository to your local machine.
|
||||
|
||||
3. [**Create new branch**](https://docs.github.com/en/desktop/making-changes-in-a-branch/managing-branches-in-github-desktop): Create a new branch in your forked repo with a descriptive name that reflects your changes.
|
||||
|
||||
```sh
|
||||
git checkout -b descriptive-name-for-your-changes
|
||||
```
|
||||
|
||||
4. **Setup the development environment**: If you are working on the code, make sure to install the necessary dependencies for development
|
||||
|
||||
```sh
|
||||
pip install -e "libs/kotaemon[dev]"
|
||||
```
|
||||
|
||||
5. **Make your changes**: Ensure your code follows the project's coding style and passes all test cases.
|
||||
|
||||
- Check the coding style
|
||||
|
||||
```sh
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
- Run the tests
|
||||
|
||||
```sh
|
||||
pytest libs/kotaemon/tests/
|
||||
```
|
||||
|
||||
6. [**Commit your changes**](https://docs.github.com/en/desktop/making-changes-in-a-branch/committing-and-reviewing-changes-to-your-project-in-github-desktop): Once you are done with your changes, add and commit them with clear messages.
|
||||
|
||||
```sh
|
||||
git add your_changes.py
|
||||
git commit -m "clear message described your changes."
|
||||
git push -u origin descriptive-name-for-your-changes
|
||||
```
|
||||
|
||||
7. [**Create a pull request**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request): When you are satisfied with your changes, [submit a pull request](https://github.com/Cinnamon/kotaemon/compare) from your forked repository to Kotaemon repository. In the pull request, provide a clear description of your changes and any related issues. For the title of the pull request, please refer to our [commit messages convention](#-commit-messages).
|
||||
|
||||
8. **Wait for reviews**: Wait for the maintainers to review your pull request. If everything is okay, your changes will be merged into the Kotaemon project.
|
||||
|
||||
### GitHub Actions CI Tests
|
||||
|
||||
All pull requests must pass the [GitHub Actions Continuous Integration (CI)](https://docs.github.com/en/actions/about-github-actions/about-continuous-integration-with-github-actions) tests before they can be merged. These tests include coding-style checks, PR title validation, unit tests, etc. to ensure that your changes meet the project's quality standards. Please review and fix any CI failures that arise.
|
||||
|
||||
## 📥 Opening an Issue
|
||||
|
||||
Before [creating an issues](https://github.com/Cinnamon/kotaemon/issues/new/choose), search through existing issues to ensure you are not opening a duplicate. If you are reporting a bug or issue, please provide a reproducible example to help us quickly identify the problem.
|
||||
|
||||
## 📝 Commit Messages
|
||||
|
||||
### Overview
|
||||
|
||||
We use [Angular convention](https://www.conventionalcommits.org/en/) for commit messages to maintain consistency and clarity in our project history. Please take a moment to familiarize yourself with this convention before making your first commit.
|
||||
|
||||
_For the sake of simplicity, we use [squashing merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits) with pull requests. Therefore, if you contribute via a pull request, just make sure your PR's title, instead of the whole commits, follows this convention._
|
||||
|
||||
Commit format:
|
||||
|
||||
```sh
|
||||
<gitmoji> <type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
docs(api): update api doc
|
||||
```
|
||||
|
||||
### Commit types
|
||||
|
||||
| Types | Description |
|
||||
| :--------- | :------------------------------------------------------------ |
|
||||
| `feat` | New features |
|
||||
| `fix` | Bug fix |
|
||||
| `docs` | Documentation only changes |
|
||||
| `build` | Changes that affect the build system or external dependencies |
|
||||
| `chore` | Something that doesn’t fit the other types |
|
||||
| `ci` | Changes to our CI configuration files and scripts |
|
||||
| `perf` | Improve performance |
|
||||
| `refactor` | Refactor code |
|
||||
| `revert` | Revert a previous commit |
|
||||
| `style` | Improve structure/format of the code |
|
||||
| `test` | Add, update or pass tests |
|
||||
|
||||
## 🧾 License
|
||||
|
||||
All contributions will be licensed under the project's license: [Apache License 2.0](https://github.com/Cinnamon/kotaemon/blob/main/LICENSE.txt).
|
||||
@@ -0,0 +1,124 @@
|
||||
# Lite version
|
||||
FROM python:3.11-slim AS lite
|
||||
|
||||
# Common dependencies
|
||||
RUN apt-get update -qqy && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ssh \
|
||||
git \
|
||||
gcc \
|
||||
g++ \
|
||||
poppler-utils \
|
||||
libpoppler-dev \
|
||||
unzip \
|
||||
curl \
|
||||
cargo \
|
||||
&& \
|
||||
apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Setup args
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETARCH
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONIOENCODING=UTF-8
|
||||
ENV TARGETARCH=${TARGETARCH}
|
||||
|
||||
# Create working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Download pdfjs
|
||||
COPY scripts/download_pdfjs.sh /app/scripts/download_pdfjs.sh
|
||||
RUN chmod +x /app/scripts/download_pdfjs.sh
|
||||
ENV PDFJS_PREBUILT_DIR="/app/libs/ktem/ktem/assets/prebuilt/pdfjs-dist"
|
||||
RUN bash scripts/download_pdfjs.sh $PDFJS_PREBUILT_DIR
|
||||
|
||||
# Install uv dependencies
|
||||
RUN pip install --no-cache-dir "uv"
|
||||
|
||||
# Copy contents
|
||||
COPY . /app
|
||||
COPY launch.sh /app/launch.sh
|
||||
COPY .env.example /app/.env
|
||||
|
||||
# Install pip packages
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-cache \
|
||||
&& uv pip install --python .venv "pdfservices-sdk@git+https://github.com/niallcm/pdfservices-python-sdk.git@bump-and-unfreeze-requirements"
|
||||
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
if [ "$TARGETARCH" = "amd64" ]; then uv pip install --python .venv "graphrag<=0.3.6" future; fi
|
||||
|
||||
ENTRYPOINT ["sh", "/app/launch.sh"]
|
||||
|
||||
# Full version
|
||||
FROM lite AS full
|
||||
|
||||
# Additional dependencies for full version
|
||||
RUN apt-get update -qqy && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-jpn \
|
||||
libsm6 \
|
||||
libxext6 \
|
||||
libreoffice \
|
||||
ffmpeg \
|
||||
libmagic-dev \
|
||||
&& \
|
||||
apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install torch and torchvision for unstructured
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python .venv torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
|
||||
# Install additional pip packages (adv + unstructured)
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python .venv "libs/kotaemon[adv]" \
|
||||
&& uv pip install --python .venv unstructured[all-docs]
|
||||
|
||||
# Download NLTK data from LlamaIndex
|
||||
RUN /app/.venv/bin/python -c "from llama_index.core.readers.base import BaseReader"
|
||||
|
||||
# Optional reader: docling
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python .venv "libs/kotaemon[docling]"
|
||||
|
||||
# Optional RAG: lightRAG
|
||||
ENV USE_LIGHTRAG=true
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python .venv "libs/kotaemon[lightrag]"
|
||||
|
||||
ENTRYPOINT ["sh", "/app/launch.sh"]
|
||||
|
||||
# PaddleOCR version (GPU-only)
|
||||
FROM full AS paddle
|
||||
|
||||
ARG CUDA_VERSION=130
|
||||
|
||||
# Install paddlepaddle and paddleocr
|
||||
RUN --mount=type=ssh \
|
||||
--mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python .venv paddlepaddle-gpu==3.3.0 \
|
||||
-i "https://www.paddlepaddle.org.cn/packages/stable/cu${CUDA_VERSION}/" \
|
||||
&& uv pip install --python .venv "libs/kotaemon[paddleocr]"
|
||||
|
||||
ENTRYPOINT ["sh", "/app/launch.sh"]
|
||||
|
||||
# Ollama-bundled version
|
||||
FROM full AS ollama
|
||||
|
||||
# Install ollama
|
||||
RUN curl -fsSL https://ollama.com/install.sh | sh
|
||||
|
||||
# RUN nohup bash -c "ollama serve &" && sleep 4 && ollama pull qwen2.5:7b
|
||||
RUN nohup bash -c "ollama serve &" && sleep 4 && ollama pull nomic-embed-text
|
||||
|
||||
ENTRYPOINT ["sh", "/app/launch.sh"]
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,411 @@
|
||||
<div align="center">
|
||||
|
||||
# kotaemon
|
||||
|
||||
An open-source clean & customizable RAG UI for chatting with your documents. Built with both end users and
|
||||
developers in mind.
|
||||
|
||||

|
||||
|
||||
<a href="https://trendshift.io/repositories/11607" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11607" alt="Cinnamon%2Fkotaemon | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[Live Demo #1](https://huggingface.co/spaces/cin-model/kotaemon) |
|
||||
[Live Demo #2](https://huggingface.co/spaces/cin-model/kotaemon-demo) |
|
||||
[Online Install](https://cinnamon.github.io/kotaemon/online_install/) |
|
||||
[Colab Notebook (Local RAG)](https://colab.research.google.com/drive/1eTfieec_UOowNizTJA1NjawBJH9y_1nn)
|
||||
|
||||
[User Guide](https://cinnamon.github.io/kotaemon/) |
|
||||
[Developer Guide](https://cinnamon.github.io/kotaemon/development/) |
|
||||
[Feedback](https://github.com/Cinnamon/kotaemon/issues) |
|
||||
[Contact](mailto:kotaemon.support@cinnamon.is)
|
||||
|
||||
[](https://www.python.org/downloads/release/python-31013/)
|
||||
[](https://github.com/psf/black)
|
||||
<a href="https://github.com/Cinnamon/kotaemon/pkgs/container/kotaemon" target="_blank">
|
||||
<img src="https://img.shields.io/badge/docker_pull-kotaemon:latest-brightgreen" alt="docker pull ghcr.io/cinnamon/kotaemon:latest"></a>
|
||||

|
||||
<a href='https://huggingface.co/spaces/cin-model/kotaemon-demo'><img src='https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue'></a>
|
||||
<a href="https://hellogithub.com/en/repository/d3141471a0244d5798bc654982b263eb" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=d3141471a0244d5798bc654982b263eb&claim_uid=RLiD9UZ1rEHNaMf&theme=small" alt="Featured|HelloGitHub" /></a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- start-intro -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This project serves as a functional RAG UI for both end users who want to do QA on their
|
||||
documents and developers who want to build their own RAG pipeline.
|
||||
<br>
|
||||
|
||||
```yml
|
||||
+----------------------------------------------------------------------------+
|
||||
| End users: Those who use apps built with `kotaemon`. |
|
||||
| (You use an app like the one in the demo above) |
|
||||
| +----------------------------------------------------------------+ |
|
||||
| | Developers: Those who built with `kotaemon`. | |
|
||||
| | (You have `import kotaemon` somewhere in your project) | |
|
||||
| | +----------------------------------------------------+ | |
|
||||
| | | Contributors: Those who make `kotaemon` better. | | |
|
||||
| | | (You make PR to this repo) | | |
|
||||
| | +----------------------------------------------------+ | |
|
||||
| +----------------------------------------------------------------+ |
|
||||
+----------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### For end users
|
||||
|
||||
- **Clean & Minimalistic UI**: A user-friendly interface for RAG-based QA.
|
||||
- **Support for Various LLMs**: Compatible with LLM API providers (OpenAI, AzureOpenAI, Cohere, etc.) and local LLMs (via `ollama` and `llama-cpp-python`).
|
||||
- **Easy Installation**: Simple scripts to get you started quickly.
|
||||
|
||||
### For developers
|
||||
|
||||
- **Framework for RAG Pipelines**: Tools to build your own RAG-based document QA pipeline.
|
||||
- **Customizable UI**: See your RAG pipeline in action with the provided UI, built with <a href='https://github.com/gradio-app/gradio'>Gradio <img src='https://img.shields.io/github/stars/gradio-app/gradio'></a>.
|
||||
- **Gradio Theme**: If you use Gradio for development, check out our theme here: [kotaemon-gradio-theme](https://github.com/lone17/kotaemon-gradio-theme).
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Host your own document QA (RAG) web-UI**: Support multi-user login, organize your files in private/public collections, collaborate and share your favorite chat with others.
|
||||
|
||||
- **Organize your LLM & Embedding models**: Support both local LLMs & popular API providers (OpenAI, Azure, Ollama, Groq).
|
||||
|
||||
- **Hybrid RAG pipeline**: Sane default RAG pipeline with hybrid (full-text & vector) retriever and re-ranking to ensure best retrieval quality.
|
||||
|
||||
- **Multi-modal QA support**: Perform Question Answering on multiple documents with figures and tables support. Support multi-modal document parsing (selectable options on UI).
|
||||
|
||||
- **Advanced citations with document preview**: By default the system will provide detailed citations to ensure the correctness of LLM answers. View your citations (incl. relevant score) directly in the _in-browser PDF viewer_ with highlights. Warning when retrieval pipeline return low relevant articles.
|
||||
|
||||
- **Support complex reasoning methods**: Use question decomposition to answer your complex/multi-hop question. Support agent-based reasoning with `ReAct`, `ReWOO` and other agents.
|
||||
|
||||
- **Configurable settings UI**: You can adjust most important aspects of retrieval & generation process on the UI (incl. prompts).
|
||||
|
||||
- **Extensible**: Being built on Gradio, you are free to customize or add any UI elements as you like. Also, we aim to support multiple strategies for document indexing & retrieval. `GraphRAG` indexing pipeline is provided as an example.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
> If you are not a developer and just want to use the app, please check out our easy-to-follow [User Guide](https://cinnamon.github.io/kotaemon/). Download the `.zip` file from the [latest release](https://github.com/Cinnamon/kotaemon/releases/latest) to get all the newest features and bug fixes.
|
||||
|
||||
### System requirements
|
||||
|
||||
1. [Python](https://www.python.org/downloads/) >= 3.10
|
||||
2. [Docker](https://www.docker.com/): optional, if you [install with Docker](#with-docker-recommended)
|
||||
3. [Unstructured](https://docs.unstructured.io/open-source/installation/full-installation#full-installation) if you want to process files other than `.pdf`, `.html`, `.mhtml`, and `.xlsx` documents. Installation steps differ depending on your operating system. Please visit the link and follow the specific instructions provided there.
|
||||
|
||||
### With Docker (recommended)
|
||||
|
||||
1. We support both `lite` & `full` version of Docker images. With `full` version, the extra packages of `unstructured` will be installed, which can support additional file types (`.doc`, `.docx`, ...) but the cost is larger docker image size. For most users, the `lite` image should work well in most cases.
|
||||
|
||||
- To use the `full` version.
|
||||
|
||||
```shell
|
||||
docker run \
|
||||
-e GRADIO_SERVER_NAME=0.0.0.0 \
|
||||
-e GRADIO_SERVER_PORT=7860 \
|
||||
-v ./ktem_app_data:/app/ktem_app_data \
|
||||
-p 7860:7860 -it --rm \
|
||||
ghcr.io/cinnamon/kotaemon:main-full
|
||||
```
|
||||
|
||||
- To use the `full` version with bundled **Ollama** for _local / private RAG_.
|
||||
|
||||
```shell
|
||||
# change image name to
|
||||
docker run <...> ghcr.io/cinnamon/kotaemon:main-ollama
|
||||
```
|
||||
|
||||
- To use the `lite` version.
|
||||
|
||||
```shell
|
||||
# change image name to
|
||||
docker run <...> ghcr.io/cinnamon/kotaemon:main-lite
|
||||
```
|
||||
|
||||
2. We currently support and test two platforms: `linux/amd64` and `linux/arm64` (for newer Mac). You can specify the platform by passing `--platform` in the `docker run` command. For example:
|
||||
|
||||
```shell
|
||||
# To run docker with platform linux/arm64
|
||||
docker run \
|
||||
-e GRADIO_SERVER_NAME=0.0.0.0 \
|
||||
-e GRADIO_SERVER_PORT=7860 \
|
||||
-v ./ktem_app_data:/app/ktem_app_data \
|
||||
-p 7860:7860 -it --rm \
|
||||
--platform linux/arm64 \
|
||||
ghcr.io/cinnamon/kotaemon:main-lite
|
||||
```
|
||||
|
||||
3. Once everything is set up correctly, you can go to `http://localhost:7860/` to access the WebUI.
|
||||
|
||||
4. We use [GHCR](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) to store docker images, all images can be found [here.](https://github.com/Cinnamon/kotaemon/pkgs/container/kotaemon)
|
||||
|
||||
### Without Docker
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/Cinnamon/kotaemon
|
||||
cd kotaemon
|
||||
```
|
||||
|
||||
2. Setup the environment:
|
||||
|
||||
- **Option 1: Using [uv](https://docs.astral.sh/uv/getting-started/installation/) (recommended)**
|
||||
|
||||
```shell
|
||||
uv sync --python 3.10
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
- **Option 2: Using conda**
|
||||
|
||||
```shell
|
||||
conda create -n kotaemon python=3.10
|
||||
conda activate kotaemon
|
||||
|
||||
pip install -e "libs/kotaemon[all]"
|
||||
pip install -e "libs/ktem"
|
||||
```
|
||||
|
||||
3. Create a `.env` file in the root of this project. Use `.env.example` as a template.
|
||||
|
||||
The `.env` file is there to serve use cases where users want to pre-config the models before starting up the app (e.g. deploy the app on HF hub). The file will only be used to populate the db once upon the first run, it will no longer be used in consequent runs.
|
||||
|
||||
4. (Optional) To enable in-browser `PDF_JS` viewer, download [PDF_JS_DIST](https://github.com/mozilla/pdf.js/releases/download/v4.0.379/pdfjs-4.0.379-dist.zip) then extract it to `libs/ktem/ktem/assets/prebuilt`.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Cinnamon/kotaemon/main/docs/images/pdf-viewer-setup.png" alt="pdf-setup" width="300">
|
||||
|
||||
5. Start the web server:
|
||||
|
||||
```shell
|
||||
python app.py
|
||||
```
|
||||
|
||||
- The app will be automatically launched in your browser.
|
||||
- Default username and password are both `admin`. You can set up additional users directly through the UI.
|
||||
|
||||

|
||||
|
||||
6. Check the `Resources` tab and `LLMs and Embeddings` and ensure that your `api_key` value is set correctly from your `.env` file. If it is not set, you can set it there.
|
||||
|
||||
### Setup GraphRAG
|
||||
|
||||
> [!NOTE]
|
||||
> Official MS GraphRAG indexing only works with OpenAI or Ollama API.
|
||||
> We recommend most users to use NanoGraphRAG implementation for straightforward integration with Kotaemon.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Setup Nano GRAPHRAG</summary>
|
||||
|
||||
- Install nano-GraphRAG: `pip install nano-graphrag`
|
||||
- `nano-graphrag` install might introduce version conflicts, see [this issue](https://github.com/Cinnamon/kotaemon/issues/440)
|
||||
- To quickly fix: `pip uninstall hnswlib chroma-hnswlib && pip install chroma-hnswlib`
|
||||
- Launch Kotaemon with `USE_NANO_GRAPHRAG=true` environment variable.
|
||||
- Set your default LLM & Embedding models in Resources setting and it will be recognized automatically from NanoGraphRAG.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Setup LIGHTRAG</summary>
|
||||
|
||||
- Install LightRAG: `pip install git+https://github.com/HKUDS/LightRAG.git`
|
||||
- `LightRAG` install might introduce version conflicts, see [this issue](https://github.com/Cinnamon/kotaemon/issues/440)
|
||||
- To quickly fix: `pip uninstall hnswlib chroma-hnswlib && pip install chroma-hnswlib`
|
||||
- Launch Kotaemon with `USE_LIGHTRAG=true` environment variable.
|
||||
- Set your default LLM & Embedding models in Resources setting and it will be recognized automatically from LightRAG.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Setup MS GRAPHRAG</summary>
|
||||
|
||||
- **Non-Docker Installation**: If you are not using Docker, install GraphRAG with the following command:
|
||||
|
||||
```shell
|
||||
pip install "graphrag<=0.3.6" future
|
||||
```
|
||||
|
||||
- **Setting Up API KEY**: To use the GraphRAG retriever feature, ensure you set the `GRAPHRAG_API_KEY` environment variable. You can do this directly in your environment or by adding it to a `.env` file.
|
||||
- **Using Local Models and Custom Settings**: If you want to use GraphRAG with local models (like `Ollama`) or customize the default LLM and other configurations, set the `USE_CUSTOMIZED_GRAPHRAG_SETTING` environment variable to true. Then, adjust your settings in the `settings.yaml.example` file.
|
||||
|
||||
</details>
|
||||
|
||||
### Setup Local Models (for local/private RAG)
|
||||
|
||||
See [Local model setup](docs/local_model.md).
|
||||
|
||||
### Setup multimodal document parsing (OCR, table parsing, figure extraction)
|
||||
|
||||
These options are available:
|
||||
|
||||
- [Azure Document Intelligence (API)](https://azure.microsoft.com/en-us/products/ai-services/ai-document-intelligence)
|
||||
- [Adobe PDF Extract (API)](https://developer.adobe.com/document-services/docs/overview/pdf-extract-api/)
|
||||
- [Docling (local, open-source)](https://github.com/DS4SD/docling) – see [integrations/docling.md](./docs/integrations/docling.md) for Kotaemon-specific setup.
|
||||
- [PaddleOCR (local, open-source)](https://github.com/PADDLEPADDLE/PADDLEOCR) – see [integrations/paddle_ocr.md](./docs/integrations/paddle_ocr.md) for Kotaemon-specific setup.
|
||||
|
||||
Select corresponding loaders in `Settings -> Retrieval Settings -> File loader`
|
||||
|
||||
### Customize your application
|
||||
|
||||
- By default, all application data is stored in the `./ktem_app_data` folder. You can back up or copy this folder to transfer your installation to a new machine.
|
||||
|
||||
- For advanced users or specific use cases, you can customize these files:
|
||||
|
||||
- `flowsettings.py`
|
||||
- `.env`
|
||||
|
||||
#### `flowsettings.py`
|
||||
|
||||
This file contains the configuration of your application. You can use the example
|
||||
[here](flowsettings.py) as the starting point.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Notable settings</summary>
|
||||
|
||||
```python
|
||||
# setup your preferred document store (with full-text search capabilities)
|
||||
KH_DOCSTORE=(Elasticsearch | LanceDB | SimpleFileDocumentStore)
|
||||
|
||||
# setup your preferred vectorstore (for vector-based search)
|
||||
KH_VECTORSTORE=(ChromaDB | LanceDB | InMemory | Milvus | Qdrant)
|
||||
|
||||
# Enable / disable multimodal QA
|
||||
KH_REASONINGS_USE_MULTIMODAL=True
|
||||
|
||||
# Setup your new reasoning pipeline or modify existing one.
|
||||
KH_REASONINGS = [
|
||||
"ktem.reasoning.simple.FullQAPipeline",
|
||||
"ktem.reasoning.simple.FullDecomposeQAPipeline",
|
||||
"ktem.reasoning.react.ReactAgentPipeline",
|
||||
"ktem.reasoning.rewoo.RewooAgentPipeline",
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### `.env`
|
||||
|
||||
This file provides another way to configure your models and credentials.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configure model via the .env file</summary>
|
||||
|
||||
- Alternatively, you can configure the models via the `.env` file with the information needed to connect to the LLMs. This file is located in the folder of the application. If you don't see it, you can create one.
|
||||
|
||||
- Currently, the following providers are supported:
|
||||
|
||||
- **OpenAI**
|
||||
|
||||
In the `.env` file, set the `OPENAI_API_KEY` variable with your OpenAI API key in order
|
||||
to enable access to OpenAI's models. There are other variables that can be modified,
|
||||
please feel free to edit them to fit your case. Otherwise, the default parameter should
|
||||
work for most people.
|
||||
|
||||
```shell
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_API_KEY=<your OpenAI API key here>
|
||||
OPENAI_CHAT_MODEL=gpt-3.5-turbo
|
||||
OPENAI_EMBEDDINGS_MODEL=text-embedding-ada-002
|
||||
```
|
||||
|
||||
- **Azure OpenAI**
|
||||
|
||||
For OpenAI models via Azure platform, you need to provide your Azure endpoint and API
|
||||
key. You might also need to provide your developments' name for the chat model and the
|
||||
embedding model depending on how you set up Azure development.
|
||||
|
||||
```shell
|
||||
AZURE_OPENAI_ENDPOINT=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
OPENAI_API_VERSION=2024-02-15-preview
|
||||
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo
|
||||
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT=text-embedding-ada-002
|
||||
```
|
||||
|
||||
- **Local Models**
|
||||
|
||||
- Using `ollama` OpenAI compatible server:
|
||||
|
||||
- Install [ollama](https://github.com/ollama/ollama) and start the application.
|
||||
|
||||
- Pull your model, for example:
|
||||
|
||||
```shell
|
||||
ollama pull llama3.1:8b
|
||||
ollama pull nomic-embed-text
|
||||
```
|
||||
|
||||
- Set the model names on web UI and make it as default:
|
||||
|
||||

|
||||
|
||||
- Using `GGUF` with `llama-cpp-python`
|
||||
|
||||
You can search and download a LLM to be ran locally from the [Hugging Face Hub](https://huggingface.co/models). Currently, these model formats are supported:
|
||||
|
||||
- GGUF
|
||||
|
||||
You should choose a model whose size is less than your device's memory and should leave
|
||||
about 2 GB. For example, if you have 16 GB of RAM in total, of which 12 GB is available,
|
||||
then you should choose a model that takes up at most 10 GB of RAM. Bigger models tend to
|
||||
give better generation but also take more processing time.
|
||||
|
||||
Here are some recommendations and their size in memory:
|
||||
|
||||
- [Qwen1.5-1.8B-Chat-GGUF](https://huggingface.co/Qwen/Qwen1.5-1.8B-Chat-GGUF/resolve/main/qwen1_5-1_8b-chat-q8_0.gguf?download=true): around 2 GB
|
||||
|
||||
Add a new LlamaCpp model with the provided model name on the web UI.
|
||||
|
||||
</details>
|
||||
|
||||
### Adding your own RAG pipeline
|
||||
|
||||
#### Custom Reasoning Pipeline
|
||||
|
||||
1. Check the default pipeline implementation in [here](libs/ktem/ktem/reasoning/simple.py). You can make quick adjustment to how the default QA pipeline work.
|
||||
2. Add new `.py` implementation in `libs/ktem/ktem/reasoning/` and later include it in `flowssettings` to enable it on the UI.
|
||||
|
||||
#### Custom Indexing Pipeline
|
||||
|
||||
- Check sample implementation in `libs/ktem/ktem/index/file/graph`
|
||||
|
||||
> (more instruction WIP).
|
||||
|
||||
<!-- end-intro -->
|
||||
|
||||
## Citation
|
||||
|
||||
Please cite this project as
|
||||
|
||||
```BibTeX
|
||||
@misc{kotaemon2024,
|
||||
title = {Kotaemon - An open-source RAG-based tool for chatting with any content.},
|
||||
author = {The Kotaemon Team},
|
||||
year = {2024},
|
||||
howpublished = {\url{https://github.com/Cinnamon/kotaemon}},
|
||||
}
|
||||
```
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#Cinnamon/kotaemon&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Cinnamon/kotaemon&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Cinnamon/kotaemon&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Cinnamon/kotaemon&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
## Contribution
|
||||
|
||||
Since our project is actively being developed, we greatly value your feedback and contributions. Please see our [Contributing Guide](https://github.com/Cinnamon/kotaemon/blob/main/CONTRIBUTING.md) to get started. Thank you to all our contributors!
|
||||
|
||||
<a href="https://github.com/Cinnamon/kotaemon/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Cinnamon/kotaemon" />
|
||||
</a>
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`Cinnamon/kotaemon`
|
||||
- 原始仓库:https://github.com/Cinnamon/kotaemon
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
from theflow.settings import settings as flowsettings
|
||||
|
||||
KH_APP_DATA_DIR = getattr(flowsettings, "KH_APP_DATA_DIR", ".")
|
||||
KH_GRADIO_SHARE = getattr(flowsettings, "KH_GRADIO_SHARE", False)
|
||||
GRADIO_TEMP_DIR = os.getenv("GRADIO_TEMP_DIR", None)
|
||||
# override GRADIO_TEMP_DIR if it's not set
|
||||
if GRADIO_TEMP_DIR is None:
|
||||
GRADIO_TEMP_DIR = os.path.join(KH_APP_DATA_DIR, "gradio_tmp")
|
||||
os.environ["GRADIO_TEMP_DIR"] = GRADIO_TEMP_DIR
|
||||
|
||||
|
||||
from ktem.main import App # noqa
|
||||
|
||||
app = App()
|
||||
demo = app.make()
|
||||
demo.queue().launch(
|
||||
favicon_path=app._favicon,
|
||||
inbrowser=True,
|
||||
allowed_paths=[
|
||||
"libs/ktem/ktem/assets",
|
||||
GRADIO_TEMP_DIR,
|
||||
],
|
||||
share=KH_GRADIO_SHARE,
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
mkdocs-material
|
||||
mkdocs-gen-files
|
||||
mkdocs-literate-nav
|
||||
mkdocs-git-revision-date-localized-plugin
|
||||
mkdocs-section-index
|
||||
mkdocs-include-markdown-plugin[cache]
|
||||
mdx_truly_sane_lists
|
||||
@@ -0,0 +1,11 @@
|
||||
# About Kotaemon
|
||||
|
||||
An open-source tool for chatting with your documents. Built with both end users and
|
||||
developers in mind.
|
||||
|
||||
[Source Code](https://github.com/Cinnamon/kotaemon) |
|
||||
[HF Space](https://huggingface.co/spaces/cin-model/kotaemon-demo)
|
||||
|
||||
[Installation Guide](https://cinnamon.github.io/kotaemon/) |
|
||||
[Developer Guide](https://cinnamon.github.io/kotaemon/development/) |
|
||||
[Feedback](https://github.com/Cinnamon/kotaemon/issues)
|
||||
@@ -0,0 +1,116 @@
|
||||
# Contributing
|
||||
|
||||
## Setting up
|
||||
|
||||
- Clone the repo
|
||||
|
||||
```shell
|
||||
git clone git@github.com:Cinnamon/kotaemon.git
|
||||
cd kotaemon
|
||||
```
|
||||
|
||||
- Install the environment
|
||||
|
||||
- Create a conda environment (python >= 3.10 is recommended)
|
||||
|
||||
```shell
|
||||
conda create -n kotaemon python=3.10
|
||||
conda activate kotaemon
|
||||
|
||||
# install dependencies
|
||||
cd libs/kotaemon
|
||||
pip install -e ".[all]"
|
||||
```
|
||||
|
||||
- Or run the installer (one of the `scripts/run_*` scripts depends on your OS), then
|
||||
you will have all the dependencies installed as a conda environment at
|
||||
`install_dir/env`.
|
||||
|
||||
```shell
|
||||
conda activate install_dir/env
|
||||
```
|
||||
|
||||
- Pre-commit
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
- Test
|
||||
|
||||
```shell
|
||||
pytest tests
|
||||
```
|
||||
|
||||
## Package overview
|
||||
|
||||
`kotaemon` library focuses on the AI building blocks to implement a RAG-based QA application. It consists of base interfaces, core components and a list of utilities:
|
||||
|
||||
- Base interfaces: `kotaemon` defines the base interface of a component in a pipeline. A pipeline is also a component. By clearly define this interface, a pipeline of steps can be easily constructed and orchestrated.
|
||||
- Core components: `kotaemon` implements (or wraps 3rd-party libraries
|
||||
like Langchain, llama-index,... when possible) commonly used components in
|
||||
kotaemon use cases. Some of these components are: LLM, vector store,
|
||||
document store, retriever... For a detailed list and description of these
|
||||
components, please refer to the [API Reference](../reference/Summary.md) section.
|
||||
- List of utilities: `kotaemon` provides utilities and tools that are
|
||||
usually needed in client project. For example, it provides a prompt
|
||||
engineering UI for AI developers in a project to quickly create a prompt
|
||||
engineering tool for DMs and QALs. It also provides a command to quickly spin
|
||||
up a project code base. For a full list and description of these utilities,
|
||||
please refer to the [Utilities](utilities.md) section.
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((kotaemon))
|
||||
Base Interfaces
|
||||
Document
|
||||
LLMInterface
|
||||
RetrievedDocument
|
||||
BaseEmbeddings
|
||||
BaseChat
|
||||
BaseCompletion
|
||||
...
|
||||
Core Components
|
||||
LLMs
|
||||
AzureOpenAI
|
||||
OpenAI
|
||||
Embeddings
|
||||
AzureOpenAI
|
||||
OpenAI
|
||||
HuggingFaceEmbedding
|
||||
VectorStore
|
||||
InMemoryVectorstore
|
||||
ChromaVectorstore
|
||||
Agent
|
||||
Tool
|
||||
DocumentStore
|
||||
...
|
||||
Utilities
|
||||
Scaffold project
|
||||
PromptUI
|
||||
Documentation Support
|
||||
```
|
||||
|
||||
## Common conventions
|
||||
|
||||
- PR title: One-line description (example: Feat: Declare BaseComponent and decide LLM call interface).
|
||||
- [Encouraged] Provide a quick description in the PR, so that:
|
||||
- Reviewers can quickly understand the direction of the PR.
|
||||
- It will be included in the commit message when the PR is merged.
|
||||
|
||||
## Environment caching on PR
|
||||
|
||||
- To speed up CI, environments are cached based on the version specified in `__init__.py`.
|
||||
- Since dependencies versions in `setup.py` are not pinned, you need to pump the version in order to use a new environment. That environment will then be cached and used by your subsequence commits within the PR, until you pump the version again
|
||||
- The new environment created during your PR is cached and will be available to others once the PR is merged.
|
||||
- If you are experimenting with new dependencies and want a fresh environment every time, add `[ignore cache]` in your commit message. The CI will create a fresh environment to run your commit and then discard it.
|
||||
- If your PR include updated dependencies, the recommended workflow would be:
|
||||
- Doing development as usual.
|
||||
- When you want to run the CI, push a commit with the message containing `[ignore cache]`.
|
||||
- Once the PR is final, pump the version in `__init__.py` and push a final commit not containing `[ignore cache]`.
|
||||
|
||||
## Merge PR guideline
|
||||
|
||||
- Use squash and merge option
|
||||
- 1st line message is the PR title.
|
||||
- The text area is the PR description.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Creating a component
|
||||
|
||||
A fundamental concept in kotaemon is "component".
|
||||
|
||||
Anything that isn't data or data structure is a "component". A component can be
|
||||
thought of as a step within a pipeline. It takes in some input, processes it,
|
||||
and returns an output, just the same as a Python function! The output will then
|
||||
become an input for the next component in a pipeline. In fact, a pipeline is just
|
||||
a component. More appropriately, a nested component: a component that makes use of one or more other components in
|
||||
the processing step. So in reality, there isn't a difference between a pipeline
|
||||
and a component! Because of that, in kotaemon, we will consider them the
|
||||
same as "component".
|
||||
|
||||
To define a component, you will:
|
||||
|
||||
1. Create a class that subclasses from `kotaemon.base.BaseComponent`
|
||||
2. Declare init params with type annotation
|
||||
3. Declare nodes (nodes are just other components!) with type annotation
|
||||
4. Implement the processing logic in `run`.
|
||||
|
||||
The syntax of a component is as follow:
|
||||
|
||||
```python
|
||||
from kotaemon.base import BaseComponent
|
||||
from kotaemon.llms import LCAzureChatOpenAI
|
||||
from kotaemon.parsers import RegexExtractor
|
||||
|
||||
|
||||
class FancyPipeline(BaseComponent):
|
||||
param1: str = "This is param1"
|
||||
param2: int = 10
|
||||
param3: float
|
||||
|
||||
node1: BaseComponent # this is a node because of BaseComponent type annotation
|
||||
node2: LCAzureChatOpenAI # this is also a node because LCAzureChatOpenAI subclasses BaseComponent
|
||||
node3: RegexExtractor # this is also a node bceause RegexExtractor subclasses BaseComponent
|
||||
|
||||
def run(self, some_text: str):
|
||||
prompt = (self.param1 + some_text) * int(self.param2 + self.param3)
|
||||
llm_pred = self.node2(prompt).text
|
||||
matches = self.node3(llm_pred)
|
||||
return matches
|
||||
```
|
||||
|
||||
Then this component can be used as follow:
|
||||
|
||||
```python
|
||||
llm = LCAzureChatOpenAI(endpoint="some-endpont")
|
||||
extractor = RegexExtractor(pattern=["yes", "Yes"])
|
||||
|
||||
component = FancyPipeline(
|
||||
param1="Hello"
|
||||
param3=1.5
|
||||
node1=llm,
|
||||
node2=llm,
|
||||
node3=extractor
|
||||
)
|
||||
component("goodbye")
|
||||
```
|
||||
|
||||
This way, we can define each operation as a reusable component, and use them to
|
||||
compose larger reusable components!
|
||||
|
||||
## Benefits of component
|
||||
|
||||
By defining a component as above, we formally encapsulate all the necessary
|
||||
information inside a single class. This introduces several benefits:
|
||||
|
||||
1. Allow tools like promptui to inspect the inner working of a component in
|
||||
order to automatically generate the promptui.
|
||||
2. Allow visualizing a pipeline for debugging purpose.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Data & Data Structure Components
|
||||
|
||||
The data & data structure components include:
|
||||
|
||||
- The `Document` class.
|
||||
- The document store.
|
||||
- The vector store.
|
||||
|
||||
## Data Loader
|
||||
|
||||
- PdfLoader
|
||||
- Layout-aware with table parsing PdfLoader
|
||||
|
||||
- MathPixLoader: To use this loader, you need MathPix API key, refer to [mathpix docs](https://docs.mathpix.com/#introduction) for more information
|
||||
- OCRLoader: This loader uses lib-table and Flax pipeline to perform OCR and read table structure from PDF file (TODO: add more info about deployment of this module).
|
||||
- Output:
|
||||
|
||||
- Document: text + metadata to identify whether it is table or not
|
||||
|
||||
```
|
||||
- "source": source file name
|
||||
- "type": "table" or "text"
|
||||
- "table_origin": original table in markdown format (to be feed to LLM or visualize using external tools)
|
||||
- "page_label": page number in the original PDF document
|
||||
```
|
||||
|
||||
## Document Store
|
||||
|
||||
- InMemoryDocumentStore
|
||||
|
||||
## Vector Store
|
||||
|
||||
- ChromaVectorStore
|
||||
- InMemoryVectorStore
|
||||
@@ -0,0 +1,5 @@
|
||||
{%
|
||||
include-markdown "../../README.md"
|
||||
start="<!-- start-intro -->"
|
||||
end="<!-- end-intro -->"
|
||||
%}
|
||||
@@ -0,0 +1,169 @@
|
||||
# Utilities
|
||||
|
||||
## Prompt engineering UI
|
||||
|
||||

|
||||
|
||||
**_Important:_** despite the name prompt engineering UI, this tool allows testers to test any kind of parameters that are exposed by developers. Prompt is one kind of param. There can be other type of params that testers can tweak (e.g. top_k, temperature...).
|
||||
|
||||
In the development process, developers typically build the pipeline. However, for use
|
||||
cases requiring expertise in prompt creation, non-technical members (testers, domain experts) can be more
|
||||
effective. To facilitate this, `kotaemon` offers a user-friendly prompt engineering UI
|
||||
that developers integrate into their pipelines. This enables non-technical members to
|
||||
adjust prompts and parameters, run experiments, and export results for optimization.
|
||||
|
||||
As of Sept 2023, there are 2 kinds of prompt engineering UI:
|
||||
|
||||
- Simple pipeline: run one-way from start to finish.
|
||||
- Chat pipeline: interactive back-and-forth.
|
||||
|
||||
### Simple pipeline
|
||||
|
||||
For simple pipeline, the supported client project workflow looks as follow:
|
||||
|
||||
1. [tech] Build pipeline
|
||||
2. [tech] Export pipeline to config: `$ kotaemon promptui export <module.path.piplineclass> --output <path/to/config/file.yml>`
|
||||
3. [tech] Customize the config
|
||||
4. [tech] Spin up prompt engineering UI: `$ kotaemon promptui run <path/to/config/file.yml>`
|
||||
5. [non-tech] Change params, run inference
|
||||
6. [non-tech] Export to Excel
|
||||
7. [non-tech] Select the set of params that achieve the best output
|
||||
|
||||
The prompt engineering UI prominently involves from step 2 to step 7 (step 1 is normally
|
||||
done by the developers, while step 7 happens exclusively in Excel file).
|
||||
|
||||
#### Step 2 - Export pipeline to config
|
||||
|
||||
Command:
|
||||
|
||||
```shell
|
||||
$ kotaemon promptui export <module.path.piplineclass> --output <path/to/config/file.yml>
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
- `<module.path.pipelineclass>` is a dot-separated path to the pipeline. For example, if your pipeline can be accessed with `from projectA.pipelines import AnsweringPipeline`, then this value is `projectA.pipelines.AnswerPipeline`.
|
||||
- `<path/to/config/file.yml>` is the target file path that the config will be exported to. If the config file already exists, and contains information of other pipelines, the config of current pipeline will additionally be added. If it contains information of the current pipeline (in the past), the old information will be replaced.
|
||||
|
||||
By default, all params in a pipeline (including nested params) will be export to the configuration file. For params that you do not wish to expose to the UI, you can directly remove them from the config YAML file. You can also annotate those param with `ignore_ui=True`, and they will be ignored in the config generation process. Example:
|
||||
|
||||
```python
|
||||
class Pipeline(BaseComponent):
|
||||
param1: str = Param(default="hello")
|
||||
param2: str = Param(default="goodbye", ignore_ui=True)
|
||||
```
|
||||
|
||||
Declared as above, and `param1` will show up in the config YAML file, while `param2` will not.
|
||||
|
||||
#### Step 3 - Customize the config
|
||||
|
||||
developers can further edit the config file in this step to get the most suitable UI (step 4) with their tasks. The exported config will have this overall schema:
|
||||
|
||||
```yml
|
||||
<module.path.pipelineclass1>:
|
||||
params: ... (Detail param information to initiate a pipeline. This corresponds to the pipeline init parameters.)
|
||||
inputs: ... (Detail the input of the pipeline e.g. a text prompt. This corresponds to the params of `run(...)` method.)
|
||||
outputs: ... (Detail the output of the pipeline e.g. prediction, accuracy... This is the output information we wish to see in the UI.)
|
||||
logs: ... (Detail what information should show up in the log.)
|
||||
```
|
||||
|
||||
##### Input and params
|
||||
|
||||
The inputs section have the overall schema as follow:
|
||||
|
||||
```yml
|
||||
inputs:
|
||||
<input-variable-name-1>:
|
||||
component: <supported-UI-component>
|
||||
params: # this section is optional)
|
||||
value: <default-value>
|
||||
<input-variable-name-2>: ... # similar to above
|
||||
params:
|
||||
<param-variable-name-1>: ... # similar to those in the inputs
|
||||
```
|
||||
|
||||
The list of supported prompt UI and their corresponding gradio UI components:
|
||||
|
||||
```python
|
||||
COMPONENTS_CLASS = {
|
||||
"text": gr.components.Textbox,
|
||||
"checkbox": gr.components.CheckboxGroup,
|
||||
"dropdown": gr.components.Dropdown,
|
||||
"file": gr.components.File,
|
||||
"image": gr.components.Image,
|
||||
"number": gr.components.Number,
|
||||
"radio": gr.components.Radio,
|
||||
"slider": gr.components.Slider,
|
||||
}
|
||||
```
|
||||
|
||||
##### Outputs
|
||||
|
||||
The outputs are a list of variables that we wish to show in the UI. Since in Python, the function output doesn't have variable name, so output declaration is a little bit different than input and param declaration:
|
||||
|
||||
```yml
|
||||
outputs:
|
||||
- component: <supported-UI-component>
|
||||
step: <name-of-pipeline-step>
|
||||
item: <jsonpath way to retrieve the info>
|
||||
- ... # similar to above
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
- component: the same text string and corresponding Gradio UI as in inputs & params
|
||||
- step: the pipeline step that we wish to look fetch and show output on the UI
|
||||
- item: the jsonpath mechanism to get the targeted variable from the step above
|
||||
|
||||
##### Logs
|
||||
|
||||
The logs show a list of sheetname and how to retrieve the desired information.
|
||||
|
||||
```yml
|
||||
logs:
|
||||
<logname>:
|
||||
inputs:
|
||||
- name: <column name>
|
||||
step: <the pipeline step that we would wish to see the input>
|
||||
variable: <the variable in the step>
|
||||
- ...
|
||||
outputs:
|
||||
- name: <column name>
|
||||
step: <the pipeline step that we would wish to see the output>
|
||||
item: <how to retrieve the output of that step>
|
||||
```
|
||||
|
||||
#### Step 4 + 5 - Spin up prompt engineering UI + Perform prompt engineering
|
||||
|
||||
Command:
|
||||
|
||||
```shell
|
||||
$ kotaemon promptui run <path/to/config/file.yml>
|
||||
```
|
||||
|
||||
This will generate an UI as follow:
|
||||
|
||||

|
||||
|
||||
where:
|
||||
|
||||
- The tabs at the top of the UI corresponds to the pipeline to do prompt engineering.
|
||||
- The inputs and params tabs allow users to edit (these corresponds to the inputs and params in the config file).
|
||||
- The outputs panel holds the UI elements to show the outputs defined in config file.
|
||||
- The Run button: will execute pipeline with the supplied inputs and params, and render result in the outputs panel.
|
||||
- The Export button: will export the logs of all the run to an Excel files users to inspect for best set of params.
|
||||
|
||||
#### Step 6 - Export to Excel
|
||||
|
||||
Upon clicking export, the users can download Excel file.
|
||||
|
||||
### Chat pipeline
|
||||
|
||||
Chat pipeline workflow is different from simple pipeline workflow. In simple pipeline, each Run creates a set of output, input and params for users to compare. In chat pipeline, each Run is not a one-off run, but a long interactive session. Hence, the workflow is as follow:
|
||||
|
||||
1. Set the desired parameters.
|
||||
2. Click "New chat" to start a chat session with the supplied parameters. This set of parameters will persist until the end of the chat session. During an ongoing chat session, changing the parameters will not take any effect.
|
||||
3. Chat and interact with the chat bot on the right panel. You can add any additional input (if any), and they will be supplied to the chatbot.
|
||||
4. During chat, the log of the chat will show up in the "Output" tabs. This is empty by default, so if you want to show the log here, tell the AI developers to configure the UI settings.
|
||||
5. When finishing chat, select your preference in the radio box. Click "End chat". This will save the chat log and the preference to disk.
|
||||
6. To compare the result of different run, click "Export" to get an Excel spreadsheet summary of different run.
|
||||
@@ -0,0 +1,5 @@
|
||||
.language-pycon .gp,
|
||||
.language-pycon .go {
|
||||
/* Generic.Prompt, Generic.Output */
|
||||
user-select: none;
|
||||
}
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 950 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 369 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 383 KiB |
|
After Width: | Height: | Size: 545 KiB |
|
After Width: | Height: | Size: 268 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 566 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 216 KiB |
@@ -0,0 +1,48 @@
|
||||
# Getting Started with Kotaemon
|
||||
|
||||

|
||||
|
||||
This page is intended for **end users** who want to use the `kotaemon` tool for Question
|
||||
Answering on local documents. If you are a **developer** who wants contribute to the project, please visit the [development](development/index.md) page.
|
||||
|
||||
## Installation (Online HuggingFace Space) - easy (10 mins)
|
||||
|
||||
Visit this [guide](online_install.md).
|
||||
|
||||
## Installation (Offline) - intermediate (20 mins)
|
||||
|
||||
### Download
|
||||
|
||||
Download the `kotaemon-app.zip` file from the [latest release](https://github.com/Cinnamon/kotaemon/releases/latest/).
|
||||
|
||||
### Run setup script
|
||||
|
||||
0. Unzip the downloaded file.
|
||||
1. Navigate to the `scripts` folder and start an installer that matches your OS:
|
||||
- Windows: `run_windows.bat`. Just double click the file.
|
||||
- macOS: `run_macos.sh`
|
||||
1. Right click on your file and select Open with and Other.
|
||||
2. Enable All Applications and choose Terminal.
|
||||
3. NOTE: If you always want to open that file with Terminal, then check Always Open With.
|
||||
4. From now on, double click on your file and it should work.
|
||||
- Linux: `run_linux.sh`. Please run the script using `bash run_linux.sh` in your terminal.
|
||||
2. After the installation, the installer will ask to launch the ktem's UI, answer to continue.
|
||||
3. If launched, the application will be open automatically in your browser.
|
||||
4. Default login information is: `username: admin / password: admin`. You should change this credential right after the first login on the UI.
|
||||
|
||||
## Launch
|
||||
|
||||
To launch the app after initial setup or any change, simply run the `run_*` script again.
|
||||
|
||||
A browser window will be opened and greets you with this screen:
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
For how to use the application, see [Usage](usage.md). This page will also be available to
|
||||
you within the application.
|
||||
|
||||
## Feedback
|
||||
|
||||
Feel free to create a bug report or a feature request on our [repo](https://github.com/Cinnamon/kotaemon/issues).
|
||||
@@ -0,0 +1,29 @@
|
||||
# Docling
|
||||
|
||||
Kotaemon provides a [Docling](https://github.com/DS4SD/docling) reader to enable local document ingestion with structure-aware parsing, including text, tables, and figures.
|
||||
The reader is located under `kotaemon/loaders/docling_loader`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Install Docling:
|
||||
|
||||
```bash
|
||||
uv pip install -e "libs/kotaemon[docling]"
|
||||
```
|
||||
|
||||
- Configure optional figure captioning:
|
||||
|
||||
Docling can generate figure captions when a VLM endpoint is available. Set `KH_VLM_ENDPOINT` in your `.env` file or application settings to enable captioning.
|
||||
|
||||
```bash
|
||||
KH_VLM_ENDPOINT=http://your-vlm-endpoint
|
||||
```
|
||||
|
||||
If `KH_VLM_ENDPOINT` is not set, Docling will still extract text, tables, and figure metadata, but it will skip generated figure captions.
|
||||
|
||||
## Configure the loader
|
||||
|
||||
1. Run Kotaemon and open the app UI.
|
||||
2. Navigate to Settings → Retrieval Settings → File loader.
|
||||
3. Select `Docling (figure+table extraction)`.
|
||||
4. Save the settings, then upload or ingest a document. Kotaemon will use Docling during indexing and convert extracted content into `Document` objects.
|
||||
@@ -0,0 +1,43 @@
|
||||
# PaddleOCR
|
||||
|
||||
Kotaemon provides two [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) readers to enable document ingestion with full layout understanding, including multilingual text, tables, figures, formulas, and seals.
|
||||
|
||||
- `PaddleOCRVLReader`: Wraps the PaddleOCR-VL 1.5 visual-language model for robust layout and VQA-based parsing.
|
||||
- `PPStructureV3Reader`: Uses the PPStructureV3 pipeline for structured layout analysis, including table and chart detection.
|
||||
|
||||
Both readers are located under `kotaemon/loaders/paddleocr_loader`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Install PaddlePaddle:
|
||||
- Ensure that the installed PaddlePaddle version matches your system configuration and hardware (CPU/GPU). For additional wheel options, refer to the [PaddlePaddle official website](https://www.paddlepaddle.org.cn/install/quick?docurl=undefined).
|
||||
- Check device support for [PaddleOCR-VL](https://www.paddleocr.ai/main/en/version3.x/pipeline_usage/PaddleOCR-VL.html#inference-device-support-for-paddleocr-vl).
|
||||
|
||||
```bash
|
||||
# CPU
|
||||
uv pip install paddlepaddle==3.3.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/
|
||||
|
||||
# gpu,requires GPU driver version ≥550.54.14 (Linux) or ≥550.54.14 (Windows)
|
||||
uv pip install paddlepaddle-gpu==3.3.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu130/
|
||||
```
|
||||
|
||||
- Install the PaddleOCR doc parser extras:
|
||||
|
||||
```bash
|
||||
uv pip install -e "libs/kotaemon[paddleocr]"
|
||||
```
|
||||
|
||||
- Configure the device: You can set the `PADDLE_DEVICE` environment variable in your .env file to control the execution device.
|
||||
|
||||
```bash
|
||||
PADDLE_DEVICE=gpu # cpu, gpu:0
|
||||
```
|
||||
|
||||
## Configure the loader
|
||||
|
||||
1. Run Kotaemon and open the app UI.
|
||||
2. Navigate to Settings → File Loader (under indexing or ingestion settings).
|
||||
3. Select one of the PaddleOCR loaders:
|
||||
- `PaddleOCR PPStructureV3 (table+figure extraction)`
|
||||
- `PaddleOCR-VL (VLM document parsing)`
|
||||
4. Save the settings, then upload or ingest a document. Kotaemon will automatically use the selected PaddleOCR loader during indexing and convert extracted content into Document objects.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Setup local LLMs & Embedding models
|
||||
|
||||
## Prepare local models
|
||||
|
||||
#### NOTE
|
||||
|
||||
In the case of using Docker image, please replace `http://localhost` with `http://host.docker.internal` to correctly communicate with service on the host machine. See [more detail](https://stackoverflow.com/questions/31324981/how-to-access-host-port-from-docker-container).
|
||||
|
||||
### Ollama OpenAI compatible server (recommended)
|
||||
|
||||
Install [ollama](https://github.com/ollama/ollama) and start the application.
|
||||
|
||||
Pull your model (e.g):
|
||||
|
||||
```
|
||||
ollama pull llama3.1:8b
|
||||
ollama pull nomic-embed-text
|
||||
```
|
||||
|
||||
Setup LLM and Embedding model on Resources tab with type OpenAI. Set these model parameters to connect to Ollama:
|
||||
|
||||
```
|
||||
api_key: ollama
|
||||
base_url: http://localhost:11434/v1/
|
||||
model: gemma2:2b (for llm) | nomic-embed-text (for embedding)
|
||||
```
|
||||
|
||||

|
||||
|
||||
### oobabooga/text-generation-webui OpenAI compatible server
|
||||
|
||||
Install [oobabooga/text-generation-webui](https://github.com/oobabooga/text-generation-webui/).
|
||||
|
||||
Follow the setup guide to download your models (GGUF, HF).
|
||||
Also take a look at [OpenAI compatible server](https://github.com/oobabooga/text-generation-webui/wiki/12-%E2%80%90-OpenAI-API) for detail instructions.
|
||||
|
||||
Here is a short version
|
||||
|
||||
```
|
||||
# install sentence-transformer for embeddings creation
|
||||
pip install sentence_transformers
|
||||
# change to text-generation-webui src dir
|
||||
python server.py --api
|
||||
```
|
||||
|
||||
Use the `Models` tab to download new model and press Load.
|
||||
|
||||
Setup LLM and Embedding model on Resources tab with type OpenAI. Set these model parameters to connect to `text-generation-webui`:
|
||||
|
||||
```
|
||||
api_key: dummy
|
||||
base_url: http://localhost:5000/v1/
|
||||
model: any
|
||||
```
|
||||
|
||||
### llama-cpp-python server (LLM only)
|
||||
|
||||
See [llama-cpp-python OpenAI server](https://llama-cpp-python.readthedocs.io/en/latest/server/).
|
||||
|
||||
Download any GGUF model weight on HuggingFace or other source. Place it somewhere on your local machine.
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
LOCAL_MODEL=<path/to/GGUF> python scripts/serve_local.py
|
||||
```
|
||||
|
||||
Setup LLM model on Resources tab with type OpenAI. Set these model parameters to connect to `llama-cpp-python`:
|
||||
|
||||
```
|
||||
api_key: dummy
|
||||
base_url: http://localhost:8000/v1/
|
||||
model: model_name
|
||||
```
|
||||
|
||||
## Use local models for RAG
|
||||
|
||||
- Set default LLM and Embedding model to a local variant.
|
||||
|
||||

|
||||
|
||||
- Set embedding model for the File Collection to a local model (e.g: `ollama`)
|
||||
|
||||

|
||||
|
||||
- Go to Retrieval settings and choose LLM relevant scoring model as a local model (e.g: `ollama`). Or, you can choose to disable this feature if your machine cannot handle a lot of parallel LLM requests at the same time.
|
||||
|
||||

|
||||
|
||||
You are set! Start a new conversation to test your local RAG pipeline.
|
||||
@@ -0,0 +1,13 @@
|
||||
## Installation (Online HuggingFace Space)
|
||||
|
||||
1. Go to [HF kotaemon_template](https://huggingface.co/spaces/cin-model/kotaemon_template).
|
||||
2. Use Duplicate function to create your own space. Or use this [direct link](https://huggingface.co/spaces/cin-model/kotaemon_template?duplicate=true).
|
||||

|
||||

|
||||
3. Wait for the build to complete and start up (apprx 10 mins).
|
||||

|
||||

|
||||
4. Follow the first setup instructions (and register for Cohere API key if needed).
|
||||

|
||||
5. Complete the setup and use your own private space!
|
||||

|
||||
@@ -0,0 +1,232 @@
|
||||
# Add new indexing and reasoning pipeline to the application
|
||||
|
||||
@trducng
|
||||
|
||||
At high level, to add new indexing and reasoning pipeline:
|
||||
|
||||
1. You define your indexing or reasoning pipeline as a class from
|
||||
`BaseComponent`.
|
||||
2. You declare that class in the setting files `flowsettings.py`.
|
||||
|
||||
Then when `python app.py`, the application will dynamically load those
|
||||
pipelines.
|
||||
|
||||
The below sections talk in more detail about how the pipelines should be
|
||||
constructed.
|
||||
|
||||
## Define a pipeline as a class
|
||||
|
||||
In essence, a pipeline will subclass from `kotaemon.base.BaseComponent`.
|
||||
Each pipeline has 2 main parts:
|
||||
|
||||
- All declared arguments and sub-pipelines.
|
||||
- The logic inside the pipeline.
|
||||
|
||||
An example pipeline:
|
||||
|
||||
```python
|
||||
from kotaemon.base import BaseComponent
|
||||
|
||||
|
||||
class SoSimple(BaseComponent):
|
||||
arg1: int
|
||||
arg2: str
|
||||
|
||||
def run(self, arg3: str):
|
||||
return self.arg1 * self.arg2 + arg3
|
||||
```
|
||||
|
||||
This pipeline is simple for demonstration purpose, but we can imagine pipelines
|
||||
with much more arguments, that can take other pipelines as arguments, and have
|
||||
more complicated logic in the `run` method.
|
||||
|
||||
**_An indexing or reasoning pipeline is just a class subclass from
|
||||
`BaseComponent` like above._**
|
||||
|
||||
For more detail on this topic, please refer to [Creating a
|
||||
Component](/create-a-component/)
|
||||
|
||||
## Run signatures
|
||||
|
||||
**Note**: this section is tentative at the moment. We will finalize `def run`
|
||||
function signature by latest early April.
|
||||
|
||||
The indexing pipeline:
|
||||
|
||||
```python
|
||||
def run(
|
||||
self,
|
||||
file_paths: str | Path | list[str | Path],
|
||||
reindex: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Index files to intermediate representation (e.g. vector, database...)
|
||||
|
||||
Args:
|
||||
file_paths: the list of paths to files
|
||||
reindex: if True, files in `file_paths` that already exists in database
|
||||
should be reindex.
|
||||
"""
|
||||
```
|
||||
|
||||
The reasoning pipeline:
|
||||
|
||||
```python
|
||||
def run(self, question: str, history: list, **kwargs) -> Document:
|
||||
"""Answer the question
|
||||
|
||||
Args:
|
||||
question: the user input
|
||||
history: the chat history [(user_msg1, bot_msg1), (user_msg2, bot_msg2)...]
|
||||
|
||||
Returns:
|
||||
kotaemon.base.Document: the final answer
|
||||
"""
|
||||
```
|
||||
|
||||
## Register your pipeline to ktem
|
||||
|
||||
To register your pipelines to ktem, you declare it in the `flowsettings.py`
|
||||
file. This file locates at the current working directory where you start the
|
||||
ktem. In most use cases, it is this
|
||||
[one](https://github.com/Cinnamon/kotaemon/blob/main/flowsettings.py).
|
||||
|
||||
```python
|
||||
KH_REASONING = ["<python.module.path.to.the.reasoning.class>"]
|
||||
|
||||
KH_INDEX = "<python.module.path.to.the.indexing.class>"
|
||||
```
|
||||
|
||||
You can register multiple reasoning pipelines to ktem by populating the
|
||||
`KH_REASONING` list. The user can select which reasoning pipeline to use
|
||||
in their Settings page.
|
||||
|
||||
For now, there's only one supported index option for `KH_INDEX`.
|
||||
|
||||
Make sure that your class is discoverable by Python.
|
||||
|
||||
## Allow users to customize your pipeline in the app settings
|
||||
|
||||
To allow the users to configure your pipeline, you need to declare what you
|
||||
allow the users to configure as a dictionary. `ktem` will include them into the
|
||||
application settings.
|
||||
|
||||
In your pipeline class, add a classmethod `get_user_settings` that returns a
|
||||
setting dictionary, add a classmethod `get_info` that returns an info
|
||||
dictionary. Example:
|
||||
|
||||
```python
|
||||
class SoSimple(BaseComponent):
|
||||
|
||||
... # as above
|
||||
|
||||
@classmethod
|
||||
def get_user_settings(cls) -> dict:
|
||||
"""The settings to the user"""
|
||||
return {
|
||||
"setting_1": {
|
||||
"name": "Human-friendly name",
|
||||
"value": "Default value",
|
||||
"choices": [("Human-friendly Choice 1", "choice1-id"), ("HFC 2", "choice2-id")], # optional
|
||||
"component": "Which Gradio UI component to render, can be: text, number, checkbox, dropdown, radio, checkboxgroup"
|
||||
},
|
||||
"setting_2": {
|
||||
# follow the same rule as above
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_info(cls) -> dict:
|
||||
"""Pipeline information for bookkeeping purpose"""
|
||||
return {
|
||||
"id": "a unique id to differentiate this pipeline from other pipeline",
|
||||
"name": "Human-friendly name of the pipeline",
|
||||
"description": "Can be a short description of this pipeline"
|
||||
}
|
||||
```
|
||||
|
||||
Once adding these methods to your pipeline class, `ktem` will automatically
|
||||
extract and add them to the settings.
|
||||
|
||||
## Construct to pipeline object
|
||||
|
||||
Once `ktem` runs your pipeline, it will call your classmethod `get_pipeline`
|
||||
with the full user settings and expect to obtain the pipeline object. Within
|
||||
this `get_pipeline` method, you implement all the necessary logics to initiate
|
||||
the pipeline object. Example:
|
||||
|
||||
```python
|
||||
class SoSimple(BaseComponent):
|
||||
... # as above
|
||||
|
||||
@classmethod
|
||||
def get_pipeline(self, setting):
|
||||
obj = cls(arg1=setting["reasoning.id.setting1"])
|
||||
return obj
|
||||
```
|
||||
|
||||
## Reasoning: Stream output to UI
|
||||
|
||||
For fast user experience, you can stream the output directly to UI. This way,
|
||||
user can start observing the output as soon as the LLM model generates the 1st
|
||||
token, rather than having to wait the pipeline finishes to read the whole message.
|
||||
|
||||
To stream the output, you need to;
|
||||
|
||||
1. Turn the `run` function to async.
|
||||
2. Pass in the output to a special queue with `self.report_output`.
|
||||
|
||||
```python
|
||||
|
||||
async def run(self, question: str, history: list, **kwargs) -> Document:
|
||||
for char in "This is a long messages":
|
||||
self.report_output({"output": text.text})
|
||||
```
|
||||
|
||||
The argument to `self.report_output` is a dictionary, that contains either or
|
||||
all of these 2 keys: "output", "evidence". The "output" string will be streamed
|
||||
to the chat message, and the "evidence" string will be streamed to the
|
||||
information panel.
|
||||
|
||||
## Access application LLMs, Embeddings
|
||||
|
||||
You can access users' collections of LLMs and embedding models with:
|
||||
|
||||
```python
|
||||
from ktem.embeddings.manager import embeddings
|
||||
from ktem.llms.manager import llms
|
||||
|
||||
|
||||
llm = llms.get_default()
|
||||
embedding_model = embeddings.get_default()
|
||||
```
|
||||
|
||||
You can also allow the users to specifically select which llms or embedding
|
||||
models they want to use through the settings.
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def get_user_settings(cls) -> dict:
|
||||
from ktem.llms.manager import llms
|
||||
|
||||
return {
|
||||
"citation_llm": {
|
||||
"name": "LLM for citation",
|
||||
"value": llms.get_default(),
|
||||
"component: "dropdown",
|
||||
"choices": list(llms.options().keys()),
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Optional: Access application data
|
||||
|
||||
You can access the user's application database, vector store as follow:
|
||||
|
||||
```python
|
||||
# get the database that contains the source files
|
||||
from ktem.db.models import Source, Index, Conversation, User
|
||||
|
||||
# get the vector store
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
`ktem` provides user management as an extension. To enable user management, in
|
||||
your `flowsettings.py`, set the following variables:
|
||||
|
||||
- `KH_FEATURE_USER_MANAGEMENT`: True to enable.
|
||||
- `KH_FEATURE_USER_MANAGEMENT_ADMIN`: the admin username. This user will be
|
||||
created when the app 1st start.
|
||||
- `KH_FEATURE_USER_MANAGEMENT_PASSWORD`: the admin password. This value
|
||||
accompanies the admin username.
|
||||
|
||||
Once enabled, you have access to the following features:
|
||||
|
||||
- User login/logout (located in Settings Tab)
|
||||
- User changing password (located in Settings Tab)
|
||||
- Create / List / Edit / Delete user (located in Resources > Users Tab)
|
||||
@@ -0,0 +1,8 @@
|
||||
## Chat
|
||||
|
||||
The kotaemon focuses on question and answering over a corpus of data. Below
|
||||
is the gentle introduction about the chat functionality.
|
||||
|
||||
- Users can upload corpus of files.
|
||||
- Users can converse to the chatbot to ask questions about the corpus of files.
|
||||
- Users can view the reference in the files.
|
||||
@@ -0,0 +1,366 @@
|
||||
## User group / tenant management
|
||||
|
||||
### Create new user group
|
||||
|
||||
(6 man-days)
|
||||
|
||||
**Description**: each client has a dedicated user group. Each user group has an
|
||||
admin user who can do administrative tasks (e.g. creating user account in that
|
||||
user group...). The workflow for creating new user group is as follow:
|
||||
|
||||
1. Cinnamon accesses the user group management UI.
|
||||
2. On "Create user group" panel, we supply:
|
||||
a. Client name: e.g. Apple.
|
||||
b. Sub-domain name: e.g. apple.
|
||||
c. Admin email, username & password.
|
||||
3. The system will:
|
||||
a. An Aurora Platform deployment with the specified sub-domain.
|
||||
b. Send an email to the admin, with the username & password.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- The admin can go to the deployed Aurora Platform.
|
||||
- The admin can login with the specified username & password.
|
||||
|
||||
**Condition**:
|
||||
|
||||
- When sub-domain name already exists, raise error.
|
||||
- If error sending email to the client, raise the error, and delete the
|
||||
newly-created user-group.
|
||||
- Password rule:
|
||||
- Have at least 8 characters.
|
||||
- Must contain uppercase, lowercase, number and symbols.
|
||||
|
||||
---
|
||||
|
||||
### Delete user group
|
||||
|
||||
(2 man-days)
|
||||
|
||||
**Description**: in the tenant management page, we can delete the selected user
|
||||
group. The user flow is as follow:
|
||||
|
||||
1. Cinnamon accesses the user group management UI,
|
||||
2. View list of user groups.
|
||||
3. Next to target user group, click delete.
|
||||
4. Confirm whether to delete.
|
||||
5. If Yes, delete the user group. If No, cancel the operation.
|
||||
|
||||
**Expectation**: when a user group is deleted, we expect to delete everything
|
||||
related to the user groups: domain, files, databases, caches, deployments.
|
||||
|
||||
## User management
|
||||
|
||||
---
|
||||
|
||||
### Create user account (for admin user)
|
||||
|
||||
(1 man-day)
|
||||
|
||||
**Description**: the admin user in the client's account can create user account
|
||||
for that user group. To create the new user, the client admin do:
|
||||
|
||||
1. Navigate to "Admin" > "Users"
|
||||
2. In the "Create user" panel, supply:
|
||||
- Username
|
||||
- Password
|
||||
- Confirm password
|
||||
3. Click "Create"
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- The user can create the account.
|
||||
- The username:
|
||||
- Is case-insensitive (e.g. Moon and moon will be the same)
|
||||
- Can only contains these characters: a-z A-Z 0-9 \_ + - .
|
||||
- Has maximum length of 32 characters
|
||||
- The password is subjected to the following rule:
|
||||
- 8-character minimum length
|
||||
- Contains at least 1 number
|
||||
- Contains at least 1 lowercase letter
|
||||
- Contains at least 1 uppercase letter
|
||||
- Contains at least 1 special character from the following set, or a
|
||||
non-leading, non-trailing space character: `^ $ * . [ ] { } ( ) ? - " ! @ # % & / \ , > < ' : ; | _ ~ ` + =
|
||||
|
||||
---
|
||||
|
||||
### Delete user account (for admin user)
|
||||
|
||||
**Description**: the admin user in the client's account can delete user account.
|
||||
Once an user account is deleted, he/she cannot login to Aurora Platform.
|
||||
|
||||
1. The admin user navigates to "Admin" > "Users".
|
||||
2. In the user list panel, next to the username, the admin click on the "Delete"
|
||||
button. The Confirmation dialog appears.
|
||||
3. If "Delete", the user account is deleted. If "Cancel", do nothing. The
|
||||
Confirmation dialog disappears.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- Once the user is deleted, the following information relating to the user will
|
||||
be deleted:
|
||||
- His/her personal setting.
|
||||
- His/her conversations.
|
||||
- The following information relating to the user will still be retained:
|
||||
- His/her uploaded files.
|
||||
|
||||
---
|
||||
|
||||
### Edit user account (for admin user)
|
||||
|
||||
**Description**: the admin user can change any information about the user
|
||||
account, including password. To change user information:
|
||||
|
||||
1. The admin user navigates to "Admin" > "Users".
|
||||
2. In the user list panel, next to the username, the admin click on the "Edit"
|
||||
button.
|
||||
3. The user list disappears, the user detail appears, with the following
|
||||
information show up:
|
||||
- Username: (prefilled the username)
|
||||
- Password: (blank)
|
||||
- Confirm password: (blank)
|
||||
4. The admin can edit any of the information, and click "Save" or "Cancel".
|
||||
- If "Save": the information will be updated to the database, or show
|
||||
error per Expectation below.
|
||||
- If "Cancel": skip.
|
||||
5. If Save success or Cancel, transfer back to the user list UI, where the user
|
||||
information is updated accordingly.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- If the "Password" & "Confirm password" are different from each other, show
|
||||
error: "Password mismatch".
|
||||
- If both "Password" & \*"Confirm password" are blank, don't change the user
|
||||
password.
|
||||
- If changing password, the password rule is subjected to the same rule when
|
||||
creating user.
|
||||
- It's possible to change username. If changing username, the target user has to
|
||||
use the new username.
|
||||
|
||||
---
|
||||
|
||||
### Sign-in
|
||||
|
||||
(3 man-days)
|
||||
|
||||
**Description**: the users can sign-in to Aurora Platform as follow:
|
||||
|
||||
1. User navigates to the URL.
|
||||
2. If the user is not logged in, the UI just shows the login screen.
|
||||
3. User types username & password.
|
||||
4. If correct, the user will proceed to normal working UI.
|
||||
5. If incorrect, the login screen shows text error.
|
||||
|
||||
---
|
||||
|
||||
### Sign-out
|
||||
|
||||
(1 man-day)
|
||||
|
||||
**Description**: the user can sign-out of Aurora Platform as follow:
|
||||
|
||||
1. User navigates to the Settings > User page.
|
||||
2. User click on logout.
|
||||
3. The user is signed out to the UI login screen.
|
||||
|
||||
**Expectation**: the user is completely signed out. Next time he/she uses the
|
||||
Aurora Platform, he/she has to login again.
|
||||
|
||||
---
|
||||
|
||||
### Change password
|
||||
|
||||
**Description**: the user can change their password as follow:
|
||||
|
||||
1. User navigates to the Settings > User page.
|
||||
2. In the change password section, the user provides these info and click
|
||||
Change:
|
||||
- Current password
|
||||
- New password
|
||||
- Confirm new password
|
||||
3. If changing successfully, then the password is changed. Otherwise, show the
|
||||
error on the UI.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- If changing password succeeds, next time they logout/login to the system, they
|
||||
can use the new password.
|
||||
- Password rule (Same as normal password rule when creating user)
|
||||
- Errors:
|
||||
- Password does not match.
|
||||
- Violated password rules.
|
||||
|
||||
---
|
||||
|
||||
## Chat
|
||||
|
||||
### Chat to the bot
|
||||
|
||||
**Description**: the Aurora Platform focuses on question and answering over the
|
||||
uploaded data. Each chat has the following components:
|
||||
|
||||
- Chat message: show the exchange between bots and humans.
|
||||
- Text input + send button: for the user to input the message.
|
||||
- Data source panel: for selecting the files that will scope the context for the
|
||||
bot.
|
||||
- Information panel: showing evidence as the bot answers user's questions.
|
||||
|
||||
The chat workflow looks as follow:
|
||||
|
||||
1. [Optional] User select files that they want to scope the context for the bot.
|
||||
If the user doesn't select any files, then all files on Aurora Platform will
|
||||
be the context for the bot.
|
||||
- The user can type multi-line messages, using "Shift + Enter" for
|
||||
line-break.
|
||||
2. User sends the message (either clicking the Send button or hitting the Enter
|
||||
key).
|
||||
3. The bot in the chat conversation will return "Thinking..." while it
|
||||
processes.
|
||||
4. The information panel on the right begin to show data related to the user
|
||||
message.
|
||||
5. The bot begins to generate answer. The "Thinking..." placeholder disappears..
|
||||
|
||||
**Expecatation**:
|
||||
|
||||
- Messages:
|
||||
- User can send multi-line messages, using "Shift + Enter" for line-break.
|
||||
- User can thumbs up, thumbs down the AI response. This information is
|
||||
recorded in the database.
|
||||
- User can click on a copy button on the chat message to copy the content to
|
||||
clipboard.
|
||||
- Information panel:
|
||||
- The information panel shows the latest evidence.
|
||||
- The user can click on the message, and the reference for that message will
|
||||
show up on the "Reference panel" (feature in-planning).
|
||||
- The user can click on the title to show/hide the content.
|
||||
- The whole information panel can be collapsed.
|
||||
- Chatbot quality:
|
||||
- The user can converse with the bot. The bot answer the user's requests in a
|
||||
natural manner.
|
||||
- The bot message should be streamed to the UI. The bot don't wait to gather
|
||||
alll the text response, then dump all of them at once.
|
||||
|
||||
### Conversation - switch
|
||||
|
||||
**Description**: users can jump around between different conversations. They can
|
||||
see the list of all conversations, can select an old converation, and continue
|
||||
the chat under the context of the old conversation. The switching workflow is
|
||||
like this:
|
||||
|
||||
1. Users click on the conversation dropdown. It will show a list of
|
||||
conversations.
|
||||
2. Within that dropdown, the user selects one conversation.
|
||||
3. The chat messages, information panel, and selected data will show the content
|
||||
in that old chat.
|
||||
4. The user can continue chatting as normal under the context of this old chat.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- In the conversation drop down list, the conversations are ordered in created
|
||||
date order.
|
||||
- When there is no conversation, the conversation list is empty.
|
||||
- When there is no conversation, the user can still converse with the chat bot.
|
||||
When doing so, it automatically create new conversation.
|
||||
|
||||
### Conversation - create
|
||||
|
||||
**Description**: the user can explicitly start a new conversation with the
|
||||
chatbot:
|
||||
|
||||
1. User click on the "New" button.
|
||||
2. The new conversation is automatically created.
|
||||
|
||||
**Expectation**:
|
||||
|
||||
- The default conversation name is the current datetime.
|
||||
- It become selected.
|
||||
- It is added to the conversation list.
|
||||
|
||||
### Conversation - rename
|
||||
|
||||
**Description**: user can rename the chatbot by typing the name, and click on
|
||||
the Rename button next to it.
|
||||
|
||||
- If rename succeeds: the name shown in the 1st dropdown will change accordingly
|
||||
- If rename doesn't succeed: show error message in red color below the rename section
|
||||
|
||||
**Condition**:
|
||||
|
||||
- Name constraint:
|
||||
- Min characters: 1
|
||||
- Max characters: 40
|
||||
- Could not having the same name with an existing conversation of the same
|
||||
user.
|
||||
|
||||
### Conversation - delete
|
||||
|
||||
**Description**: user can delete the existing conversation as follow:
|
||||
|
||||
1. Click on Delete button.
|
||||
2. The UI show confirmation with 2 buttons:
|
||||
- Delete
|
||||
- Cancel.
|
||||
3. If Delete, delete the conversation, switch to the next oldest conversation,
|
||||
close the confirmation panel.
|
||||
4. If cancel, just close the confirmation panel.
|
||||
|
||||
## File management
|
||||
|
||||
The file management allows users to upload, list and delete files that they
|
||||
upload to the Aurora Platform
|
||||
|
||||
### Upload file
|
||||
|
||||
**Description**: the user can upload files to the Aurora Platform. The uploaded
|
||||
files will be served as context for our chatbot to refer to when it converses
|
||||
with the user. To upload file, the user:
|
||||
|
||||
1. Navigate to the File tab.
|
||||
2. Within the File tab, there is an Upload section.
|
||||
3. User can add files to the Upload section through drag & drop, and or by click
|
||||
on the file browser.
|
||||
4. User can select some options relating to uploading and indexing. Depending on
|
||||
the project, these options can be different. Nevertheless, they will discuss
|
||||
below.
|
||||
5. User click on "Upload and Index" button.
|
||||
6. The app show notifications when indexing starts and finishes, and when errors
|
||||
happen on the top right corner.
|
||||
|
||||
**Options**:
|
||||
|
||||
- Force re-index file. When user tries to upload files that already exists on
|
||||
the system:
|
||||
- If this option is True: will re-index those files.
|
||||
- If this option is False: will skip indexing those files.
|
||||
|
||||
**Condition**:
|
||||
|
||||
- Max number of files: 100 files.
|
||||
- Max number of pages per file: 500 pages
|
||||
- Max file size: 10 MB
|
||||
|
||||
### List all files
|
||||
|
||||
**Description**: the user can know which files are on the system by:
|
||||
|
||||
1. Navigate to the File tab.
|
||||
2. By default, it will show all the uploaded files, each with the following
|
||||
information: file name, file size, number of pages, uploaded date
|
||||
3. The UI also shows total number of pages, and total number of sizes in MB.
|
||||
|
||||
### Delete file
|
||||
|
||||
**Description**: users can delete files from this UI to free up the space, or to
|
||||
remove outdated information. To remove the files:
|
||||
|
||||
1. User navigate to the File tab.
|
||||
2. In the list of file, next to each file, there is a Delete button.
|
||||
3. The user clicks on the Delete button. Confirmation dialog appear.
|
||||
4. If Delete, delete the file. If Cancel, close the confirmation dialog.
|
||||
|
||||
**Expectation**: once the file is deleted:
|
||||
|
||||
- The database entry of that file is deleted.
|
||||
- The file is removed from "Chat - Data source".
|
||||
- The total number of pages and MB sizes are reduced accordingly.
|
||||
- The reference to the file in the information panel is still retained.
|
||||
@@ -0,0 +1,115 @@
|
||||
The file index stores files in a local folder and index them for retrieval.
|
||||
This file index provides the following infrastructure to support the indexing:
|
||||
|
||||
- SQL table Source: store the list of files that are indexed by the system
|
||||
- Vector store: contain the embedding of segments of the files
|
||||
- Document store: contain the text of segments of the files. Each text stored
|
||||
in this document store is associated with a vector in the vector store.
|
||||
- SQL table Index: store the relationship between (1) the source and the
|
||||
docstore, and (2) the source and the vector store.
|
||||
|
||||
The indexing and retrieval pipelines are encouraged to use the above software
|
||||
infrastructure.
|
||||
|
||||
## Indexing pipeline
|
||||
|
||||
The ktem has default indexing pipeline: `ktem.index.file.pipelines.IndexDocumentPipeline`.
|
||||
|
||||
This default pipeline works as follow:
|
||||
|
||||
- **Input**: list of file paths
|
||||
- **Output**: list of nodes that are indexed into database
|
||||
- **Process**:
|
||||
- Read files into texts. Different file types has different ways to read texts.
|
||||
- Split text files into smaller segments
|
||||
- Run each segments into embeddings.
|
||||
- Store the embeddings into vector store. Store the texts of each segment
|
||||
into docstore. Store the list of files in Source. Store the linking
|
||||
between Sources and docstore + vectorstore in Index table.
|
||||
|
||||
You can customize this default pipeline if your indexing process is close to the
|
||||
default pipeline. You can create your own indexing pipeline if there are too
|
||||
much different logic.
|
||||
|
||||
### Customize the default pipeline
|
||||
|
||||
The default pipeline provides the contact points in `flowsettings.py`.
|
||||
|
||||
1. `FILE_INDEX_PIPELINE_FILE_EXTRACTORS`. Supply overriding file extractor,
|
||||
based on file extension. Example: `{".pdf": "path.to.PDFReader", ".xlsx": "path.to.ExcelReader"}`
|
||||
2. `FILE_INDEX_PIPELINE_SPLITTER_CHUNK_SIZE`. The expected number of characters
|
||||
of each text segment. Example: 1024.
|
||||
3. `FILE_INDEX_PIPELINE_SPLITTER_CHUNK_OVERLAP`. The expected number of
|
||||
characters that consecutive text segments should overlap with each other.
|
||||
Example: 256.
|
||||
|
||||
### Create your own indexing pipeline
|
||||
|
||||
Your indexing pipeline will subclass `BaseFileIndexIndexing`.
|
||||
|
||||
You should define the following methods:
|
||||
|
||||
- `run(self, file_paths)`: run the indexing given the pipeline
|
||||
- `get_pipeline(cls, user_settings, index_settings)`: return the
|
||||
fully-initialized pipeline, ready to be used by ktem.
|
||||
- `user_settings`: is a dictionary contains user settings (e.g. `{"pdf_mode": True, "num_retrieval": 5}`). You can declare these settings in the `get_user_settings` classmethod. ktem will collect these settings into the app Settings page, and will supply these user settings to your `get_pipeline` method.
|
||||
- `index_settings`: is a dictionary. Currently it's empty for File Index.
|
||||
- `get_user_settings`: to declare user settings, return a dictionary.
|
||||
|
||||
By subclassing `BaseFileIndexIndexing`, You will have access to the following resources:
|
||||
|
||||
- `self._Source`: the source table
|
||||
- `self._Index`: the index table
|
||||
- `self._VS`: the vector store
|
||||
- `self._DS`: the docstore
|
||||
|
||||
Once you have prepared your pipeline, register it in `flowsettings.py`: `FILE_INDEX_PIPELINE = "<python.path.to.your.pipeline>"`.
|
||||
|
||||
## Retrieval pipeline
|
||||
|
||||
The ktem has default retrieval pipeline:
|
||||
`ktem.index.file.pipelines.DocumentRetrievalPipeline`. This pipeline works as
|
||||
follow:
|
||||
|
||||
- Input: user text query & optionally a list of source file ids
|
||||
- Output: the output segments that match the user text query
|
||||
- Process:
|
||||
- If a list of source file ids is given, get the list of vector ids that
|
||||
associate with those file ids.
|
||||
- Embed the user text query.
|
||||
- Query the vector store. Provide a list of vector ids to limit query scope
|
||||
if the user restrict.
|
||||
- Return the matched text segments
|
||||
|
||||
### Create your own retrieval pipeline
|
||||
|
||||
Your retrieval pipeline will subclass `BaseFileIndexRetriever`. The retriever
|
||||
has the same database, vectorstore and docstore accesses like the indexing
|
||||
pipeline.
|
||||
|
||||
You should define the following methods:
|
||||
|
||||
- `run(self, query, file_ids)`: retrieve relevant documents relating to the
|
||||
query. If `file_ids` is given, you should restrict your search within these
|
||||
`file_ids`.
|
||||
- `get_pipeline(cls, user_settings, index_settings, selected)`: return the
|
||||
fully-initialized pipeline, ready to be used by ktem.
|
||||
- `user_settings`: is a dictionary contains user settings (e.g. `{"pdf_mode": True, "num_retrieval": 5}`). You can declare these settings in the `get_user_settings` classmethod. ktem will collect these settings into the app Settings page, and will supply these user settings to your `get_pipeline` method.
|
||||
- `index_settings`: is a dictionary. Currently it's empty for File Index.
|
||||
- `selected`: a list of file ids selected by user. If user doesn't select
|
||||
anything, this variable will be None.
|
||||
- `get_user_settings`: to declare user settings, return a dictionary.
|
||||
|
||||
Once you build the retrieval pipeline class, you can register it in
|
||||
`flowsettings.py`: `FILE_INDEXING_RETRIEVER_PIPELIENS = ["path.to.retrieval.pipelie"]`. Because there can be
|
||||
multiple parallel pipelines within an index, this variable takes a list of
|
||||
string rather than a string.
|
||||
|
||||
## Software infrastructure
|
||||
|
||||
| Infra | Access | Schema | Ref |
|
||||
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| SQL table Source | self.\_Source | - id (int): id of the source (auto)<br>- name (str): the name of the file<br>- path (str): the path of the file<br>- size (int): the file size in bytes<br>- note (dict): allow extra optional information about the file<br>- date_created (datetime): the time the file is created (auto) | This is SQLALchemy ORM class. Can consult |
|
||||
| SQL table Index | self.\_Index | - id (int): id of the index entry (auto)<br>- source_id (int): the id of a file in the Source table<br>- target_id: the id of the segment in docstore or vector store<br>- relation_type (str): if the link is "document" or "vector" | This is SQLAlchemy ORM class |
|
||||
| Vector store | self.\_VS | - self.\_VS.add: add the list of embeddings to the vector store (optionally associate metadata and ids)<br>- self.\_VS.delete: delete vector entries based on ids<br>- self.\_VS.query: get embeddings based on embeddings. | kotaemon > storages > vectorstores > BaseVectorStore |
|
||||
| Doc store | self.\_DS | - self.\_DS.add: add the segments to document stores<br>- self.\_DS.get: get the segments based on id<br>- self.\_DS.get_all: get all segments<br>- self.\_DS.delete: delete segments based on id | kotaemon > storages > docstores > base > BaseDocumentStore |
|
||||
@@ -0,0 +1,8 @@
|
||||
# Overview
|
||||
|
||||
There are 3 kinds of settings in `ktem`, geared towards different stakeholders
|
||||
for different use cases:
|
||||
|
||||
- Developer settings. These settings are meant for very basic app customization, such as database URL, cloud config, logging config, which features to enable... You will be interested in the developer settings if you deploy `ktem` to your customers, or if you build extension for `ktem` for developers. These settings are declared inside `flowsettings.py`.
|
||||
- Admin settings. These settings show up in the Admin page, and are meant to allow admin-level user to customize low level features, such as which credentials to connect to data sources, which keys to use for LLM...
|
||||
- [User settings](/pages/app/settings/user-settings/). These settings are meant for run-time users to tweak ktem to their personal needs, such as which output languages the chatbot should generate, which reasoning type to use...
|
||||
@@ -0,0 +1,52 @@
|
||||
# User settings
|
||||
|
||||
`ktem` allows developers to extend the index and the reasoning pipeline. In
|
||||
many cases, these components can have settings that should be modified by
|
||||
users at run-time, (e.g. `topk`, `chunksize`...). These are the user settings.
|
||||
|
||||
`ktem` allows developers to declare such user settings in their code. Once
|
||||
declared, `ktem` will render them in a Settings page.
|
||||
|
||||
There are 2 places that `ktem` looks for declared user settings. You can
|
||||
refer to the respective pages.
|
||||
|
||||
- In the index.
|
||||
- In the reasoning pipeline.
|
||||
|
||||
## Syntax of a settings
|
||||
|
||||
A collection of settings is a dictionary of type `dict[str, dict]`, where the
|
||||
key is a setting id, and the value is the description of the setting.
|
||||
|
||||
```python
|
||||
settings = {
|
||||
"topk": {
|
||||
"name": "Top-k chunks",
|
||||
"value": 10,
|
||||
"component": "number",
|
||||
},
|
||||
"lang": {
|
||||
"name": "Languages",
|
||||
"value": "en",
|
||||
"component": "dropdown",
|
||||
"choices": [("en", "English"), ("cn", "Chinese")],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each setting description must have:
|
||||
|
||||
- name: the human-understandable name of the settings.
|
||||
- value: the default value of the settings.
|
||||
- component: the UI component to render such setting on the UI. Available:
|
||||
|
||||
- "text": single-value
|
||||
- "number": single-value
|
||||
- "checkbox": single-value
|
||||
- "dropdown": choices
|
||||
- "radio": choices
|
||||
- "checkboxgroup": choices
|
||||
|
||||
- choices: the list of choices, if the component type allows.
|
||||
|
||||
## Settings page structure
|
||||
@@ -0,0 +1,65 @@
|
||||
# import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
import mkdocs_gen_files
|
||||
|
||||
# get the root source code directory
|
||||
doc_dir_name = "docs"
|
||||
doc_dir = Path(__file__)
|
||||
while doc_dir.name != doc_dir_name and doc_dir != doc_dir.parent:
|
||||
doc_dir = doc_dir.parent
|
||||
|
||||
if doc_dir == doc_dir.parent:
|
||||
raise ValueError(f"root_name ({doc_dir_name}) not in path ({str(Path(__file__))}).")
|
||||
|
||||
|
||||
def generate_docs_for_examples_readme(
|
||||
examples_dir: Path, target_doc_folder: str, ignored_modules: Iterable[Any] = []
|
||||
):
|
||||
if not examples_dir.is_dir():
|
||||
raise ModuleNotFoundError(str(examples_dir))
|
||||
|
||||
nav = mkdocs_gen_files.Nav()
|
||||
|
||||
for path in sorted(examples_dir.rglob("*README.md")):
|
||||
# ignore modules with name starts with underscore (i.e. __init__)
|
||||
if path.name.startswith("_") or path.name.startswith("test"):
|
||||
continue
|
||||
|
||||
module_path = path.parent.relative_to(examples_dir).with_suffix("")
|
||||
doc_path = path.parent.relative_to(examples_dir).with_suffix(".md")
|
||||
full_doc_path = Path(target_doc_folder, doc_path)
|
||||
|
||||
parts = list(module_path.parts)
|
||||
identifier = ".".join(parts)
|
||||
|
||||
if "tests" in parts:
|
||||
continue
|
||||
|
||||
ignore = False
|
||||
for each_module in ignored_modules:
|
||||
if identifier.startswith(each_module):
|
||||
ignore = True
|
||||
break
|
||||
if ignore:
|
||||
continue
|
||||
|
||||
nav_titles = [name.replace("_", " ").title() for name in parts]
|
||||
nav[nav_titles] = doc_path.as_posix()
|
||||
|
||||
with mkdocs_gen_files.open(full_doc_path, "w") as f:
|
||||
f.write(f'--8<-- "{path.relative_to(examples_dir.parent)}"')
|
||||
|
||||
mkdocs_gen_files.set_edit_path(
|
||||
full_doc_path, Path("..") / path.relative_to(examples_dir.parent)
|
||||
)
|
||||
|
||||
with mkdocs_gen_files.open(f"{target_doc_folder}/NAV.md", "w") as nav_file:
|
||||
nav_file.writelines(nav.build_literate_nav())
|
||||
|
||||
|
||||
generate_docs_for_examples_readme(
|
||||
examples_dir=doc_dir.parent / "examples",
|
||||
target_doc_folder="examples",
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
# import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
import mkdocs_gen_files
|
||||
|
||||
# get the root source code directory
|
||||
doc_dir_name = "docs"
|
||||
doc_dir = Path(__file__)
|
||||
while doc_dir.name != doc_dir_name and doc_dir != doc_dir.parent:
|
||||
doc_dir = doc_dir.parent
|
||||
|
||||
if doc_dir == doc_dir.parent:
|
||||
raise ValueError(f"root_name ({doc_dir_name}) not in path ({str(Path(__file__))}).")
|
||||
|
||||
nav_title_map = {"cli": "CLI", "llms": "LLMs"}
|
||||
|
||||
|
||||
def generate_docs_for_src_code(
|
||||
code_dir: Path, target_doc_folder: str, ignored_modules: Iterable[Any] = []
|
||||
):
|
||||
if not code_dir.is_dir():
|
||||
raise ModuleNotFoundError(str(code_dir))
|
||||
|
||||
nav = mkdocs_gen_files.Nav()
|
||||
|
||||
for path in sorted(code_dir.rglob("*.py")):
|
||||
# ignore modules with name starts with underscore (i.e. __init__)
|
||||
# if path.name.startswith("_") or path.name.startswith("test"):
|
||||
# continue
|
||||
|
||||
module_path = path.relative_to(code_dir).with_suffix("")
|
||||
doc_path = path.relative_to(code_dir).with_suffix(".md")
|
||||
full_doc_path = Path(target_doc_folder, doc_path)
|
||||
|
||||
parts = list(module_path.parts)
|
||||
|
||||
if parts[-1] == "__init__":
|
||||
doc_path = doc_path.with_name("index.md")
|
||||
full_doc_path = full_doc_path.with_name("index.md")
|
||||
parts.pop()
|
||||
|
||||
if not parts:
|
||||
continue
|
||||
|
||||
if "tests" in parts:
|
||||
continue
|
||||
|
||||
identifier = ".".join(parts)
|
||||
ignore = False
|
||||
for each_module in ignored_modules:
|
||||
if identifier.startswith(each_module):
|
||||
ignore = True
|
||||
break
|
||||
if ignore:
|
||||
continue
|
||||
|
||||
nav_titles = [
|
||||
nav_title_map.get(name, name.replace("_", " ").title()) for name in parts
|
||||
]
|
||||
nav[nav_titles] = doc_path.as_posix()
|
||||
|
||||
with mkdocs_gen_files.open(full_doc_path, "w") as f:
|
||||
f.write(f"::: {identifier}")
|
||||
|
||||
# this method works in docs folder
|
||||
mkdocs_gen_files.set_edit_path(
|
||||
full_doc_path, Path("..") / path.relative_to(code_dir.parent)
|
||||
)
|
||||
|
||||
with mkdocs_gen_files.open(f"{target_doc_folder}/Summary.md", "w") as nav_file:
|
||||
nav_file.writelines(nav.build_literate_nav())
|
||||
|
||||
|
||||
generate_docs_for_src_code(
|
||||
code_dir=doc_dir.parent / "libs" / "kotaemon" / "kotaemon",
|
||||
target_doc_folder="reference",
|
||||
ignored_modules={"contribs"},
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";var e;e=function(e){"true"===localStorage.getItem("data-md-prefers-color-scheme")&&document.querySelector("body").setAttribute("data-md-color-scheme",e.matches?"dracula":"default")},new MutationObserver((function(t){t.forEach((function(t){if("childList"===t.type&&t.addedNodes.length)for(var a=0;a<t.addedNodes.length;a++){var r=t.addedNodes[a];if(1===r.nodeType&&"body"===r.tagName.toLowerCase()){d=r,o=void 0,c=void 0,l=void 0,o="not all"!==window.matchMedia("(prefers-color-scheme)").media,c=localStorage.getItem("data-md-color-scheme"),l=localStorage.getItem("data-md-prefers-color-scheme"),c||(c="dracula"),l||(l="false"),"true"===l&&o?c=window.matchMedia("(prefers-color-scheme: dark)").matches?"dracula":"default":l="false",d.setAttribute("data-md-prefers-color-scheme",l),d.setAttribute("data-md-color-scheme",c),o&&window.matchMedia("(prefers-color-scheme: dark)").addListener(e);break}}var d,o,c,l}))})).observe(document.querySelector("html"),{childList:!0}),window.toggleScheme=function(){var e=document.querySelector("body"),t="not all"!==window.matchMedia("(prefers-color-scheme)").media,a=e.getAttribute("data-md-color-scheme"),r=e.getAttribute("data-md-prefers-color-scheme");t&&"default"===a&&"true"!==r?(r="true",a=window.matchMedia("(prefers-color-scheme: dark)").matches?"dracula":"default"):t&&"true"===r?(r="false",a="dracula"):"dracula"===a?(r="false",a="default"):(r="false",a="dracula"),localStorage.setItem("data-md-prefers-color-scheme",r),e.setAttribute("data-md-prefers-color-scheme",r),e.setAttribute("data-md-color-scheme",a)}}();
|
||||
//# sourceMappingURL=material-extra-theme-TVq-kNRT.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"material-extra-theme-TVq-kNRT.js","sources":["material-extra-theme.js"],"sourcesContent":["(() => {\n\n const preferToggle = e => {\n if (localStorage.getItem(\"data-md-prefers-color-scheme\") === \"true\") {\n document.querySelector(\"body\").setAttribute(\"data-md-color-scheme\", (e.matches) ? \"dracula\" : \"default\")\n }\n }\n\n const setupTheme = body => {\n const preferSupported = window.matchMedia(\"(prefers-color-scheme)\").media !== \"not all\"\n let scheme = localStorage.getItem(\"data-md-color-scheme\")\n let prefers = localStorage.getItem(\"data-md-prefers-color-scheme\")\n\n if (!scheme) {\n scheme = \"dracula\"\n }\n if (!prefers) {\n prefers = \"false\"\n }\n\n if (prefers === \"true\" && preferSupported) {\n scheme = (window.matchMedia(\"(prefers-color-scheme: dark)\").matches) ? \"dracula\" : \"default\"\n } else {\n prefers = \"false\"\n }\n\n body.setAttribute(\"data-md-prefers-color-scheme\", prefers)\n body.setAttribute(\"data-md-color-scheme\", scheme)\n\n if (preferSupported) {\n const matchListener = window.matchMedia(\"(prefers-color-scheme: dark)\")\n matchListener.addListener(preferToggle)\n }\n }\n\n const observer = new MutationObserver(mutations => {\n mutations.forEach(mutation => {\n if (mutation.type === \"childList\") {\n if (mutation.addedNodes.length) {\n for (let i = 0; i < mutation.addedNodes.length; i++) {\n const el = mutation.addedNodes[i]\n\n if (el.nodeType === 1 && el.tagName.toLowerCase() === \"body\") {\n setupTheme(el)\n break\n }\n }\n }\n }\n })\n })\n\n observer.observe(document.querySelector(\"html\"), {childList: true})\n})()\n\nwindow.toggleScheme = () => {\n const body = document.querySelector(\"body\")\n const preferSupported = window.matchMedia(\"(prefers-color-scheme)\").media !== \"not all\"\n let scheme = body.getAttribute(\"data-md-color-scheme\")\n let prefer = body.getAttribute(\"data-md-prefers-color-scheme\")\n\n if (preferSupported && scheme === \"default\" && prefer !== \"true\") {\n prefer = \"true\"\n scheme = (window.matchMedia(\"(prefers-color-scheme: dark)\").matches) ? \"dracula\" : \"default\"\n } else if (preferSupported && prefer === \"true\") {\n prefer = \"false\"\n scheme = \"dracula\"\n } else if (scheme === \"dracula\") {\n prefer = \"false\"\n scheme = \"default\"\n } else {\n prefer = \"false\"\n scheme = \"dracula\"\n }\n localStorage.setItem(\"data-md-prefers-color-scheme\", prefer)\n body.setAttribute(\"data-md-prefers-color-scheme\", prefer)\n body.setAttribute(\"data-md-color-scheme\", scheme)\n}\n"],"names":["preferToggle","e","localStorage","getItem","document","querySelector","setAttribute","matches","MutationObserver","mutations","forEach","mutation","type","addedNodes","length","i","el","nodeType","tagName","toLowerCase","body","preferSupported","scheme","prefers","window","matchMedia","media","addListener","observe","childList","toggleScheme","getAttribute","prefer","setItem"],"mappings":"yBAAA,IAEQA,IAAe,SAAAC,GAC0C,SAAzDC,aAAaC,QAAQ,iCACvBC,SAASC,cAAc,QAAQC,aAAa,uBAAyBL,EAAEM,QAAW,UAAY,YA+BjF,IAAIC,kBAAiB,SAAAC,GACpCA,EAAUC,SAAQ,SAAAC,GAChB,GAAsB,cAAlBA,EAASC,MACPD,EAASE,WAAWC,OACtB,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,EAASE,WAAWC,OAAQC,IAAK,CACnD,IAAMC,EAAKL,EAASE,WAAWE,GAE/B,GAAoB,IAAhBC,EAAGC,UAA+C,SAA7BD,EAAGE,QAAQC,cAA0B,CAlCrDC,EAmCIJ,EAlCfK,SACFC,SACAC,SAFEF,EAAwE,YAAtDG,OAAOC,WAAW,0BAA0BC,MAChEJ,EAASpB,aAAaC,QAAQ,wBAC9BoB,EAAUrB,aAAaC,QAAQ,gCAE9BmB,IACHA,EAAS,WAENC,IACHA,EAAU,SAGI,SAAZA,GAAsBF,EACxBC,EAAUE,OAAOC,WAAW,gCAAgClB,QAAW,UAAY,UAEnFgB,EAAU,QAGZH,EAAKd,aAAa,+BAAgCiB,GAClDH,EAAKd,aAAa,uBAAwBgB,GAEtCD,GACoBG,OAAOC,WAAW,gCAC1BE,YAAY3B,GAalB,KACF,CACF,CAtCW,IAAAoB,EACXC,EACFC,EACAC,CAsCJ,GACF,IAESK,QAAQxB,SAASC,cAAc,QAAS,CAACwB,WAAW,IAG/DL,OAAOM,aAAe,WACpB,IAAMV,EAAOhB,SAASC,cAAc,QAC9BgB,EAAwE,YAAtDG,OAAOC,WAAW,0BAA0BC,MAChEJ,EAASF,EAAKW,aAAa,wBAC3BC,EAASZ,EAAKW,aAAa,gCAE3BV,GAA8B,YAAXC,GAAmC,SAAXU,GAC7CA,EAAS,OACTV,EAAUE,OAAOC,WAAW,gCAAgClB,QAAW,UAAY,WAC1Ec,GAA8B,SAAXW,GAC5BA,EAAS,QACTV,EAAS,WACW,YAAXA,GACTU,EAAS,QACTV,EAAS,YAETU,EAAS,QACTV,EAAS,WAEXpB,aAAa+B,QAAQ,+BAAgCD,GACrDZ,EAAKd,aAAa,+BAAgC0B,GAClDZ,EAAKd,aAAa,uBAAwBgB,EAC5C"}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block libs %}
|
||||
{{ super() }}
|
||||
{% include "partials/libs.html" ignore missing %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
{% import "partials/language.html" as lang with context %}
|
||||
<footer class="md-footer">
|
||||
{% if page.previous_page or page.next_page %}
|
||||
<nav
|
||||
class="md-footer__inner md-grid"
|
||||
aria-label="{{ lang.t('footer.title') }}"
|
||||
>
|
||||
{% if page.previous_page %}
|
||||
<a
|
||||
href="{{ page.previous_page.url | url }}"
|
||||
class="md-footer__link md-footer__link--prev"
|
||||
rel="prev"
|
||||
>
|
||||
<div class="md-footer__button md-icon">
|
||||
{% include ".icons/material/arrow-left.svg" %}
|
||||
</div>
|
||||
<div class="md-footer__title">
|
||||
<div class="md-ellipsis">
|
||||
<span class="md-footer__direction">
|
||||
{{ lang.t("footer.previous") }}
|
||||
</span>
|
||||
{{ page.previous_page.title }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if page.next_page %}
|
||||
<a
|
||||
href="{{ page.next_page.url | url }}"
|
||||
class="md-footer__link md-footer__link--next"
|
||||
rel="next"
|
||||
>
|
||||
<div class="md-footer__title">
|
||||
<div class="md-ellipsis">
|
||||
<span class="md-footer__direction">
|
||||
{{ lang.t("footer.next") }}
|
||||
</span>
|
||||
{{ page.next_page.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-footer__button md-icon">
|
||||
{% include ".icons/material/arrow-right.svg" %}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
</footer>
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
{% set site_url = config.site_url | d(nav.homepage.url, true) | url %}
|
||||
{% if not config.use_directory_urls and site_url[0] == site_url[-1] == "." %}
|
||||
{% set site_url = site_url ~ "/index.html" %}
|
||||
{% endif %}
|
||||
<header class="md-header" data-md-component="header">
|
||||
<nav
|
||||
class="md-header__inner md-grid"
|
||||
aria-label="{{ lang.t('header.title') }}"
|
||||
>
|
||||
<a
|
||||
href="{{ site_url }}"
|
||||
title="{{ config.site_name | e }}"
|
||||
class="md-header__button md-logo"
|
||||
aria-label="{{ config.site_name }}"
|
||||
>
|
||||
{% include "partials/logo.html" %}
|
||||
</a>
|
||||
<label class="md-header__button md-icon" for="__drawer">
|
||||
{% include ".icons/material/menu" ~ ".svg" %}
|
||||
</label>
|
||||
<div class="md-header__title" data-md-component="header-title">
|
||||
<div class="md-header__ellipsis">
|
||||
<div class="md-header__topic">
|
||||
<span class="md-ellipsis">
|
||||
{{ config.site_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
{% if page and page.meta and page.meta.title %}
|
||||
{{ page.meta.title }}
|
||||
{% else %}
|
||||
{{ page.title }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md-header__options">
|
||||
<div class="md-header-nav__scheme md-header-nav__button md-source__icon md-icon">
|
||||
<a
|
||||
href="javascript:toggleScheme();"
|
||||
title="Light mode"
|
||||
class="light-mode"
|
||||
>
|
||||
{% set icon = "material/weather-sunny" %}
|
||||
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:toggleScheme();"
|
||||
title="Dark mode"
|
||||
class="dark-mode"
|
||||
>
|
||||
{% set icon = "material/weather-night" %}
|
||||
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||
</a>
|
||||
<a
|
||||
href="javascript:toggleScheme();"
|
||||
title="System preference"
|
||||
class="system-mode"
|
||||
>
|
||||
{% set icon = "material/theme-light-dark" %}
|
||||
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||
</a>
|
||||
<!-- <a
|
||||
href="javascript:toggleScheme();"
|
||||
title="Unknown scheme"
|
||||
class="unknown-mode"
|
||||
>
|
||||
{% set icon = "material/help-circle" %}
|
||||
{% include ".icons/" ~ icon ~ ".svg" %}
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
{% if "material/search" in config.plugins %}
|
||||
<label class="md-header__button md-icon" for="__search">
|
||||
{% include ".icons/material/magnify.svg" %}
|
||||
</label>
|
||||
{% include "partials/search.html" %}
|
||||
{% endif %}
|
||||
{% if config.repo_url %}
|
||||
<div class="md-header__source">
|
||||
{% include "partials/source.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,2 @@
|
||||
<script src="{{ 'assets/pymdownx-extras/material-extra-theme-TVq-kNRT.js' | url }}" type="text/javascript"></script>
|
||||
<script src="{{ 'assets/pymdownx-extras/material-extra-3rdparty-E-i8w1WA.js' | url }}" type="text/javascript"></script>
|
||||
@@ -0,0 +1,157 @@
|
||||
## 1. Add your AI models
|
||||
|
||||

|
||||
|
||||
- The tool uses Large Language Model (LLMs) to perform various tasks in a QA pipeline.
|
||||
So, you need to provide the application with access to the LLMs you want
|
||||
to use.
|
||||
- You only need to provide at least one. However, it is recommended that you include all the LLMs
|
||||
that you have access to, you will be able to switch between them while using the
|
||||
application.
|
||||
|
||||
To add a model:
|
||||
|
||||
1. Navigate to the `Resources` tab.
|
||||
2. Select the `LLMs` sub-tab.
|
||||
3. Select the `Add` sub-tab.
|
||||
4. Config the model to add:
|
||||
- Give it a name.
|
||||
- Pick a vendor/provider (e.g. `ChatOpenAI`).
|
||||
- Provide the specifications.
|
||||
- (Optional) Set the model as default.
|
||||
5. Click `Add` to add the model.
|
||||
6. Select `Embedding Models` sub-tab and repeat the step 3 to 5 to add an embedding model.
|
||||
|
||||
<details markdown>
|
||||
|
||||
<summary>(Optional) Configure model via the .env file</summary>
|
||||
|
||||
Alternatively, you can configure the models via the `.env` file with the information needed to connect to the LLMs. This file is located in
|
||||
the folder of the application. If you don't see it, you can create one.
|
||||
|
||||
Currently, the following providers are supported:
|
||||
|
||||
### OpenAI
|
||||
|
||||
In the `.env` file, set the `OPENAI_API_KEY` variable with your OpenAI API key in order
|
||||
to enable access to OpenAI's models. There are other variables that can be modified,
|
||||
please feel free to edit them to fit your case. Otherwise, the default parameter should
|
||||
work for most people.
|
||||
|
||||
```shell
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_API_KEY=<your OpenAI API key here>
|
||||
OPENAI_CHAT_MODEL=gpt-3.5-turbo
|
||||
OPENAI_EMBEDDINGS_MODEL=text-embedding-ada-002
|
||||
```
|
||||
|
||||
### Azure OpenAI
|
||||
|
||||
For OpenAI models via Azure platform, you need to provide your Azure endpoint and API
|
||||
key. You might also need to provide your developments' name for the chat model and the
|
||||
embedding model depending on how you set up Azure development.
|
||||
|
||||
```shell
|
||||
AZURE_OPENAI_ENDPOINT=
|
||||
AZURE_OPENAI_API_KEY=
|
||||
OPENAI_API_VERSION=2024-02-15-preview # could be different for you
|
||||
AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo # change to your deployment name
|
||||
AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT=text-embedding-ada-002 # change to your deployment name
|
||||
```
|
||||
|
||||
### Local models
|
||||
|
||||
Pros:
|
||||
|
||||
- Privacy. Your documents will be stored and process locally.
|
||||
- Choices. There are a wide range of LLMs in terms of size, domain, language to choose
|
||||
from.
|
||||
- Cost. It's free.
|
||||
|
||||
Cons:
|
||||
|
||||
- Quality. Local models are much smaller and thus have lower generative quality than
|
||||
paid APIs.
|
||||
- Speed. Local models are deployed using your machine so the processing speed is
|
||||
limited by your hardware.
|
||||
|
||||
#### Find and download a LLM
|
||||
|
||||
You can search and download a LLM to be ran locally from the [Hugging Face
|
||||
Hub](https://huggingface.co/models). Currently, these model formats are supported:
|
||||
|
||||
- GGUF
|
||||
|
||||
You should choose a model whose size is less than your device's memory and should leave
|
||||
about 2 GB. For example, if you have 16 GB of RAM in total, of which 12 GB is available,
|
||||
then you should choose a model that take up at most 10 GB of RAM. Bigger models tend to
|
||||
give better generation but also take more processing time.
|
||||
|
||||
Here are some recommendations and their size in memory:
|
||||
|
||||
- [Qwen1.5-1.8B-Chat-GGUF](https://huggingface.co/Qwen/Qwen1.5-1.8B-Chat-GGUF/resolve/main/qwen1_5-1_8b-chat-q8_0.gguf?download=true):
|
||||
around 2 GB
|
||||
|
||||
#### Enable local models
|
||||
|
||||
To add a local model to the model pool, set the `LOCAL_MODEL` variable in the `.env`
|
||||
file to the path of the model file.
|
||||
|
||||
```shell
|
||||
LOCAL_MODEL=<full path to your model file>
|
||||
```
|
||||
|
||||
Here is how to get the full path of your model file:
|
||||
|
||||
- On Windows 11: right click the file and select `Copy as Path`.
|
||||
</details>
|
||||
|
||||
## 2. Upload your documents
|
||||
|
||||

|
||||
|
||||
In order to do QA on your documents, you need to upload them to the application first.
|
||||
Navigate to the `File Index` tab and you will see 2 sections:
|
||||
|
||||
1. File upload:
|
||||
- Drag and drop your file to the UI or select it from your file system.
|
||||
Then click `Upload and Index`.
|
||||
- The application will take some time to process the file and show a message once it is done.
|
||||
2. File list:
|
||||
- This section shows the list of files that have been uploaded to the application and allows users to delete them.
|
||||
|
||||
## 3. Chat with your documents
|
||||
|
||||

|
||||
|
||||
Now navigate back to the `Chat` tab. The chat tab is divided into 3 regions:
|
||||
|
||||
1. Conversation Settings Panel
|
||||
- Here you can select, create, rename, and delete conversations.
|
||||
- By default, a new conversation is created automatically if no conversation is selected.
|
||||
- Below that you have the file index, where you can choose whether to disable, select all files, or select which files to retrieve references from.
|
||||
- If you choose "Disabled", no files will be considered as context during chat.
|
||||
- If you choose "Search All", all files will be considered during chat.
|
||||
- If you choose "Select", a dropdown will appear for you to select the
|
||||
files to be considered during chat. If no files are selected, then no
|
||||
files will be considered during chat.
|
||||
2. Chat Panel
|
||||
- This is where you can chat with the chatbot.
|
||||
3. Information Panel
|
||||
|
||||

|
||||
|
||||
- Supporting information such as the retrieved evidence and reference will be
|
||||
displayed here.
|
||||
- Direct citation for the answer produced by the LLM is highlighted.
|
||||
- The confidence score of the answer and relevant scores of evidences are displayed to quickly assess the quality of the answer and retrieved content.
|
||||
|
||||
- Meaning of the score displayed:
|
||||
- **Answer confidence**: answer confidence level from the LLM model.
|
||||
- **Relevance score**: overall relevant score between evidence and user question.
|
||||
- **Vectorstore score**: relevant score from vector embedding similarity calculation (show `full-text search` if retrieved from full-text search DB).
|
||||
- **LLM relevant score**: relevant score from LLM model (which judge relevancy between question and evidence using specific prompt).
|
||||
- **Reranking score**: relevant score from Cohere [reranking model](https://cohere.com/rerank).
|
||||
|
||||
Generally, the score quality is `LLM relevant score` > `Reranking score` > `Vectorscore`.
|
||||
By default, overall relevance score is taken directly from LLM relevant score. Evidences are sorted based on their overall relevance score and whether they have citation or not.
|
||||
@@ -0,0 +1,404 @@
|
||||
import os
|
||||
from importlib.metadata import version
|
||||
from inspect import currentframe, getframeinfo
|
||||
from pathlib import Path
|
||||
|
||||
from decouple import config
|
||||
from ktem.utils.lang import SUPPORTED_LANGUAGE_MAP
|
||||
from theflow.settings.default import * # noqa
|
||||
|
||||
cur_frame = currentframe()
|
||||
if cur_frame is None:
|
||||
raise ValueError("Cannot get the current frame.")
|
||||
this_file = getframeinfo(cur_frame).filename
|
||||
this_dir = Path(this_file).parent
|
||||
|
||||
# change this if your app use a different name
|
||||
KH_PACKAGE_NAME = "kotaemon_app"
|
||||
|
||||
KH_APP_VERSION = config("KH_APP_VERSION", None)
|
||||
if not KH_APP_VERSION:
|
||||
try:
|
||||
# Caution: This might produce the wrong version
|
||||
# https://stackoverflow.com/a/59533071
|
||||
KH_APP_VERSION = version(KH_PACKAGE_NAME)
|
||||
except Exception:
|
||||
KH_APP_VERSION = "local"
|
||||
|
||||
KH_GRADIO_SHARE = config("KH_GRADIO_SHARE", default=False, cast=bool)
|
||||
KH_ENABLE_FIRST_SETUP = config("KH_ENABLE_FIRST_SETUP", default=True, cast=bool)
|
||||
KH_DEMO_MODE = config("KH_DEMO_MODE", default=False, cast=bool)
|
||||
KH_OLLAMA_URL = config("KH_OLLAMA_URL", default="http://localhost:11434/v1/")
|
||||
|
||||
# App can be ran from anywhere and it's not trivial to decide where to store app data.
|
||||
# So let's use the same directory as the flowsetting.py file.
|
||||
KH_APP_DATA_DIR = this_dir / "ktem_app_data"
|
||||
KH_APP_DATA_EXISTS = KH_APP_DATA_DIR.exists()
|
||||
KH_APP_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# User data directory
|
||||
KH_USER_DATA_DIR = KH_APP_DATA_DIR / "user_data"
|
||||
KH_USER_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# markdown output directory
|
||||
KH_MARKDOWN_OUTPUT_DIR = KH_APP_DATA_DIR / "markdown_cache_dir"
|
||||
KH_MARKDOWN_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# chunks output directory
|
||||
KH_CHUNKS_OUTPUT_DIR = KH_APP_DATA_DIR / "chunks_cache_dir"
|
||||
KH_CHUNKS_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# zip output directory
|
||||
KH_ZIP_OUTPUT_DIR = KH_APP_DATA_DIR / "zip_cache_dir"
|
||||
KH_ZIP_OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# zip input directory
|
||||
KH_ZIP_INPUT_DIR = KH_APP_DATA_DIR / "zip_cache_dir_in"
|
||||
KH_ZIP_INPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# HF models can be big, let's store them in the app data directory so that it's easier
|
||||
# for users to manage their storage.
|
||||
# ref: https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache
|
||||
os.environ["HF_HOME"] = str(KH_APP_DATA_DIR / "huggingface")
|
||||
os.environ["HF_HUB_CACHE"] = str(KH_APP_DATA_DIR / "huggingface")
|
||||
|
||||
# doc directory
|
||||
KH_DOC_DIR = this_dir / "docs"
|
||||
|
||||
KH_MODE = "dev"
|
||||
KH_SSO_ENABLED = config("KH_SSO_ENABLED", default=False, cast=bool)
|
||||
|
||||
KH_FEATURE_CHAT_SUGGESTION = config(
|
||||
"KH_FEATURE_CHAT_SUGGESTION", default=False, cast=bool
|
||||
)
|
||||
KH_FEATURE_USER_MANAGEMENT = config(
|
||||
"KH_FEATURE_USER_MANAGEMENT", default=True, cast=bool
|
||||
)
|
||||
KH_USER_CAN_SEE_PUBLIC = None
|
||||
KH_FEATURE_USER_MANAGEMENT_ADMIN = str(
|
||||
config("KH_FEATURE_USER_MANAGEMENT_ADMIN", default="admin")
|
||||
)
|
||||
KH_FEATURE_USER_MANAGEMENT_PASSWORD = str(
|
||||
config("KH_FEATURE_USER_MANAGEMENT_PASSWORD", default="admin")
|
||||
)
|
||||
KH_ENABLE_ALEMBIC = False
|
||||
KH_DATABASE = f"sqlite:///{KH_USER_DATA_DIR / 'sql.db'}"
|
||||
KH_FILESTORAGE_PATH = str(KH_USER_DATA_DIR / "files")
|
||||
KH_WEB_SEARCH_BACKEND = (
|
||||
"kotaemon.indices.retrievers.tavily_web_search.WebSearch"
|
||||
# "kotaemon.indices.retrievers.jina_web_search.WebSearch"
|
||||
)
|
||||
|
||||
KH_DOCSTORE = {
|
||||
# "__type__": "kotaemon.storages.ElasticsearchDocumentStore",
|
||||
# "__type__": "kotaemon.storages.SimpleFileDocumentStore",
|
||||
"__type__": "kotaemon.storages.LanceDBDocumentStore",
|
||||
"path": str(KH_USER_DATA_DIR / "docstore"),
|
||||
}
|
||||
KH_VECTORSTORE = {
|
||||
# "__type__": "kotaemon.storages.LanceDBVectorStore",
|
||||
"__type__": "kotaemon.storages.ChromaVectorStore",
|
||||
# "__type__": "kotaemon.storages.MilvusVectorStore",
|
||||
# "__type__": "kotaemon.storages.QdrantVectorStore",
|
||||
"path": str(KH_USER_DATA_DIR / "vectorstore"),
|
||||
}
|
||||
KH_LLMS = {}
|
||||
KH_EMBEDDINGS = {}
|
||||
KH_RERANKINGS = {}
|
||||
|
||||
# populate options from config
|
||||
if config("AZURE_OPENAI_API_KEY", default="") and config(
|
||||
"AZURE_OPENAI_ENDPOINT", default=""
|
||||
):
|
||||
if config("AZURE_OPENAI_CHAT_DEPLOYMENT", default=""):
|
||||
KH_LLMS["azure"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.AzureChatOpenAI",
|
||||
"temperature": 0,
|
||||
"azure_endpoint": config("AZURE_OPENAI_ENDPOINT", default=""),
|
||||
"api_key": config("AZURE_OPENAI_API_KEY", default=""),
|
||||
"api_version": config("OPENAI_API_VERSION", default="")
|
||||
or "2024-02-15-preview",
|
||||
"azure_deployment": config("AZURE_OPENAI_CHAT_DEPLOYMENT", default=""),
|
||||
"timeout": 20,
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
if config("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT", default=""):
|
||||
KH_EMBEDDINGS["azure"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.AzureOpenAIEmbeddings",
|
||||
"azure_endpoint": config("AZURE_OPENAI_ENDPOINT", default=""),
|
||||
"api_key": config("AZURE_OPENAI_API_KEY", default=""),
|
||||
"api_version": config("OPENAI_API_VERSION", default="")
|
||||
or "2024-02-15-preview",
|
||||
"azure_deployment": config(
|
||||
"AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT", default=""
|
||||
),
|
||||
"timeout": 10,
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
|
||||
OPENAI_DEFAULT = "<YOUR_OPENAI_KEY>"
|
||||
OPENAI_API_KEY = config("OPENAI_API_KEY", default=OPENAI_DEFAULT)
|
||||
GOOGLE_API_KEY = config("GOOGLE_API_KEY", default="your-key")
|
||||
IS_OPENAI_DEFAULT = len(OPENAI_API_KEY) > 0 and OPENAI_API_KEY != OPENAI_DEFAULT
|
||||
|
||||
if OPENAI_API_KEY:
|
||||
KH_LLMS["openai"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.ChatOpenAI",
|
||||
"temperature": 0,
|
||||
"base_url": config("OPENAI_API_BASE", default="")
|
||||
or "https://api.openai.com/v1",
|
||||
"api_key": OPENAI_API_KEY,
|
||||
"model": config("OPENAI_CHAT_MODEL", default="gpt-4o-mini"),
|
||||
"timeout": 20,
|
||||
},
|
||||
"default": IS_OPENAI_DEFAULT,
|
||||
}
|
||||
KH_EMBEDDINGS["openai"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.OpenAIEmbeddings",
|
||||
"base_url": config("OPENAI_API_BASE", default="https://api.openai.com/v1"),
|
||||
"api_key": OPENAI_API_KEY,
|
||||
"model": config(
|
||||
"OPENAI_EMBEDDINGS_MODEL", default="text-embedding-3-large"
|
||||
),
|
||||
"timeout": 10,
|
||||
"context_length": 8191,
|
||||
},
|
||||
"default": IS_OPENAI_DEFAULT,
|
||||
}
|
||||
|
||||
VOYAGE_API_KEY = config("VOYAGE_API_KEY", default="")
|
||||
if VOYAGE_API_KEY:
|
||||
KH_EMBEDDINGS["voyageai"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.VoyageAIEmbeddings",
|
||||
"api_key": VOYAGE_API_KEY,
|
||||
"model": config("VOYAGE_EMBEDDINGS_MODEL", default="voyage-3-large"),
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_RERANKINGS["voyageai"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.rerankings.VoyageAIReranking",
|
||||
"model_name": "rerank-2",
|
||||
"api_key": VOYAGE_API_KEY,
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
|
||||
if config("LOCAL_MODEL", default=""):
|
||||
KH_LLMS["ollama"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.ChatOpenAI",
|
||||
"base_url": KH_OLLAMA_URL,
|
||||
"model": config("LOCAL_MODEL", default="qwen2.5:7b"),
|
||||
"api_key": "ollama",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_LLMS["ollama-long-context"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.LCOllamaChat",
|
||||
"base_url": KH_OLLAMA_URL.replace("v1/", ""),
|
||||
"model": config("LOCAL_MODEL", default="qwen2.5:7b"),
|
||||
"num_ctx": 8192,
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
|
||||
KH_EMBEDDINGS["ollama"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.OpenAIEmbeddings",
|
||||
"base_url": KH_OLLAMA_URL,
|
||||
"model": config("LOCAL_MODEL_EMBEDDINGS", default="nomic-embed-text"),
|
||||
"api_key": "ollama",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_EMBEDDINGS["fast_embed"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.FastEmbedEmbeddings",
|
||||
"model_name": "BAAI/bge-base-en-v1.5",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
|
||||
# additional LLM configurations
|
||||
KH_LLMS["claude"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.chats.LCAnthropicChat",
|
||||
"model_name": "claude-3-5-sonnet-20240620",
|
||||
"api_key": "your-key",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_LLMS["google"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.chats.LCGeminiChat",
|
||||
"model_name": "gemini-1.5-flash",
|
||||
"api_key": GOOGLE_API_KEY,
|
||||
},
|
||||
"default": not IS_OPENAI_DEFAULT,
|
||||
}
|
||||
KH_LLMS["groq"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.ChatOpenAI",
|
||||
"base_url": "https://api.groq.com/openai/v1",
|
||||
"model": "llama-3.1-8b-instant",
|
||||
"api_key": "your-key",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_LLMS["cohere"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.chats.LCCohereChat",
|
||||
"model_name": "command-r-plus-08-2024",
|
||||
"api_key": config("COHERE_API_KEY", default="your-key"),
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_LLMS["mistral"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.llms.ChatOpenAI",
|
||||
"base_url": "https://api.mistral.ai/v1",
|
||||
"model": "ministral-8b-latest",
|
||||
"api_key": config("MISTRAL_API_KEY", default="your-key"),
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
|
||||
# additional embeddings configurations
|
||||
KH_EMBEDDINGS["cohere"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.LCCohereEmbeddings",
|
||||
"model": "embed-multilingual-v3.0",
|
||||
"cohere_api_key": config("COHERE_API_KEY", default="your-key"),
|
||||
"user_agent": "default",
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
KH_EMBEDDINGS["google"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.LCGoogleEmbeddings",
|
||||
"model": "models/text-embedding-004",
|
||||
"google_api_key": GOOGLE_API_KEY,
|
||||
},
|
||||
"default": not IS_OPENAI_DEFAULT,
|
||||
}
|
||||
KH_EMBEDDINGS["mistral"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.embeddings.LCMistralEmbeddings",
|
||||
"model": "mistral-embed",
|
||||
"api_key": config("MISTRAL_API_KEY", default="your-key"),
|
||||
},
|
||||
"default": False,
|
||||
}
|
||||
# KH_EMBEDDINGS["huggingface"] = {
|
||||
# "spec": {
|
||||
# "__type__": "kotaemon.embeddings.LCHuggingFaceEmbeddings",
|
||||
# "model_name": "sentence-transformers/all-mpnet-base-v2",
|
||||
# },
|
||||
# "default": False,
|
||||
# }
|
||||
|
||||
# default reranking models
|
||||
KH_RERANKINGS["cohere"] = {
|
||||
"spec": {
|
||||
"__type__": "kotaemon.rerankings.CohereReranking",
|
||||
"model_name": "rerank-v4.0-fast",
|
||||
"cohere_api_key": config("COHERE_API_KEY", default=""),
|
||||
},
|
||||
"default": True,
|
||||
}
|
||||
|
||||
KH_REASONINGS = [
|
||||
"ktem.reasoning.simple.FullQAPipeline",
|
||||
"ktem.reasoning.simple.FullDecomposeQAPipeline",
|
||||
"ktem.reasoning.react.ReactAgentPipeline",
|
||||
"ktem.reasoning.rewoo.RewooAgentPipeline",
|
||||
]
|
||||
KH_REASONINGS_USE_MULTIMODAL = config("USE_MULTIMODAL", default=False, cast=bool)
|
||||
KH_VLM_ENDPOINT = "{0}/openai/deployments/{1}/chat/completions?api-version={2}".format(
|
||||
config("AZURE_OPENAI_ENDPOINT", default=""),
|
||||
config("OPENAI_VISION_DEPLOYMENT_NAME", default="gpt-4o"),
|
||||
config("OPENAI_API_VERSION", default=""),
|
||||
)
|
||||
|
||||
|
||||
SETTINGS_APP: dict[str, dict] = {}
|
||||
|
||||
|
||||
SETTINGS_REASONING = {
|
||||
"use": {
|
||||
"name": "Reasoning options",
|
||||
"value": None,
|
||||
"choices": [],
|
||||
"component": "radio",
|
||||
},
|
||||
"lang": {
|
||||
"name": "Language",
|
||||
"value": "en",
|
||||
"choices": [(lang, code) for code, lang in SUPPORTED_LANGUAGE_MAP.items()],
|
||||
"component": "dropdown",
|
||||
},
|
||||
"max_context_length": {
|
||||
"name": "Max context length (LLM)",
|
||||
"value": 32000,
|
||||
"component": "number",
|
||||
},
|
||||
}
|
||||
|
||||
USE_GLOBAL_GRAPHRAG = config("USE_GLOBAL_GRAPHRAG", default=True, cast=bool)
|
||||
USE_NANO_GRAPHRAG = config("USE_NANO_GRAPHRAG", default=False, cast=bool)
|
||||
USE_LIGHTRAG = config("USE_LIGHTRAG", default=True, cast=bool)
|
||||
USE_MS_GRAPHRAG = config("USE_MS_GRAPHRAG", default=True, cast=bool)
|
||||
|
||||
GRAPHRAG_INDEX_TYPES = []
|
||||
|
||||
if USE_MS_GRAPHRAG:
|
||||
GRAPHRAG_INDEX_TYPES.append("ktem.index.file.graph.GraphRAGIndex")
|
||||
if USE_NANO_GRAPHRAG:
|
||||
GRAPHRAG_INDEX_TYPES.append("ktem.index.file.graph.NanoGraphRAGIndex")
|
||||
if USE_LIGHTRAG:
|
||||
GRAPHRAG_INDEX_TYPES.append("ktem.index.file.graph.LightRAGIndex")
|
||||
|
||||
KH_INDEX_TYPES = [
|
||||
"ktem.index.file.FileIndex",
|
||||
*GRAPHRAG_INDEX_TYPES,
|
||||
]
|
||||
|
||||
GRAPHRAG_INDICES = [
|
||||
{
|
||||
"name": graph_type.split(".")[-1].replace("Index", "")
|
||||
+ " Collection", # get last name
|
||||
"config": {
|
||||
"supported_file_types": (
|
||||
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
|
||||
".pptx, .csv, .html, .mhtml, .txt, .md, .zip"
|
||||
),
|
||||
"private": True,
|
||||
},
|
||||
"index_type": graph_type,
|
||||
}
|
||||
for graph_type in GRAPHRAG_INDEX_TYPES
|
||||
]
|
||||
|
||||
KH_INDICES = [
|
||||
{
|
||||
"name": "File Collection",
|
||||
"config": {
|
||||
"supported_file_types": (
|
||||
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
|
||||
".pptx, .csv, .html, .mhtml, .txt, .md, .zip"
|
||||
),
|
||||
"private": True,
|
||||
},
|
||||
"index_type": "ktem.index.file.FileIndex",
|
||||
},
|
||||
*GRAPHRAG_INDICES,
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
# fly.toml app configuration file generated for kotaemon on 2024-12-24T20:56:32+07:00
|
||||
#
|
||||
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||
#
|
||||
|
||||
app = 'kotaemon'
|
||||
primary_region = 'sin'
|
||||
|
||||
[build]
|
||||
|
||||
[mounts]
|
||||
destination = "/app/ktem_app_data"
|
||||
source = "ktem_volume"
|
||||
|
||||
[http_service]
|
||||
internal_port = 7860
|
||||
force_https = true
|
||||
auto_stop_machines = 'suspend'
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
memory = '4gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 4
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$GRADIO_SERVER_NAME" ]; then
|
||||
export GRADIO_SERVER_NAME="0.0.0.0"
|
||||
fi
|
||||
if [ -z "$GRADIO_SERVER_PORT" ]; then
|
||||
export GRADIO_SERVER_PORT="7860"
|
||||
fi
|
||||
|
||||
# Check if environment variable KH_DEMO_MODE is set to true
|
||||
if [ "$KH_DEMO_MODE" = "true" ]; then
|
||||
echo "KH_DEMO_MODE is true. Launching in demo mode..."
|
||||
# Command to launch in demo mode
|
||||
GR_FILE_ROOT_PATH="/app" KH_FEATURE_USER_MANAGEMENT=false USE_LIGHTRAG=false .venv/bin/uvicorn sso_app_demo:app --host "$GRADIO_SERVER_NAME" --port "$GRADIO_SERVER_PORT"
|
||||
else
|
||||
if [ "$KH_SSO_ENABLED" = "true" ]; then
|
||||
echo "KH_SSO_ENABLED is true. Launching in SSO mode..."
|
||||
GR_FILE_ROOT_PATH="/app" KH_SSO_ENABLED=true .venv/bin/uvicorn sso_app:app --host "$GRADIO_SERVER_NAME" --port "$GRADIO_SERVER_PORT"
|
||||
else
|
||||
ollama serve &
|
||||
.venv/bin/python app.py
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,129 @@
|
||||
# kotaemon
|
||||
|
||||
Quick and easy AI components to build Kotaemon
|
||||
|
||||
## Documentation
|
||||
|
||||
TBD
|
||||
|
||||
## Install
|
||||
|
||||
```shell
|
||||
pip install kotaemon@git+ssh://git@github.com/Cinnamon/kotaemon.git
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
### Setup
|
||||
|
||||
- Create conda environment (suggest 3.10)
|
||||
|
||||
```shell
|
||||
conda create -n kotaemon python=3.10
|
||||
conda activate kotaemon
|
||||
```
|
||||
|
||||
- Clone the repo
|
||||
|
||||
```shell
|
||||
git clone git@github.com:Cinnamon/kotaemon.git
|
||||
cd kotaemon
|
||||
```
|
||||
|
||||
- Install all
|
||||
|
||||
```shell
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
- Pre-commit
|
||||
|
||||
```shell
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
- Test
|
||||
|
||||
```shell
|
||||
pytest tests
|
||||
```
|
||||
|
||||
### Credential sharing
|
||||
|
||||
This repo uses [git-secret](https://sobolevn.me/git-secret/) to share credentials, which
|
||||
internally uses `gpg` to encrypt and decrypt secret files.
|
||||
|
||||
This repo uses `python-dotenv` to manage credentials stored as environment variable.
|
||||
Please note that the use of `python-dotenv` and credentials are for development
|
||||
purposes only. Thus, it should not be used in the main source code (i.e. `kotaemon/` and `tests/`), but can be used in `examples/`.
|
||||
|
||||
#### Install git-secret
|
||||
|
||||
Please follow the [official guide](https://sobolevn.me/git-secret/installation) to install git-secret.
|
||||
|
||||
For Windows users, see [For Windows users](#for-windows-users).
|
||||
|
||||
For users who don't have sudo privilege to install packages, follow the `Manual Installation` in the [official guide](https://sobolevn.me/git-secret/installation) and set `PREFIX` to a path that you have access to. And please don't forget to add `PREFIX` to your `PATH`.
|
||||
|
||||
#### Gaining access
|
||||
|
||||
In order to gain access to the secret files, you must provide your gpg public file to anyone who has access and ask them to ask your key to the keyring. For a quick tutorial on generating your gpg key pair, you can refer to the `Using gpg` section from the [git-secret main page](https://sobolevn.me/git-secret/).
|
||||
|
||||
#### Decrypt the secret file
|
||||
|
||||
The credentials are encrypted in the `.env.secret` file. To print the decrypted content to stdout, run
|
||||
|
||||
```shell
|
||||
git-secret cat [filename]
|
||||
```
|
||||
|
||||
Or to get the decrypted `.env` file, run
|
||||
|
||||
```shell
|
||||
git-secret reveal [filename]
|
||||
```
|
||||
|
||||
#### For Windows users
|
||||
|
||||
git-secret is currently not available for Windows, thus the easiest way is to use it in WSL (please use the latest version of WSL2). From there you have 2 options:
|
||||
|
||||
1. Using the gpg of WSL.
|
||||
|
||||
This is the most straight-forward option since you would use WSL just like any other unix environment. However, the downside is that you have to make WSL your main environment, which means WSL must have write permission on your repo. To achieve this, you must either:
|
||||
|
||||
- Clone and store your repo inside WSL's file system.
|
||||
- Provide WSL with necessary permission on your Windows file system. This can be achieve by setting `automount` options for WSL. To do that, add these content to `/etc/wsl.conf` and then restart your sub-system.
|
||||
|
||||
```shell
|
||||
[automount]
|
||||
options = "metadata,umask=022,fmask=011"
|
||||
```
|
||||
|
||||
This enables all permissions for user owner.
|
||||
|
||||
2. Using the gpg of Windows but with git-secret from WSL.
|
||||
|
||||
For those who use Windows as the main environment, having to switch back and forth between Windows and WSL will be inconvenient. You can instead stay within your Windows environment and apply some tricks to use `git-secret` from WSL.
|
||||
|
||||
- Install and setup `gpg` on Windows.
|
||||
- Install `git-secret` on WSL. Now in Windows, you can invoke `git-secret` using `wsl git-secret`.
|
||||
- Alternatively you can setup alias in CMD to shorten the syntax. Please refer to [this SO answer](https://stackoverflow.com/a/65823225) for the instruction. Some recommended aliases are:
|
||||
|
||||
```bat
|
||||
@echo off
|
||||
|
||||
:: Commands
|
||||
DOSKEY ls=dir /B $*
|
||||
DOSKEY ll=dir /a $*
|
||||
DOSKEY git-secret=wsl git-secret $*
|
||||
DOSKEY gs=wsl git-secret $*
|
||||
```
|
||||
|
||||
Now you can invoke `git-secret` in CMD using `git-secret` or `gs`.
|
||||
|
||||
- For Powershell users, similar behaviours can be achieved using `Set-Alias` and `profile.ps1`. Please refer this [SO thread](https://stackoverflow.com/questions/61081434/how-do-i-create-a-permanent-alias-file-in-powershell-core) as an example.
|
||||
|
||||
### Code base structure
|
||||
|
||||
- documents: define document
|
||||
- loaders
|
||||
@@ -0,0 +1,23 @@
|
||||
# Disable telemetry with monkey patching
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
import posthog
|
||||
|
||||
def capture(*args, **kwargs):
|
||||
logger.info("posthog.capture called with args: %s, kwargs: %s", args, kwargs)
|
||||
|
||||
posthog.capture = capture
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import os
|
||||
|
||||
os.environ["HAYSTACK_TELEMETRY_ENABLED"] = "False"
|
||||
import haystack.telemetry
|
||||
|
||||
haystack.telemetry.telemetry = None
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -0,0 +1,33 @@
|
||||
from .base import BaseAgent
|
||||
from .io import AgentFinish, AgentOutput, AgentType, BaseScratchPad
|
||||
from .langchain_based import LangchainAgent
|
||||
from .react.agent import ReactAgent
|
||||
from .rewoo.agent import RewooAgent
|
||||
from .tools import (
|
||||
BaseTool,
|
||||
ComponentTool,
|
||||
GoogleSearchTool,
|
||||
LLMTool,
|
||||
MCPTool,
|
||||
WikipediaTool,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# agent
|
||||
"BaseAgent",
|
||||
"ReactAgent",
|
||||
"RewooAgent",
|
||||
"LangchainAgent",
|
||||
# tool
|
||||
"BaseTool",
|
||||
"ComponentTool",
|
||||
"GoogleSearchTool",
|
||||
"WikipediaTool",
|
||||
"LLMTool",
|
||||
"MCPTool",
|
||||
# io
|
||||
"AgentType",
|
||||
"AgentOutput",
|
||||
"AgentFinish",
|
||||
"BaseScratchPad",
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from kotaemon.base import BaseComponent, Node, Param
|
||||
from kotaemon.llms import BaseLLM, PromptTemplate
|
||||
|
||||
from .io import AgentOutput, AgentType
|
||||
from .tools import BaseTool
|
||||
|
||||
|
||||
class BaseAgent(BaseComponent):
|
||||
"""Define base agent interface"""
|
||||
|
||||
name: str = Param(help="Name of the agent.")
|
||||
agent_type: AgentType = Param(help="Agent type, must be one of AgentType")
|
||||
description: str = Param(
|
||||
help=(
|
||||
"Description used to tell the model how/when/why to use the agent. You can"
|
||||
" provide few-shot examples as a part of the description. This will be"
|
||||
" input to the prompt of LLM."
|
||||
)
|
||||
)
|
||||
llm: Optional[BaseLLM] = Node(
|
||||
help=(
|
||||
"LLM to be used for the agent (optional). LLM must implement BaseLLM"
|
||||
" interface."
|
||||
)
|
||||
)
|
||||
prompt_template: Optional[Union[PromptTemplate, dict[str, PromptTemplate]]] = Param(
|
||||
help="A prompt template or a dict to supply different prompt to the agent"
|
||||
)
|
||||
plugins: list[BaseTool] = Param(
|
||||
default_callback=lambda _: [],
|
||||
help="List of plugins / tools to be used in the agent",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def safeguard_run(run_func, *args, **kwargs):
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return run_func(self, *args, **kwargs)
|
||||
except Exception as e:
|
||||
return AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status="failed",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
def add_tools(self, tools: list[BaseTool]) -> None:
|
||||
"""Helper method to add tools and update agent state if needed"""
|
||||
self.plugins.extend(tools)
|
||||
|
||||
def run(self, *args, **kwargs) -> AgentOutput | list[AgentOutput]:
|
||||
"""Run the component."""
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,3 @@
|
||||
from .base import AgentAction, AgentFinish, AgentOutput, AgentType, BaseScratchPad
|
||||
|
||||
__all__ = ["AgentOutput", "AgentFinish", "BaseScratchPad", "AgentType", "AgentAction"]
|
||||
@@ -0,0 +1,258 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Literal, NamedTuple, Optional, Union
|
||||
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from kotaemon.base import LLMInterface
|
||||
|
||||
|
||||
def check_log():
|
||||
"""
|
||||
Checks if logging has been enabled.
|
||||
:return: True if logging has been enabled, False otherwise.
|
||||
:rtype: bool
|
||||
"""
|
||||
return os.environ.get("LOG_PATH", None) is not None
|
||||
|
||||
|
||||
class AgentType(Enum):
|
||||
"""
|
||||
Enumerated type for agent types.
|
||||
"""
|
||||
|
||||
openai = "openai"
|
||||
openai_multi = "openai_multi"
|
||||
openai_tool = "openai_tool"
|
||||
self_ask = "self_ask"
|
||||
react = "react"
|
||||
rewoo = "rewoo"
|
||||
vanilla = "vanilla"
|
||||
|
||||
|
||||
class BaseScratchPad:
|
||||
"""
|
||||
Base class for output handlers.
|
||||
|
||||
Attributes:
|
||||
-----------
|
||||
logger : logging.Logger
|
||||
The logger object to log messages.
|
||||
|
||||
Methods:
|
||||
--------
|
||||
stop():
|
||||
Stop the output.
|
||||
|
||||
update_status(output: str, **kwargs):
|
||||
Update the status of the output.
|
||||
|
||||
thinking(name: str):
|
||||
Log that a process is thinking.
|
||||
|
||||
done(_all=False):
|
||||
Log that the process is done.
|
||||
|
||||
stream_print(item: str):
|
||||
Not implemented.
|
||||
|
||||
json_print(item: Dict[str, Any]):
|
||||
Log a JSON object.
|
||||
|
||||
panel_print(item: Any, title: str = "Output", stream: bool = False):
|
||||
Log a panel output.
|
||||
|
||||
clear():
|
||||
Not implemented.
|
||||
|
||||
print(content: str, **kwargs):
|
||||
Log arbitrary content.
|
||||
|
||||
format_json(json_obj: str):
|
||||
Format a JSON object.
|
||||
|
||||
debug(content: str, **kwargs):
|
||||
Log a debug message.
|
||||
|
||||
info(content: str, **kwargs):
|
||||
Log an informational message.
|
||||
|
||||
warning(content: str, **kwargs):
|
||||
Log a warning message.
|
||||
|
||||
error(content: str, **kwargs):
|
||||
Log an error message.
|
||||
|
||||
critical(content: str, **kwargs):
|
||||
Log a critical message.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the BaseOutput object.
|
||||
|
||||
"""
|
||||
self.logger = logging
|
||||
self.log = []
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the output.
|
||||
"""
|
||||
|
||||
def update_status(self, output: str, **kwargs):
|
||||
"""
|
||||
Update the status of the output.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.info(output)
|
||||
|
||||
def thinking(self, name: str):
|
||||
"""
|
||||
Log that a process is thinking.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.info(f"{name} is thinking...")
|
||||
|
||||
def done(self, _all=False):
|
||||
"""
|
||||
Log that the process is done.
|
||||
"""
|
||||
|
||||
if check_log():
|
||||
self.logger.info("Done")
|
||||
|
||||
def stream_print(self, item: str):
|
||||
"""
|
||||
Stream print.
|
||||
"""
|
||||
|
||||
def json_print(self, item: Dict[str, Any]):
|
||||
"""
|
||||
Log a JSON object.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.info(json.dumps(item, indent=2))
|
||||
|
||||
def panel_print(self, item: Any, title: str = "Output", stream: bool = False):
|
||||
"""
|
||||
Log a panel output.
|
||||
|
||||
Args:
|
||||
item : Any
|
||||
The item to log.
|
||||
title : str, optional
|
||||
The title of the panel, defaults to "Output".
|
||||
stream : bool, optional
|
||||
"""
|
||||
if not stream:
|
||||
self.log.append(item)
|
||||
if check_log():
|
||||
self.logger.info("-" * 20)
|
||||
self.logger.info(item)
|
||||
self.logger.info("-" * 20)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Not implemented.
|
||||
"""
|
||||
|
||||
def print(self, content: str, **kwargs):
|
||||
"""
|
||||
Log arbitrary content.
|
||||
"""
|
||||
self.log.append(content)
|
||||
if check_log():
|
||||
self.logger.info(content)
|
||||
|
||||
def format_json(self, json_obj: str):
|
||||
"""
|
||||
Format a JSON object.
|
||||
"""
|
||||
formatted_json = json.dumps(json_obj, indent=2)
|
||||
return formatted_json
|
||||
|
||||
def debug(self, content: str, **kwargs):
|
||||
"""
|
||||
Log a debug message.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.debug(content, **kwargs)
|
||||
|
||||
def info(self, content: str, **kwargs):
|
||||
"""
|
||||
Log an informational message.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.info(content, **kwargs)
|
||||
|
||||
def warning(self, content: str, **kwargs):
|
||||
"""
|
||||
Log a warning message.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.warning(content, **kwargs)
|
||||
|
||||
def error(self, content: str, **kwargs):
|
||||
"""
|
||||
Log an error message.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.error(content, **kwargs)
|
||||
|
||||
def critical(self, content: str, **kwargs):
|
||||
"""
|
||||
Log a critical message.
|
||||
"""
|
||||
if check_log():
|
||||
self.logger.critical(content, **kwargs)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentAction:
|
||||
"""Agent's action to take.
|
||||
|
||||
Args:
|
||||
tool: The tool to invoke.
|
||||
tool_input: The input to the tool.
|
||||
log: The log message.
|
||||
"""
|
||||
|
||||
tool: str
|
||||
tool_input: Union[str, dict]
|
||||
log: str
|
||||
|
||||
|
||||
class AgentFinish(NamedTuple):
|
||||
"""Agent's return value when finishing execution.
|
||||
|
||||
Args:
|
||||
return_values: The return values of the agent.
|
||||
log: The log message.
|
||||
"""
|
||||
|
||||
return_values: dict
|
||||
log: str
|
||||
|
||||
|
||||
class AgentOutput(LLMInterface):
|
||||
"""Output from an agent.
|
||||
|
||||
Args:
|
||||
text: The text output from the agent.
|
||||
agent_type: The type of agent.
|
||||
status: The status after executing the agent.
|
||||
error: The error message if any.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
text: str
|
||||
type: str = "agent"
|
||||
agent_type: AgentType
|
||||
status: Literal["thinking", "finished", "stopped", "failed"]
|
||||
error: Optional[str] = None
|
||||
intermediate_steps: Optional[list] = None
|
||||
@@ -0,0 +1,79 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from langchain.agents import AgentType as LCAgentType
|
||||
from langchain.agents import initialize_agent
|
||||
from langchain.agents.agent import AgentExecutor as LCAgentExecutor
|
||||
|
||||
from kotaemon.llms import LLM, ChatLLM
|
||||
|
||||
from .base import BaseAgent
|
||||
from .io import AgentOutput, AgentType
|
||||
from .tools import BaseTool
|
||||
|
||||
|
||||
class LangchainAgent(BaseAgent):
|
||||
"""Wrapper for Langchain Agent"""
|
||||
|
||||
name: str = "LangchainAgent"
|
||||
agent_type: AgentType
|
||||
description: str = "LangchainAgent for answering multi-step reasoning questions"
|
||||
AGENT_TYPE_MAP = {
|
||||
AgentType.openai: LCAgentType.OPENAI_FUNCTIONS,
|
||||
AgentType.openai_multi: LCAgentType.OPENAI_MULTI_FUNCTIONS,
|
||||
AgentType.react: LCAgentType.ZERO_SHOT_REACT_DESCRIPTION,
|
||||
AgentType.self_ask: LCAgentType.SELF_ASK_WITH_SEARCH,
|
||||
}
|
||||
agent: Optional[LCAgentExecutor] = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.agent_type not in self.AGENT_TYPE_MAP:
|
||||
raise NotImplementedError(
|
||||
f"AgentType {self.agent_type } not supported by Langchain wrapper"
|
||||
)
|
||||
self.update_agent_tools()
|
||||
|
||||
def update_agent_tools(self):
|
||||
assert isinstance(self.llm, (ChatLLM, LLM))
|
||||
langchain_plugins = [tool.to_langchain_format() for tool in self.plugins]
|
||||
|
||||
# a fix for search_doc tool name:
|
||||
# use "Intermediate Answer" for self-ask agent
|
||||
found_search_tool = False
|
||||
if self.agent_type == AgentType.self_ask:
|
||||
for plugin in langchain_plugins:
|
||||
if plugin.name == "search_doc":
|
||||
plugin.name = "Intermediate Answer"
|
||||
langchain_plugins = [plugin]
|
||||
found_search_tool = True
|
||||
break
|
||||
|
||||
if self.agent_type != AgentType.self_ask or found_search_tool:
|
||||
# reinit Langchain AgentExecutor
|
||||
self.agent = initialize_agent(
|
||||
langchain_plugins,
|
||||
self.llm.to_langchain_format(),
|
||||
agent=self.AGENT_TYPE_MAP[self.agent_type],
|
||||
handle_parsing_errors=True,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
def add_tools(self, tools: List[BaseTool]) -> None:
|
||||
super().add_tools(tools)
|
||||
self.update_agent_tools()
|
||||
return
|
||||
|
||||
def run(self, instruction: str) -> AgentOutput:
|
||||
assert (
|
||||
self.agent is not None
|
||||
), "Lanchain AgentExecutor is not correctly initialized"
|
||||
|
||||
# Langchain AgentExecutor call
|
||||
output = self.agent(instruction)["output"]
|
||||
|
||||
return AgentOutput(
|
||||
text=output,
|
||||
agent_type=self.agent_type,
|
||||
status="finished",
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .agent import ReactAgent
|
||||
|
||||
__all__ = ["ReactAgent"]
|
||||
@@ -0,0 +1,361 @@
|
||||
import logging
|
||||
import re
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
import tiktoken
|
||||
|
||||
from kotaemon.agents.base import BaseAgent, BaseLLM
|
||||
from kotaemon.agents.io import AgentAction, AgentFinish, AgentOutput, AgentType
|
||||
from kotaemon.agents.tools import BaseTool
|
||||
from kotaemon.base import Document, Param
|
||||
from kotaemon.indices.splitters import TokenSplitter
|
||||
from kotaemon.llms import PromptTemplate
|
||||
|
||||
FINAL_ANSWER_ACTION = "Final Answer:"
|
||||
|
||||
|
||||
class ReactAgent(BaseAgent):
|
||||
"""
|
||||
Sequential ReactAgent class inherited from BaseAgent.
|
||||
Implementing ReAct agent paradigm https://arxiv.org/pdf/2210.03629.pdf
|
||||
"""
|
||||
|
||||
name: str = "ReactAgent"
|
||||
agent_type: AgentType = AgentType.react
|
||||
description: str = "ReactAgent for answering multi-step reasoning questions"
|
||||
llm: BaseLLM
|
||||
prompt_template: Optional[PromptTemplate] = None
|
||||
output_lang: str = "English"
|
||||
plugins: list[BaseTool] = Param(
|
||||
default_callback=lambda _: [], help="List of tools to be used in the agent. "
|
||||
)
|
||||
examples: dict[str, str | list[str]] = Param(
|
||||
default_callback=lambda _: {}, help="Examples to be used in the agent. "
|
||||
)
|
||||
intermediate_steps: list[tuple[AgentAction | AgentFinish, str]] = Param(
|
||||
default_callback=lambda _: [],
|
||||
help="List of AgentAction and observation (tool) output",
|
||||
)
|
||||
max_iterations: int = 5
|
||||
strict_decode: bool = False
|
||||
max_context_length: int = Param(
|
||||
default=3000,
|
||||
help="Max context length for each tool output.",
|
||||
)
|
||||
trim_func: TokenSplitter | None = None
|
||||
|
||||
def _compose_plugin_description(self) -> str:
|
||||
"""
|
||||
Compose the worker prompt from the workers.
|
||||
|
||||
Example:
|
||||
toolname1[input]: tool1 description
|
||||
toolname2[input]: tool2 description
|
||||
"""
|
||||
prompt = ""
|
||||
try:
|
||||
for plugin in self.plugins:
|
||||
prompt += f"{plugin.name}[input]: {plugin.description}\n"
|
||||
except Exception:
|
||||
raise ValueError("Worker must have a name and description.")
|
||||
return prompt
|
||||
|
||||
def _construct_scratchpad(
|
||||
self, intermediate_steps: list[tuple[AgentAction | AgentFinish, str]] = []
|
||||
) -> str:
|
||||
"""Construct the scratchpad that lets the agent continue its thought process."""
|
||||
thoughts = ""
|
||||
for action, observation in intermediate_steps:
|
||||
thoughts += action.log
|
||||
thoughts += f"\nObservation: {observation}\nThought:"
|
||||
return thoughts
|
||||
|
||||
def _parse_output(self, text: str) -> Optional[AgentAction | AgentFinish]:
|
||||
"""
|
||||
Parse text output from LLM for the next Action or Final Answer
|
||||
Using Regex to parse "Action:\n Action Input:\n" for the next Action
|
||||
Using FINAL_ANSWER_ACTION to parse Final Answer
|
||||
|
||||
Args:
|
||||
text[str]: input text to parse
|
||||
"""
|
||||
includes_answer = FINAL_ANSWER_ACTION in text
|
||||
regex = (
|
||||
r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
|
||||
)
|
||||
action_match = re.search(regex, text, re.DOTALL)
|
||||
action_output: Optional[AgentAction | AgentFinish] = None
|
||||
if action_match:
|
||||
if includes_answer:
|
||||
raise Exception(
|
||||
"Parsing LLM output produced both a final answer "
|
||||
f"and a parse-able action: {text}"
|
||||
)
|
||||
action = action_match.group(1).strip()
|
||||
action_input = action_match.group(2)
|
||||
tool_input = action_input.strip(" ")
|
||||
# ensure if its a well formed SQL query we don't remove any trailing " chars
|
||||
if tool_input.startswith("SELECT ") is False:
|
||||
tool_input = tool_input.strip('"')
|
||||
|
||||
action_output = AgentAction(action, tool_input, text)
|
||||
|
||||
elif includes_answer:
|
||||
action_output = AgentFinish(
|
||||
{"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text
|
||||
)
|
||||
else:
|
||||
if self.strict_decode:
|
||||
raise Exception(f"Could not parse LLM output: `{text}`")
|
||||
else:
|
||||
action_output = AgentFinish({"output": text}, text)
|
||||
|
||||
return action_output
|
||||
|
||||
def _compose_prompt(self, instruction) -> str:
|
||||
"""
|
||||
Compose the prompt from template, worker description, examples and instruction.
|
||||
"""
|
||||
agent_scratchpad = self._construct_scratchpad(self.intermediate_steps)
|
||||
tool_description = self._compose_plugin_description()
|
||||
tool_names = ", ".join([plugin.name for plugin in self.plugins])
|
||||
if self.prompt_template is None:
|
||||
from .prompt import zero_shot_react_prompt
|
||||
|
||||
self.prompt_template = zero_shot_react_prompt
|
||||
return self.prompt_template.populate(
|
||||
instruction=instruction,
|
||||
agent_scratchpad=agent_scratchpad,
|
||||
tool_description=tool_description,
|
||||
tool_names=tool_names,
|
||||
lang=self.output_lang,
|
||||
)
|
||||
|
||||
def _format_function_map(self) -> dict[str, BaseTool]:
|
||||
"""Format the function map for the open AI function API.
|
||||
|
||||
Return:
|
||||
Dict[str, Callable]: The function map.
|
||||
"""
|
||||
# Map the function name to the real function object.
|
||||
function_map = {}
|
||||
for plugin in self.plugins:
|
||||
function_map[plugin.name] = plugin
|
||||
return function_map
|
||||
|
||||
def _trim(self, text: str | Document) -> str:
|
||||
"""
|
||||
Trim the text to the maximum token length.
|
||||
"""
|
||||
evidence_trim_func = (
|
||||
self.trim_func
|
||||
if self.trim_func
|
||||
else TokenSplitter(
|
||||
chunk_size=self.max_context_length,
|
||||
chunk_overlap=0,
|
||||
separator=" ",
|
||||
tokenizer=partial(
|
||||
tiktoken.encoding_for_model("gpt-3.5-turbo").encode,
|
||||
allowed_special=set(),
|
||||
disallowed_special="all",
|
||||
),
|
||||
)
|
||||
)
|
||||
if isinstance(text, str):
|
||||
texts = evidence_trim_func([Document(text=text)])
|
||||
elif isinstance(text, Document):
|
||||
texts = evidence_trim_func([text])
|
||||
else:
|
||||
raise ValueError("Invalid text type to trim")
|
||||
trim_text = texts[0].text
|
||||
logging.info(f"len (trimmed): {len(trim_text)}")
|
||||
return trim_text
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear and reset the agent.
|
||||
"""
|
||||
self.intermediate_steps = []
|
||||
|
||||
def run(self, instruction, max_iterations=None) -> AgentOutput:
|
||||
"""
|
||||
Run the agent with the given instruction.
|
||||
|
||||
Args:
|
||||
instruction: Instruction to run the agent with.
|
||||
max_iterations: Maximum number of iterations
|
||||
of reasoning steps, defaults to 10.
|
||||
|
||||
Return:
|
||||
AgentOutput object.
|
||||
"""
|
||||
if not max_iterations:
|
||||
max_iterations = self.max_iterations
|
||||
assert max_iterations > 0
|
||||
|
||||
self.clear()
|
||||
logging.info(f"Running {self.name} with instruction: {instruction}")
|
||||
total_cost = 0.0
|
||||
total_token = 0
|
||||
status = "failed"
|
||||
response_text = None
|
||||
|
||||
for step_count in range(1, max_iterations + 1):
|
||||
prompt = self._compose_prompt(instruction)
|
||||
logging.info(f"Prompt: {prompt}")
|
||||
response = self.llm(
|
||||
prompt, stop=["Observation:"]
|
||||
) # could cause bugs if llm doesn't have `stop` as a parameter
|
||||
response_text = response.text
|
||||
logging.info(f"Response: {response_text}")
|
||||
action_step = self._parse_output(response_text)
|
||||
if action_step is None:
|
||||
raise ValueError("Invalid action")
|
||||
is_finished_chain = isinstance(action_step, AgentFinish)
|
||||
if is_finished_chain:
|
||||
result = ""
|
||||
else:
|
||||
assert isinstance(action_step, AgentAction)
|
||||
action_name = action_step.tool
|
||||
tool_input = action_step.tool_input
|
||||
logging.info(f"Action: {action_name}")
|
||||
logging.info(f"Tool Input: {tool_input}")
|
||||
function_map = self._format_function_map()
|
||||
if action_name not in function_map:
|
||||
available = ", ".join(function_map.keys())
|
||||
result = (
|
||||
f"Tool '{action_name}' not found. "
|
||||
f"Available tools: {available}"
|
||||
)
|
||||
else:
|
||||
result = function_map[action_name](tool_input)
|
||||
|
||||
# trim the worker output to 1000 tokens, as we are appending
|
||||
# all workers' logs and it can exceed the token limit if we
|
||||
# don't limit each. Fix this number regarding to the LLM capacity.
|
||||
result = self._trim(result)
|
||||
logging.info(f"Result: {result}")
|
||||
|
||||
self.intermediate_steps.append((action_step, result))
|
||||
if is_finished_chain:
|
||||
logging.info(f"Finished after {step_count} steps.")
|
||||
status = "finished"
|
||||
break
|
||||
else:
|
||||
status = "stopped"
|
||||
|
||||
return AgentOutput(
|
||||
text=response_text,
|
||||
agent_type=self.agent_type,
|
||||
status=status,
|
||||
total_tokens=total_token,
|
||||
total_cost=total_cost,
|
||||
intermediate_steps=self.intermediate_steps,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
|
||||
def stream(self, instruction, max_iterations=None):
|
||||
"""
|
||||
Stream the agent with the given instruction.
|
||||
|
||||
Args:
|
||||
instruction: Instruction to run the agent with.
|
||||
max_iterations: Maximum number of iterations
|
||||
of reasoning steps, defaults to 10.
|
||||
|
||||
Return:
|
||||
AgentOutput object.
|
||||
"""
|
||||
if not max_iterations:
|
||||
max_iterations = self.max_iterations
|
||||
assert max_iterations > 0
|
||||
|
||||
self.clear()
|
||||
logging.info(f"Running {self.name} with instruction: {instruction}")
|
||||
print(f"Running {self.name} with instruction: {instruction}")
|
||||
total_cost = 0.0
|
||||
total_token = 0
|
||||
status = "failed"
|
||||
response_text = None
|
||||
|
||||
for step_count in range(1, max_iterations + 1):
|
||||
prompt = self._compose_prompt(instruction)
|
||||
logging.info(f"Prompt: {prompt}")
|
||||
print(f"Prompt: {prompt}")
|
||||
response = self.llm(
|
||||
prompt, stop=["Observation:"]
|
||||
) # TODO: could cause bugs if llm doesn't have `stop` as a parameter
|
||||
response_text = response.text
|
||||
logging.info(f"Response: {response_text}")
|
||||
print(f"Response: {response_text}")
|
||||
action_step = self._parse_output(response_text)
|
||||
if action_step is None:
|
||||
raise ValueError("Invalid action")
|
||||
is_finished_chain = isinstance(action_step, AgentFinish)
|
||||
if is_finished_chain:
|
||||
result = response_text
|
||||
if "Final Answer:" in response_text:
|
||||
result = response_text.split("Final Answer:")[-1].strip()
|
||||
else:
|
||||
assert isinstance(action_step, AgentAction)
|
||||
action_name = action_step.tool
|
||||
tool_input = action_step.tool_input
|
||||
logging.info(f"Action: {action_name}")
|
||||
print(f"Action: {action_name}")
|
||||
logging.info(f"Tool Input: {tool_input}")
|
||||
print(f"Tool Input: {tool_input}")
|
||||
function_map = self._format_function_map()
|
||||
if action_name not in function_map:
|
||||
available = ", ".join(function_map.keys())
|
||||
result = (
|
||||
f"Tool '{action_name}' not found. "
|
||||
f"Available tools: {available}"
|
||||
)
|
||||
else:
|
||||
result = function_map[action_name](tool_input)
|
||||
|
||||
# trim the worker output to 1000 tokens, as we are appending
|
||||
# all workers' logs and it can exceed the token limit if we
|
||||
# don't limit each. Fix this number regarding to the LLM capacity.
|
||||
result = self._trim(result)
|
||||
logging.info(f"Result: {result}")
|
||||
print(f"Result: {result}")
|
||||
|
||||
self.intermediate_steps.append((action_step, result))
|
||||
if is_finished_chain:
|
||||
logging.info(f"Finished after {step_count} steps.")
|
||||
status = "finished"
|
||||
yield AgentOutput(
|
||||
text=result,
|
||||
agent_type=self.agent_type,
|
||||
status=status,
|
||||
intermediate_steps=self.intermediate_steps[-1],
|
||||
)
|
||||
break
|
||||
else:
|
||||
yield AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status="thinking",
|
||||
intermediate_steps=self.intermediate_steps[-1],
|
||||
)
|
||||
|
||||
else:
|
||||
status = "stopped"
|
||||
yield AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status=status,
|
||||
intermediate_steps=self.intermediate_steps[-1],
|
||||
)
|
||||
|
||||
return AgentOutput(
|
||||
text=response_text,
|
||||
agent_type=self.agent_type,
|
||||
status=status,
|
||||
total_tokens=total_token,
|
||||
total_cost=total_cost,
|
||||
intermediate_steps=self.intermediate_steps,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
# flake8: noqa
|
||||
|
||||
from kotaemon.llms import PromptTemplate
|
||||
|
||||
zero_shot_react_prompt = PromptTemplate(
|
||||
template="""Answer the following questions as best you can. Give answer in {lang}. You have access to the following tools:
|
||||
{tool_description}
|
||||
Use the following format:
|
||||
|
||||
Question: the input question you must answer
|
||||
Thought: you should always think about what to do
|
||||
|
||||
Action: the action to take, should be one of [{tool_names}]
|
||||
|
||||
Action Input: the input to the action, should be different from the action input of the same action in previous steps.
|
||||
|
||||
Observation: the result of the action
|
||||
|
||||
... (this Thought/Action/Action Input/Observation can repeat N times)
|
||||
#Thought: I now know the final answer
|
||||
Final Answer: the final answer to the original input question
|
||||
|
||||
Begin! After each Action Input.
|
||||
|
||||
Question: {instruction}
|
||||
Thought:{agent_scratchpad}
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .agent import RewooAgent
|
||||
|
||||
__all__ = ["RewooAgent"]
|
||||
@@ -0,0 +1,384 @@
|
||||
import logging
|
||||
import re
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
import tiktoken
|
||||
|
||||
from kotaemon.agents.base import BaseAgent
|
||||
from kotaemon.agents.io import AgentOutput, AgentType, BaseScratchPad
|
||||
from kotaemon.agents.tools import BaseTool
|
||||
from kotaemon.agents.utils import get_plugin_response_content
|
||||
from kotaemon.base import Document, Node, Param
|
||||
from kotaemon.indices.qa.citation import CitationPipeline
|
||||
from kotaemon.indices.splitters import TokenSplitter
|
||||
from kotaemon.llms import BaseLLM, PromptTemplate
|
||||
|
||||
from .planner import Planner
|
||||
from .solver import Solver
|
||||
|
||||
|
||||
class RewooAgent(BaseAgent):
|
||||
"""Distributive RewooAgent class inherited from BaseAgent.
|
||||
Implementing ReWOO paradigm https://arxiv.org/pdf/2305.18323.pdf"""
|
||||
|
||||
name: str = "RewooAgent"
|
||||
agent_type: AgentType = AgentType.rewoo
|
||||
description: str = "RewooAgent for answering multi-step reasoning questions"
|
||||
output_lang: str = "English"
|
||||
planner_llm: BaseLLM
|
||||
solver_llm: BaseLLM
|
||||
prompt_template: dict[str, PromptTemplate] = Param(
|
||||
default_callback=lambda _: {},
|
||||
help="A dict to supply different prompt to the agent.",
|
||||
)
|
||||
plugins: list[BaseTool] = Param(
|
||||
default_callback=lambda _: [], help="A list of plugins to be used in the model."
|
||||
)
|
||||
examples: dict[str, str | list[str]] = Param(
|
||||
default_callback=lambda _: {}, help="Examples to be used in the agent."
|
||||
)
|
||||
max_context_length: int = Param(
|
||||
default=3000,
|
||||
help="Max context length for each tool output.",
|
||||
)
|
||||
trim_func: TokenSplitter | None = None
|
||||
|
||||
@Node.auto(depends_on=["planner_llm", "plugins", "prompt_template", "examples"])
|
||||
def planner(self):
|
||||
return Planner(
|
||||
model=self.planner_llm,
|
||||
plugins=self.plugins,
|
||||
prompt_template=self.prompt_template.get("Planner", None),
|
||||
examples=self.examples.get("Planner", None),
|
||||
)
|
||||
|
||||
@Node.auto(depends_on=["solver_llm", "prompt_template", "examples"])
|
||||
def solver(self):
|
||||
return Solver(
|
||||
model=self.solver_llm,
|
||||
prompt_template=self.prompt_template.get("Solver", None),
|
||||
examples=self.examples.get("Solver", None),
|
||||
output_lang=self.output_lang,
|
||||
)
|
||||
|
||||
def _parse_plan_map(
|
||||
self, planner_response: str
|
||||
) -> tuple[dict[str, list[str]], dict[str, str]]:
|
||||
"""
|
||||
Parse planner output. It should be an n-to-n mapping from Plans to #Es.
|
||||
This is because sometimes LLM cannot follow the strict output format.
|
||||
Example:
|
||||
#Plan1
|
||||
#E1
|
||||
#E2
|
||||
should result in: {"#Plan1": ["#E1", "#E2"]}
|
||||
Or:
|
||||
#Plan1
|
||||
#Plan2
|
||||
#E1
|
||||
should result in: {"#Plan1": [], "#Plan2": ["#E1"]}
|
||||
This function should also return a plan map.
|
||||
|
||||
Returns:
|
||||
tuple[Dict[str, List[str]], Dict[str, str]]: A list of plan map
|
||||
"""
|
||||
valid_chunk = [
|
||||
line
|
||||
for line in planner_response.splitlines()
|
||||
if line.startswith("#Plan") or line.startswith("#E")
|
||||
]
|
||||
|
||||
plan_to_es: dict[str, list[str]] = dict()
|
||||
plans: dict[str, str] = dict()
|
||||
prev_key = ""
|
||||
for line in valid_chunk:
|
||||
key, description = line.split(":", 1)
|
||||
key = key.strip()
|
||||
if key.startswith("#Plan"):
|
||||
plans[key] = description.strip()
|
||||
plan_to_es[key] = []
|
||||
prev_key = key
|
||||
elif key.startswith("#E"):
|
||||
plan_to_es[prev_key].append(key)
|
||||
|
||||
return plan_to_es, plans
|
||||
|
||||
def _parse_planner_evidences(
|
||||
self, planner_response: str
|
||||
) -> tuple[dict[str, str], list[list[str]]]:
|
||||
"""
|
||||
Parse planner output. This should return a mapping from #E to tool call.
|
||||
It should also identify the level of each #E in dependency map.
|
||||
Example:
|
||||
{
|
||||
"#E1": "Tool1", "#E2": "Tool2",
|
||||
"#E3": "Tool3", "#E4": "Tool4"
|
||||
}, [[#E1, #E2], [#E3, #E4]]
|
||||
|
||||
Returns:
|
||||
tuple[dict[str, str], List[List[str]]]:
|
||||
A mapping from #E to tool call and a list of levels.
|
||||
"""
|
||||
evidences: dict[str, str] = dict()
|
||||
dependence: dict[str, list[str]] = dict()
|
||||
for line in planner_response.splitlines():
|
||||
if line.startswith("#E") and line[2].isdigit():
|
||||
e, tool_call = line.split(":", 1)
|
||||
e, tool_call = e.strip(), tool_call.strip()
|
||||
if len(e) == 3:
|
||||
dependence[e] = []
|
||||
evidences[e] = tool_call
|
||||
for var in re.findall(r"#E\d+", tool_call):
|
||||
if var in evidences:
|
||||
dependence[e].append(var)
|
||||
else:
|
||||
evidences[e] = "No evidence found"
|
||||
level = []
|
||||
while dependence:
|
||||
select = [i for i in dependence if not dependence[i]]
|
||||
if len(select) == 0:
|
||||
raise ValueError("Circular dependency detected.")
|
||||
level.append(select)
|
||||
for item in select:
|
||||
dependence.pop(item)
|
||||
for item in dependence:
|
||||
for i in select:
|
||||
if i in dependence[item]:
|
||||
dependence[item].remove(i)
|
||||
|
||||
return evidences, level
|
||||
|
||||
def _run_plugin(
|
||||
self,
|
||||
e: str,
|
||||
planner_evidences: dict[str, str],
|
||||
worker_evidences: dict[str, str],
|
||||
output=BaseScratchPad(),
|
||||
):
|
||||
"""
|
||||
Run a plugin for a given evidence.
|
||||
This function should also cumulate the cost and tokens.
|
||||
"""
|
||||
result = dict(e=e, plugin_cost=0, plugin_token=0, evidence="")
|
||||
tool_call = planner_evidences[e]
|
||||
if "[" not in tool_call:
|
||||
result["evidence"] = tool_call
|
||||
else:
|
||||
tool, tool_input = tool_call.split("[", 1)
|
||||
tool_input = tool_input[:-1]
|
||||
# find variables in input and replace with previous evidences
|
||||
for var in re.findall(r"#E\d+", tool_input):
|
||||
print("Tool input: ", tool_input)
|
||||
print("Var: ", var)
|
||||
print("Worker evidences: ", worker_evidences)
|
||||
if var in worker_evidences:
|
||||
tool_input = tool_input.replace(
|
||||
var, worker_evidences.get(var, "") or ""
|
||||
)
|
||||
try:
|
||||
selected_plugin = self._find_plugin(tool)
|
||||
if selected_plugin is None:
|
||||
raise ValueError("Invalid plugin detected")
|
||||
tool_response = selected_plugin(tool_input)
|
||||
result["evidence"] = get_plugin_response_content(tool_response)
|
||||
except ValueError:
|
||||
result["evidence"] = "No evidence found."
|
||||
finally:
|
||||
output.panel_print(
|
||||
result["evidence"], f"[green] Function Response of [blue]{tool}: "
|
||||
)
|
||||
return result
|
||||
|
||||
def _get_worker_evidence(
|
||||
self,
|
||||
planner_evidences: dict[str, str],
|
||||
evidences_level: list[list[str]],
|
||||
output=BaseScratchPad(),
|
||||
) -> Any:
|
||||
"""
|
||||
Parallel execution of plugins in DAG for speedup.
|
||||
This is one of core benefits of ReWOO agents.
|
||||
|
||||
Args:
|
||||
planner_evidences: A mapping from #E to tool call.
|
||||
evidences_level: A list of levels of evidences.
|
||||
Calculated from DAG of plugin calls.
|
||||
output: Output object, defaults to BaseOutput().
|
||||
Returns:
|
||||
A mapping from #E to tool call.
|
||||
"""
|
||||
worker_evidences: dict[str, str] = dict()
|
||||
plugin_cost, plugin_token = 0.0, 0.0
|
||||
with ThreadPoolExecutor() as pool:
|
||||
for level in evidences_level:
|
||||
results = []
|
||||
for e in level:
|
||||
results.append(
|
||||
pool.submit(
|
||||
self._run_plugin,
|
||||
e,
|
||||
planner_evidences,
|
||||
worker_evidences,
|
||||
output,
|
||||
)
|
||||
)
|
||||
if len(results) > 1:
|
||||
output.update_status(f"Running tasks {level} in parallel.")
|
||||
else:
|
||||
output.update_status(f"Running task {level[0]}.")
|
||||
for r in results:
|
||||
resp = r.result()
|
||||
plugin_cost += resp["plugin_cost"]
|
||||
plugin_token += resp["plugin_token"]
|
||||
worker_evidences[resp["e"]] = self._trim_evidence(resp["evidence"])
|
||||
output.done()
|
||||
|
||||
return worker_evidences, plugin_cost, plugin_token
|
||||
|
||||
def _find_plugin(self, name: str):
|
||||
for p in self.plugins:
|
||||
if p.name == name:
|
||||
return p
|
||||
|
||||
def _trim_evidence(self, evidence: str):
|
||||
evidence_trim_func = (
|
||||
self.trim_func
|
||||
if self.trim_func
|
||||
else TokenSplitter(
|
||||
chunk_size=self.max_context_length,
|
||||
chunk_overlap=0,
|
||||
separator=" ",
|
||||
tokenizer=partial(
|
||||
tiktoken.encoding_for_model("gpt-3.5-turbo").encode,
|
||||
allowed_special=set(),
|
||||
disallowed_special="all",
|
||||
),
|
||||
)
|
||||
)
|
||||
if evidence:
|
||||
texts = evidence_trim_func([Document(text=evidence)])
|
||||
evidence = texts[0].text
|
||||
logging.info(f"len (trimmed): {len(evidence)}")
|
||||
return evidence
|
||||
|
||||
@BaseAgent.safeguard_run
|
||||
def run(self, instruction: str, use_citation: bool = False) -> AgentOutput:
|
||||
"""
|
||||
Run the agent with a given instruction.
|
||||
"""
|
||||
logging.info(f"Running {self.name} with instruction: {instruction}")
|
||||
total_cost = 0.0
|
||||
total_token = 0
|
||||
|
||||
# Plan
|
||||
planner_output = self.planner(instruction)
|
||||
planner_text_output = planner_output.text
|
||||
plan_to_es, plans = self._parse_plan_map(planner_text_output)
|
||||
planner_evidences, evidence_level = self._parse_planner_evidences(
|
||||
planner_text_output
|
||||
)
|
||||
|
||||
# Work
|
||||
worker_evidences, plugin_cost, plugin_token = self._get_worker_evidence(
|
||||
planner_evidences, evidence_level
|
||||
)
|
||||
worker_log = ""
|
||||
for plan in plan_to_es:
|
||||
worker_log += f"{plan}: {plans[plan]}\n"
|
||||
for e in plan_to_es[plan]:
|
||||
worker_log += f"{e}: {worker_evidences[e]}\n"
|
||||
|
||||
# Solve
|
||||
solver_output = self.solver(instruction, worker_log)
|
||||
solver_output_text = solver_output.text
|
||||
if use_citation:
|
||||
citation_pipeline = CitationPipeline(llm=self.solver_llm)
|
||||
citation = citation_pipeline(context=worker_log, question=instruction)
|
||||
else:
|
||||
citation = None
|
||||
|
||||
return AgentOutput(
|
||||
text=solver_output_text,
|
||||
agent_type=self.agent_type,
|
||||
status="finished",
|
||||
total_tokens=total_token,
|
||||
total_cost=total_cost,
|
||||
citation=citation,
|
||||
metadata={"citation": citation, "worker_log": worker_log},
|
||||
)
|
||||
|
||||
def stream(self, instruction: str, use_citation: bool = False):
|
||||
"""
|
||||
Stream the agent with a given instruction.
|
||||
"""
|
||||
logging.info(f"Streaming {self.name} with instruction: {instruction}")
|
||||
total_cost = 0.0
|
||||
total_token = 0
|
||||
|
||||
# Plan
|
||||
planner_output = self.planner(instruction)
|
||||
planner_text_output = planner_output.text
|
||||
plan_to_es, plans = self._parse_plan_map(planner_text_output)
|
||||
planner_evidences, evidence_level = self._parse_planner_evidences(
|
||||
planner_text_output
|
||||
)
|
||||
|
||||
print("Planner output:", planner_text_output)
|
||||
# output planner to info panel
|
||||
yield AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status="thinking",
|
||||
intermediate_steps=[{"planner_log": planner_text_output}],
|
||||
)
|
||||
|
||||
# Work
|
||||
worker_evidences, plugin_cost, plugin_token = self._get_worker_evidence(
|
||||
planner_evidences, evidence_level
|
||||
)
|
||||
worker_log = ""
|
||||
for plan in plan_to_es:
|
||||
worker_log += f"{plan}: {plans[plan]}\n"
|
||||
current_progress = f"{plan}: {plans[plan]}\n"
|
||||
for e in plan_to_es[plan]:
|
||||
worker_log += f"#Action: {planner_evidences.get(e, None)}\n"
|
||||
worker_log += f"{e}: {worker_evidences[e]}\n"
|
||||
current_progress += f"#Action: {planner_evidences.get(e, None)}\n"
|
||||
current_progress += f"{e}: {worker_evidences[e]}\n"
|
||||
|
||||
yield AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status="thinking",
|
||||
intermediate_steps=[{"worker_log": current_progress}],
|
||||
)
|
||||
|
||||
# Solve
|
||||
solver_response = ""
|
||||
for solver_output in self.solver.stream(instruction, worker_log):
|
||||
solver_output_text = solver_output.text
|
||||
solver_response += solver_output_text
|
||||
yield AgentOutput(
|
||||
text=solver_output_text,
|
||||
agent_type=self.agent_type,
|
||||
status="thinking",
|
||||
)
|
||||
if use_citation:
|
||||
citation_pipeline = CitationPipeline(llm=self.solver_llm)
|
||||
citation = citation_pipeline.invoke(
|
||||
context=worker_log, question=instruction
|
||||
)
|
||||
else:
|
||||
citation = None
|
||||
|
||||
return AgentOutput(
|
||||
text="",
|
||||
agent_type=self.agent_type,
|
||||
status="finished",
|
||||
total_tokens=total_token,
|
||||
total_cost=total_cost,
|
||||
citation=citation,
|
||||
metadata={"citation": citation, "worker_log": worker_log},
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
from typing import Any, List, Optional, Union
|
||||
|
||||
from kotaemon.agents.base import BaseLLM, BaseTool
|
||||
from kotaemon.agents.io import BaseScratchPad
|
||||
from kotaemon.base import BaseComponent
|
||||
from kotaemon.llms import PromptTemplate
|
||||
|
||||
from .prompt import few_shot_planner_prompt, zero_shot_planner_prompt
|
||||
|
||||
|
||||
class Planner(BaseComponent):
|
||||
model: BaseLLM
|
||||
prompt_template: Optional[PromptTemplate] = None
|
||||
examples: Optional[Union[str, List[str]]] = None
|
||||
plugins: List[BaseTool]
|
||||
|
||||
def _compose_worker_description(self) -> str:
|
||||
"""
|
||||
Compose the worker prompt from the workers.
|
||||
|
||||
Example:
|
||||
toolname1[input]: tool1 description
|
||||
toolname2[input]: tool2 description
|
||||
"""
|
||||
prompt = ""
|
||||
try:
|
||||
for worker in self.plugins:
|
||||
prompt += f"{worker.name}[input]: {worker.description}\n"
|
||||
except Exception:
|
||||
raise ValueError("Worker must have a name and description.")
|
||||
return prompt
|
||||
|
||||
def _compose_fewshot_prompt(self) -> str:
|
||||
if self.examples is None:
|
||||
return ""
|
||||
if isinstance(self.examples, str):
|
||||
return self.examples
|
||||
else:
|
||||
return "\n\n".join([e.strip("\n") for e in self.examples])
|
||||
|
||||
def _compose_prompt(self, instruction) -> str:
|
||||
"""
|
||||
Compose the prompt from template, worker description, examples and instruction.
|
||||
"""
|
||||
worker_desctription = self._compose_worker_description()
|
||||
fewshot = self._compose_fewshot_prompt()
|
||||
if self.prompt_template is not None:
|
||||
if "fewshot" in self.prompt_template.placeholders:
|
||||
return self.prompt_template.populate(
|
||||
tool_description=worker_desctription,
|
||||
fewshot=fewshot,
|
||||
task=instruction,
|
||||
)
|
||||
else:
|
||||
return self.prompt_template.populate(
|
||||
tool_description=worker_desctription, task=instruction
|
||||
)
|
||||
else:
|
||||
if self.examples is not None:
|
||||
return few_shot_planner_prompt.populate(
|
||||
tool_description=worker_desctription,
|
||||
fewshot=fewshot,
|
||||
task=instruction,
|
||||
)
|
||||
else:
|
||||
return zero_shot_planner_prompt.populate(
|
||||
tool_description=worker_desctription, task=instruction
|
||||
)
|
||||
|
||||
def run(self, instruction: str, output: BaseScratchPad = BaseScratchPad()) -> Any:
|
||||
response = None
|
||||
output.info("Running Planner")
|
||||
prompt = self._compose_prompt(instruction)
|
||||
output.debug(f"Prompt: {prompt}")
|
||||
try:
|
||||
response = self.model(prompt)
|
||||
self.log_progress(".planner", response=response)
|
||||
output.info("Planner run successful.")
|
||||
except ValueError as e:
|
||||
output.error("Planner failed to retrieve response from LLM")
|
||||
raise ValueError("Planner failed to retrieve response from LLM") from e
|
||||
|
||||
return response
|
||||
|
||||
def stream(self, instruction: str, output: BaseScratchPad = BaseScratchPad()):
|
||||
response = None
|
||||
output.info("Running Planner")
|
||||
prompt = self._compose_prompt(instruction)
|
||||
output.debug(f"Prompt: {prompt}")
|
||||
|
||||
response = ""
|
||||
try:
|
||||
for text in self.model.stream(prompt):
|
||||
response += text
|
||||
yield text
|
||||
self.log_progress(".planner", response=response)
|
||||
output.info("Planner run successful.")
|
||||
except NotImplementedError:
|
||||
print("Streaming is not supported, falling back to normal run")
|
||||
response = self.model(prompt)
|
||||
yield response
|
||||
except ValueError as e:
|
||||
output.error("Planner failed to retrieve response from LLM")
|
||||
raise ValueError("Planner failed to retrieve response from LLM") from e
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,119 @@
|
||||
# flake8: noqa
|
||||
|
||||
from kotaemon.llms import PromptTemplate
|
||||
|
||||
zero_shot_planner_prompt = PromptTemplate(
|
||||
template="""You are an AI agent who makes step-by-step plans to solve a problem under the help of external tools.
|
||||
For each step, make one plan followed by one tool-call, which will be executed later to retrieve evidence for that step.
|
||||
You should store each evidence into a distinct variable #E1, #E2, #E3 ... that can be referred to in later tool-call inputs.
|
||||
|
||||
##Available Tools##
|
||||
{tool_description}
|
||||
|
||||
##Output Format (Replace '<...>')##
|
||||
#Plan1: <describe your plan here>
|
||||
#E1: <toolname>[<input here>] (eg. Search[What is Python])
|
||||
#Plan2: <describe next plan>
|
||||
#E2: <toolname>[<input here, you can use #E1 to represent its expected output>]
|
||||
And so on...
|
||||
|
||||
##Your Task##
|
||||
{task}
|
||||
|
||||
##Now Begin##
|
||||
"""
|
||||
)
|
||||
|
||||
one_shot_planner_prompt = PromptTemplate(
|
||||
template="""You are an AI agent who makes step-by-step plans to solve a problem under the help of external tools.
|
||||
For each step, make one plan followed by one tool-call, which will be executed later to retrieve evidence for that step.
|
||||
You should store each evidence into a distinct variable #E1, #E2, #E3 ... that can be referred to in later tool-call inputs.
|
||||
|
||||
##Available Tools##
|
||||
{tool_description}
|
||||
|
||||
##Output Format##
|
||||
#Plan1: <describe your plan here>
|
||||
#E1: <toolname>[<input here>]
|
||||
#Plan2: <describe next plan>
|
||||
#E2: <toolname>[<input here, you can use #E1 to represent its expected output>]
|
||||
And so on...
|
||||
|
||||
##Example##
|
||||
Task: What is the 4th root of 64 to the power of 3?
|
||||
#Plan1: Find the 4th root of 64
|
||||
#E1: Calculator[64^(1/4)]
|
||||
#Plan2: Raise the result from #Plan1 to the power of 3
|
||||
#E2: Calculator[#E1^3]
|
||||
|
||||
##Your Task##
|
||||
{task}
|
||||
|
||||
##Now Begin##
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
few_shot_planner_prompt = PromptTemplate(
|
||||
template="""You are an AI agent who makes step-by-step plans to solve a problem under the help of external tools.
|
||||
For each step, make one plan followed by one tool-call, which will be executed later to retrieve evidence for that step.
|
||||
You should store each evidence into a distinct variable #E1, #E2, #E3 ... that can be referred to in later tool-call inputs.
|
||||
|
||||
##Available Tools##
|
||||
{tool_description}
|
||||
|
||||
##Output Format (Replace '<...>')##
|
||||
#Plan1: <describe your plan here>
|
||||
#E1: <toolname>[<input>]
|
||||
#Plan2: <describe next plan>
|
||||
#E2: <toolname>[<input, you can use #E1 to represent its expected output>]
|
||||
And so on...
|
||||
|
||||
##Examples##
|
||||
{fewshot}
|
||||
|
||||
##Your Task##
|
||||
{task}
|
||||
|
||||
##Now Begin##
|
||||
"""
|
||||
)
|
||||
|
||||
zero_shot_solver_prompt = PromptTemplate(
|
||||
template="""You are an AI agent who solves a problem with my assistance. I will provide step-by-step plans(#Plan) and evidences(#E) that could be helpful.
|
||||
Your task is to briefly summarize each step, then make a short final conclusion for your task. Give answer in {lang}.
|
||||
|
||||
##My Plans and Evidences##
|
||||
{plan_evidence}
|
||||
|
||||
##Example Output##
|
||||
First, I <did something> , and I think <...>; Second, I <...>, and I think <...>; ....
|
||||
So, <your conclusion>.
|
||||
|
||||
##Your Task##
|
||||
{task}
|
||||
|
||||
##Now Begin##
|
||||
"""
|
||||
)
|
||||
|
||||
few_shot_solver_prompt = PromptTemplate(
|
||||
template="""You are an AI agent who solves a problem with my assistance. I will provide step-by-step plans and evidences that could be helpful.
|
||||
Your task is to briefly summarize each step, then make a short final conclusion for your task. Give answer in {lang}.
|
||||
|
||||
##My Plans and Evidences##
|
||||
{plan_evidence}
|
||||
|
||||
##Example Output##
|
||||
First, I <did something> , and I think <...>; Second, I <...>, and I think <...>; ....
|
||||
So, <your conclusion>.
|
||||
|
||||
##Example##
|
||||
{fewshot}
|
||||
|
||||
##Your Task##
|
||||
{task}
|
||||
|
||||
##Now Begin##
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
from typing import Any, List, Optional, Union
|
||||
|
||||
from kotaemon.agents.io import BaseScratchPad
|
||||
from kotaemon.base import BaseComponent
|
||||
from kotaemon.llms import BaseLLM, PromptTemplate
|
||||
|
||||
from .prompt import few_shot_solver_prompt, zero_shot_solver_prompt
|
||||
|
||||
|
||||
class Solver(BaseComponent):
|
||||
model: BaseLLM
|
||||
prompt_template: Optional[PromptTemplate] = None
|
||||
examples: Optional[Union[str, List[str]]] = None
|
||||
output_lang: str = "English"
|
||||
|
||||
def _compose_fewshot_prompt(self) -> str:
|
||||
if self.examples is None:
|
||||
return ""
|
||||
if isinstance(self.examples, str):
|
||||
return self.examples
|
||||
else:
|
||||
return "\n\n".join([e.strip("\n") for e in self.examples])
|
||||
|
||||
def _compose_prompt(self, instruction, plan_evidence, output_lang) -> str:
|
||||
"""
|
||||
Compose the prompt from template, plan&evidence, examples and instruction.
|
||||
"""
|
||||
fewshot = self._compose_fewshot_prompt()
|
||||
if self.prompt_template is not None:
|
||||
if "fewshot" in self.prompt_template.placeholders:
|
||||
return self.prompt_template.populate(
|
||||
plan_evidence=plan_evidence,
|
||||
fewshot=fewshot,
|
||||
task=instruction,
|
||||
lang=output_lang,
|
||||
)
|
||||
else:
|
||||
return self.prompt_template.populate(
|
||||
plan_evidence=plan_evidence, task=instruction, lang=output_lang
|
||||
)
|
||||
else:
|
||||
if self.examples is not None:
|
||||
return few_shot_solver_prompt.populate(
|
||||
plan_evidence=plan_evidence,
|
||||
fewshot=fewshot,
|
||||
task=instruction,
|
||||
lang=output_lang,
|
||||
)
|
||||
else:
|
||||
return zero_shot_solver_prompt.populate(
|
||||
plan_evidence=plan_evidence,
|
||||
task=instruction,
|
||||
lang=output_lang,
|
||||
)
|
||||
|
||||
def run(
|
||||
self,
|
||||
instruction: str,
|
||||
plan_evidence: str,
|
||||
output: BaseScratchPad = BaseScratchPad(),
|
||||
) -> Any:
|
||||
response = None
|
||||
output.info("Running Solver")
|
||||
output.debug(f"Instruction: {instruction}")
|
||||
output.debug(f"Plan Evidence: {plan_evidence}")
|
||||
prompt = self._compose_prompt(instruction, plan_evidence, self.output_lang)
|
||||
output.debug(f"Prompt: {prompt}")
|
||||
try:
|
||||
response = self.model(prompt)
|
||||
output.info("Solver run successful.")
|
||||
except ValueError:
|
||||
output.error("Solver failed to retrieve response from LLM")
|
||||
|
||||
return response
|
||||
|
||||
def stream(
|
||||
self,
|
||||
instruction: str,
|
||||
plan_evidence: str,
|
||||
output: BaseScratchPad = BaseScratchPad(),
|
||||
) -> Any:
|
||||
response = ""
|
||||
output.info("Running Solver")
|
||||
output.debug(f"Instruction: {instruction}")
|
||||
output.debug(f"Plan Evidence: {plan_evidence}")
|
||||
prompt = self._compose_prompt(instruction, plan_evidence, self.output_lang)
|
||||
output.debug(f"Prompt: {prompt}")
|
||||
try:
|
||||
for text in self.model.stream(prompt):
|
||||
response += text.text
|
||||
yield text
|
||||
output.info("Planner run successful.")
|
||||
except NotImplementedError:
|
||||
response = self.model(prompt).text
|
||||
output.info("Solver run successful.")
|
||||
except ValueError:
|
||||
output.error("Solver failed to retrieve response from LLM")
|
||||
|
||||
return response
|
||||