Metadata-Version: 2.4
Name: kern-ai
Version: 0.1.0
Summary: Agent framework optimized for small models — template-based structured output, JSON repair, and LaTeX protection.
Author: Satadeep Dasgupta
License:                                  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.
        
              "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.
        
           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.
        
           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.
        
           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.
        
           7. Disclaimer of Warranty. Unless required by applicable law or
              agreed to in writing, Licensor provides the Work on an "AS IS"
              BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
        
           8. Limitation of Liability. In no event and under no legal theory,
              whether in tort, contract, or otherwise, shall any Contributor be
              liable to You for 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.
        
           END OF TERMS AND CONDITIONS
        
           Copyright 2025-2026 Agno Inc.
           Copyright 2026 Satadeep Dasgupta (Kern modifications)
        
           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.
        
Project-URL: Homepage, https://github.com/ndxrocks/kern
Project-URL: Repository, https://github.com/ndxrocks/kern
Keywords: agent,ai,llm,small-models,structured-output
Classifier: Intended Audience :: Developers
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: docstring-parser
Requires-Dist: gitpython
Requires-Dist: h11>=0.16.0
Requires-Dist: httpx[http2]
Requires-Dist: json-repair>=0.30.0
Requires-Dist: packaging
Requires-Dist: pydantic
Requires-Dist: pydantic-settings
Requires-Dist: python-dotenv
Requires-Dist: python-multipart
Requires-Dist: pyyaml
Requires-Dist: rich
Requires-Dist: typer
Requires-Dist: typing-extensions
Provides-Extra: dev
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pytest-mock; extra == "dev"
Requires-Dist: timeout-decorator; extra == "dev"
Requires-Dist: types-pyyaml; extra == "dev"
Requires-Dist: types-aiofiles; extra == "dev"
Requires-Dist: fastapi; extra == "dev"
Requires-Dist: uvicorn; extra == "dev"
Requires-Dist: PyJWT; extra == "dev"
Requires-Dist: mcp; extra == "dev"
Requires-Dist: openai; extra == "dev"
Provides-Extra: os
Requires-Dist: fastapi[standard]; extra == "os"
Requires-Dist: uvicorn; extra == "os"
Requires-Dist: sqlalchemy; extra == "os"
Requires-Dist: PyJWT; extra == "os"
Requires-Dist: opentelemetry-sdk; extra == "os"
Requires-Dist: openinference-instrumentation-agno; extra == "os"
Requires-Dist: croniter>=1.3; extra == "os"
Requires-Dist: pytz>=2023.3; extra == "os"
Provides-Extra: scheduler
Requires-Dist: croniter>=1.3; extra == "scheduler"
Requires-Dist: pytz>=2023.3; extra == "scheduler"
Provides-Extra: opentelemetry
Requires-Dist: opentelemetry-sdk; extra == "opentelemetry"
Requires-Dist: opentelemetry-exporter-otlp; extra == "opentelemetry"
Provides-Extra: anthropic
Requires-Dist: anthropic; extra == "anthropic"
Provides-Extra: azure
Requires-Dist: azure-ai-inference; extra == "azure"
Requires-Dist: aiohttp; extra == "azure"
Provides-Extra: cerebras
Requires-Dist: cerebras-cloud-sdk; extra == "cerebras"
Provides-Extra: cohere
Requires-Dist: cohere; extra == "cohere"
Provides-Extra: google
Requires-Dist: google-genai>=1.52.0; extra == "google"
Provides-Extra: groq
Requires-Dist: groq; extra == "groq"
Provides-Extra: ibm
Requires-Dist: ibm-watsonx-ai; extra == "ibm"
Provides-Extra: litellm
Requires-Dist: litellm; extra == "litellm"
Provides-Extra: mistral
Requires-Dist: mistralai; extra == "mistral"
Provides-Extra: ollama
Requires-Dist: ollama; extra == "ollama"
Provides-Extra: openai
Requires-Dist: openai; extra == "openai"
Provides-Extra: tokenizers
Requires-Dist: tiktoken; extra == "tokenizers"
Requires-Dist: tokenizers; extra == "tokenizers"
Provides-Extra: ddg
Requires-Dist: ddgs; extra == "ddg"
Provides-Extra: exa
Requires-Dist: exa_py>=2.0.0; extra == "exa"
Provides-Extra: firecrawl
Requires-Dist: firecrawl-py; extra == "firecrawl"
Provides-Extra: tavily
Requires-Dist: tavily-python; extra == "tavily"
Provides-Extra: github
Requires-Dist: PyGithub; extra == "github"
Provides-Extra: mcp
Requires-Dist: mcp>=1.9.2; extra == "mcp"
Provides-Extra: yfinance
Requires-Dist: yfinance; extra == "yfinance"
Provides-Extra: youtube
Requires-Dist: youtube_transcript_api; extra == "youtube"
Provides-Extra: newspaper
Requires-Dist: newspaper4k; extra == "newspaper"
Requires-Dist: lxml_html_clean; extra == "newspaper"
Provides-Extra: matplotlib
Requires-Dist: matplotlib; extra == "matplotlib"
Provides-Extra: browserbase
Requires-Dist: browserbase; extra == "browserbase"
Requires-Dist: playwright; extra == "browserbase"
Provides-Extra: sql
Requires-Dist: sqlalchemy; extra == "sql"
Provides-Extra: postgres
Requires-Dist: psycopg-binary; extra == "postgres"
Provides-Extra: sqlite
Requires-Dist: sqlalchemy; extra == "sqlite"
Requires-Dist: aiosqlite; extra == "sqlite"
Provides-Extra: redis
Requires-Dist: redis; extra == "redis"
Requires-Dist: redisvl>=0.12.1; extra == "redis"
Provides-Extra: mongo
Requires-Dist: pymongo>=4.9; extra == "mongo"
Requires-Dist: motor; extra == "mongo"
Provides-Extra: pgvector
Requires-Dist: pgvector; extra == "pgvector"
Provides-Extra: chromadb
Requires-Dist: chromadb; extra == "chromadb"
Provides-Extra: lancedb
Requires-Dist: lancedb>=0.26.0; extra == "lancedb"
Requires-Dist: tantivy; extra == "lancedb"
Provides-Extra: qdrant
Requires-Dist: qdrant-client; extra == "qdrant"
Provides-Extra: milvusdb
Requires-Dist: pymilvus>=2.5.10; extra == "milvusdb"
Provides-Extra: pinecone
Requires-Dist: pinecone; extra == "pinecone"
Provides-Extra: pdf
Requires-Dist: pypdf; extra == "pdf"
Requires-Dist: rapidocr_onnxruntime; extra == "pdf"
Provides-Extra: docx
Requires-Dist: python-docx; extra == "docx"
Provides-Extra: csv
Requires-Dist: aiofiles; extra == "csv"
Provides-Extra: markdown
Requires-Dist: unstructured; extra == "markdown"
Requires-Dist: markdown; extra == "markdown"
Requires-Dist: aiofiles; extra == "markdown"
Provides-Extra: models
Requires-Dist: kern-ai[anthropic]; extra == "models"
Requires-Dist: kern-ai[azure]; extra == "models"
Requires-Dist: kern-ai[cerebras]; extra == "models"
Requires-Dist: kern-ai[cohere]; extra == "models"
Requires-Dist: kern-ai[google]; extra == "models"
Requires-Dist: kern-ai[groq]; extra == "models"
Requires-Dist: kern-ai[ibm]; extra == "models"
Requires-Dist: kern-ai[mistral]; extra == "models"
Requires-Dist: kern-ai[ollama]; extra == "models"
Requires-Dist: kern-ai[openai]; extra == "models"
Provides-Extra: tools
Requires-Dist: kern-ai[ddg]; extra == "tools"
Requires-Dist: kern-ai[exa]; extra == "tools"
Requires-Dist: kern-ai[firecrawl]; extra == "tools"
Requires-Dist: kern-ai[tavily]; extra == "tools"
Requires-Dist: kern-ai[github]; extra == "tools"
Requires-Dist: kern-ai[mcp]; extra == "tools"
Requires-Dist: kern-ai[yfinance]; extra == "tools"
Requires-Dist: kern-ai[youtube]; extra == "tools"
Requires-Dist: kern-ai[newspaper]; extra == "tools"
Requires-Dist: kern-ai[matplotlib]; extra == "tools"
Requires-Dist: kern-ai[browserbase]; extra == "tools"
Provides-Extra: storage
Requires-Dist: kern-ai[sql]; extra == "storage"
Requires-Dist: kern-ai[postgres]; extra == "storage"
Requires-Dist: kern-ai[sqlite]; extra == "storage"
Requires-Dist: kern-ai[redis]; extra == "storage"
Requires-Dist: kern-ai[mongo]; extra == "storage"
Provides-Extra: vectordbs
Requires-Dist: kern-ai[pgvector]; extra == "vectordbs"
Requires-Dist: kern-ai[chromadb]; extra == "vectordbs"
Requires-Dist: kern-ai[lancedb]; extra == "vectordbs"
Requires-Dist: kern-ai[qdrant]; extra == "vectordbs"
Requires-Dist: kern-ai[milvusdb]; extra == "vectordbs"
Requires-Dist: kern-ai[pinecone]; extra == "vectordbs"
Provides-Extra: knowledge
Requires-Dist: kern-ai[pdf]; extra == "knowledge"
Requires-Dist: kern-ai[docx]; extra == "knowledge"
Requires-Dist: kern-ai[csv]; extra == "knowledge"
Requires-Dist: kern-ai[markdown]; extra == "knowledge"
Provides-Extra: all
Requires-Dist: kern-ai[models]; extra == "all"
Requires-Dist: kern-ai[tools]; extra == "all"
Requires-Dist: kern-ai[storage]; extra == "all"
Requires-Dist: kern-ai[vectordbs]; extra == "all"
Requires-Dist: kern-ai[knowledge]; extra == "all"
Requires-Dist: kern-ai[opentelemetry]; extra == "all"
Dynamic: license-file

<div align="center" id="top">
    <picture>
      <source width="100" media="(prefers-color-scheme: dark)" srcset="kern/docs/assets/kern-dark.png">
      <source width="100" media="(prefers-color-scheme: light)" srcset="kern/docs/assets/kern-light.png">
      <img width="100" src="kern/docs/assets/kern-light.png" alt="Agno">
    </picture>
</div>

<p align="center">
  Small Models. Big Impact.<br/>
</p>

Agent framework optimized for small models (1-7B parameters). Kern generates simple fill-in-the-blanks JSON templates instead of complex JSON Schema, so small models actually produce valid structured output.

## Install

```bash
pip install kern
```

With extras:

```bash
pip install kern[openai]       # OpenAI-compatible models
pip install kern[ollama]       # Ollama
pip install kern[anthropic]    # Claude
pip install kern[google]       # Gemini
pip install kern[ddg,mcp]      # DuckDuckGo search + MCP tools
pip install kern[all]          # Everything
```

## Quick Start

### Basic Agent

```python
from kern.agent import Agent
from kern.models.openai import OpenAIChat

agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    instructions="You are a helpful assistant.",
)

result = agent.run("What is the capital of France?")
print(result.content)  # "Paris"
```

### Structured Output

```python
from pydantic BaseModel, Field
from kern.agent import Agent
from kern.models.openai import OpenAIChat


class BookReview(BaseModel):
    title: str = Field(description="Book title")
    rating: int = Field(description="Rating out of 5")
    summary: str = Field(description="One-paragraph summary")
    recommended: bool


agent = Agent(
    model=OpenAIChat(id="gpt-4o-mini"),
    output_schema=BookReview,
)

result = agent.run("Review 'The Hitchhiker's Guide to the Galaxy'")
print(result.content)
# BookReview(
#     title="The Hitchhiker's Guide to the Galaxy",
#     rating=5,
#     summary="...",
#     recommended=True
# )
```

### Running with Local Models

Kern shines with local small models via OpenAI-compatible servers (llama.cpp, LM Studio, vLLM, Ollama):

```python
from kern.agent import Agent
from kern.models.openai import OpenAIChat

# Connect to any OpenAI-compatible local server
model = OpenAIChat(
    id="local-model",                    # model name (ignored by some servers)
    base_url="http://127.0.0.1:8080/v1", # your local server
    api_key="not-needed",                # placeholder for local inference
)

agent = Agent(model=model, output_schema=BookReview)
result = agent.run("Review 'Dune' by Frank Herbert")
```

## Models

Kern supports any OpenAI-compatible model provider:

| Provider              | Install           | Usage                                           |
| --------------------- | ----------------- | ----------------------------------------------- |
| OpenAI                | `kern[openai]`    | `from kern.models.openai import OpenAIChat`     |
| Anthropic             | `kern[anthropic]` | `from kern.models.anthropic import Claude`      |
| Google Gemini         | `kern[google]`    | `from kern.models.google import Gemini`         |
| Ollama                | `kern[ollama]`    | `from kern.models.ollama import Ollama`         |
| Groq                  | `kern[groq]`      | `from kern.models.groq import Groq`             |
| Cerebras              | `kern[cerebras]`  | `from kern.models.cerebras import Cerebras`     |
| Mistral               | `kern[mistral]`   | `from kern.models.mistral import MistralChat`   |
| Azure                 | `kern[azure]`     | `from kern.models.azure import AzureOpenAIChat` |
| Any OpenAI-compatible | —                 | `OpenAIChat(base_url="...", api_key="...")`     |

## Agents

### System Instructions

```python
agent = Agent(
    model=model,
    instructions=[
        "You are a math tutor for high school students.",
        "Always show your work step by step.",
        "Use LaTeX notation for equations.",
    ],
)
```

### Agent with Tools

```python
from kern.agent import Agent
from kern.tools.duckduckgo import DuckDuckGoTools

agent = Agent(
    model=model,
    tools=[DuckDuckGoTools()],
    instructions="Search the web to answer questions.",
    show_tool_calls=True,
)

result = agent.run("What's the latest news about quantum computing?")
```

### Agent Teams

```python
from kern.agent import Agent
from kern.team import Team

researcher = Agent(name="Researcher", model=model, tools=[DuckDuckGoTools()])
writer = Agent(name="Writer", model=model, instructions="Write clear, engaging prose.")

team = Team(
    name="Content Team",
    mode="coordinate",   # agents collaborate
    members=[researcher, writer],
)

result = team.run("Write a brief on AI safety")
```

### Multi-turn Conversations

```python
agent = Agent(model=model)

# Each call continues the conversation
r1 = agent.run("My name is Alice")
r2 = agent.run("What's my name?")  # remembers "Alice"
```

### Streaming

```python
agent = Agent(model=model)

for chunk in agent.run("Explain photosynthesis", stream=True):
    print(chunk.content, end="", flush=True)
```

## Structured Output (Templates)

This is where Kern differs from other frameworks. Instead of sending complex JSON Schema (`$defs`, `properties`, `anyOf`, `allOf`), Kern generates flat fill-in-the-blanks templates.

### Simple Models

```python
class Recipe(BaseModel):
    name: str
    ingredients: list[str]
    cook_time_minutes: int
```

Template sent to the model:

```json
{ "name": "string", "ingredients": ["string"], "cook_time_minutes": "integer" }
```

### Nested Models

```python
class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Person(BaseModel):
    name: str
    address: Address
```

Template:

```json
{
  "name": "string",
  "address": { "street": "string", "city": "string", "zip_code": "string" }
}
```

### Union Types

```python
from typing import Union

class TextBlock(BaseModel):
    text: str

class CodeBlock(BaseModel):
    code: str
    language: str

class Page(BaseModel):
    blocks: list[Union[TextBlock, CodeBlock]]
```

Template — both alternatives shown flat:

```json
{ "blocks": [{ "text": "string" }, { "code": "string", "language": "string" }] }
```

### Literal Enums

```python
from typing import Literal

class Article(BaseModel):
    title: str
    status: Literal["draft", "published", "archived"]
```

Template:

```json
{"title": "string", "status": "draft"|"published"|"archived"}
```

### Field Descriptions

```python
class Quiz(BaseModel):
    question: str = Field(description="The quiz question")
    options: list[str] = Field(description="4 multiple choice options")
    answer: int = Field(description="Index of the correct option (0-3)")
```

Template includes a separate descriptions block so the model knows what each field means.

## JSON Repair

Small models produce malformed JSON — missing quotes, trailing commas, broken escapes. Kern fixes it automatically:

````python
from kern.repair import extract_json

# Handles markdown code blocks, leading text, LaTeX, malformed JSON
data = extract_json("""
Here's the result:
```json
{"title": "Hello World", "items": [1, 2, 3,]}
````

""")

# {"title": "Hello World", "items": [1, 2, 3]}

````

### LaTeX Protection

When models output math like `\frac{a}{b}`, JSON parsers break because `\f` is a form-feed escape character. Kern doubles backslashes before parsing:

```python
from kern.repair import extract_json

data = extract_json('{"formula": "\\frac{1}{2} + \\theta"}')
# Parsed correctly — LaTeX preserved
````

## Tools

```python
from kern.tools import (
    DuckDuckGoTools,    # pip install kern[ddg]
    ExaTools,           # pip install kern[exa]
    FirecrawlTools,     # pip install kern[firecrawl]
    TavilyTools,        # pip install kern[tavily]
    GitHubTools,        # pip install kern[github]
    MCPTools,           # pip install kern[mcp]
    YFinanceTools,      # pip install kern[yfinance]
    NewspaperTools,     # pip install kern[newspaper]
    CalculatorTools,    # built-in
    PythonTools,        # built-in
    FileTools,          # built-in
)
```

### Custom Tools

```python
from kern.tools import Toolkit

class MyTools(Toolkit):
    def __init__(self):
        super().__init__(name="my_tools")
        self.register(self.get_weather)

    def get_weather(self, city: str) -> str:
        """Get the current weather for a city."""
        return f"The weather in {city} is sunny and 72°F"

agent = Agent(model=model, tools=[MyTools()])
```

## Storage

```python
from kern.agent import Agent
from kern.storage.agent.postgres import PgAgentStorage  # kern[postgres]

agent = Agent(
    model=model,
    storage=PgAgentStorage(
        table_name="agent_sessions",
        db_url="postgresql://localhost:5432/mydb",
    ),
)
```

Supported: Postgres, SQLite, Redis, MongoDB, GCS, Firestore, MySQL.

## Knowledge Bases

```python
from kern.knowledge.text import TextKnowledgeBase
from kern.vectordb.pgvector import PgVector  # kern[pgvector]

knowledge = TextKnowledgeBase(
    vector_db=PgVector(
        table_name="recipes",
        db_url="postgresql://localhost:5432/mydb",
    ),
)

agent = Agent(model=model, knowledge=knowledge)
agent.knowledge.load(references=["path/to/recipes.txt"])
```

## Workflows

```python
from kern.workflows import Workflow

class ResearchWorkflow(Workflow):
    research_step: Agent = Field(...)
    write_step: Agent = Field(...)

    def run(self, topic: str):
        research = self.research_step.run(f"Research {topic}")
        article = self.write_step.run(f"Write about: {research.content}")
        return article

wf = ResearchWorkflow(
    research_step=Agent(name="Researcher", tools=[DuckDuckGoTools()]),
    write_step=Agent(name="Writer"),
)
result = wf.run(topic="renewable energy")
```

## License

Apache License 2.0
