Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ IRC_USE_TLS=false
IRC_CMD_PREFIX=!

# AI agent provider settings (used by: python -m agentirc)
OPENAI_API_BASE=https://api.openai.com
OPENAI_API_KEY=
OPENAI_MODELS=

XAI_API_BASE=https://api.x.ai/v1
XAI_API_KEY=
XAI_MODELS=
Expand All @@ -21,7 +17,7 @@ LMSTUDIO_BASE_URL=http://127.0.0.1:1234/v1
LMSTUDIO_API_KEY=
LMSTUDIO_MODELS=

DEFAULT_MODEL=gpt-5-mini
DEFAULT_MODEL=grok-4-1-fast-non-reasoning
AGENTIRC_TOOLS=web_search,x_search,code_interpreter,mcp
AGENTIRC_MCP_SERVERS=[]
AGENTIRC_ADMINS=
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# agentirc

An AI-powered IRC agent built on a minimal async IRC bot framework. Supports multiple LLM providers (OpenAI, xAI, LM Studio) with per-user conversation history, tool use, and encrypted persistence.
An AI-powered IRC agent built on a minimal async IRC bot framework. Supports multiple LLM providers (xAI, LM Studio) with per-user conversation history, tool use, and encrypted persistence.

## Table of Contents

Expand Down
6 changes: 1 addition & 5 deletions agentirc/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ IRC_USE_TLS=false
IRC_CMD_PREFIX=!

# AI agent provider settings (used by: python -m agentirc)
OPENAI_API_BASE=https://api.openai.com
OPENAI_API_KEY=
OPENAI_MODELS=

XAI_API_BASE=https://api.x.ai/v1
XAI_API_KEY=
XAI_MODELS=
Expand All @@ -21,7 +17,7 @@ LMSTUDIO_BASE_URL=http://127.0.0.1:1234/v1
LMSTUDIO_API_KEY=
LMSTUDIO_MODELS=

DEFAULT_MODEL=gpt-5-mini
DEFAULT_MODEL=grok-4-1-fast-non-reasoning
AGENTIRC_TOOLS=web_search,x_search,code_interpreter,mcp
AGENTIRC_MCP_SERVERS=[]
AGENTIRC_ADMINS=
Expand Down
2 changes: 1 addition & 1 deletion agentirc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""agentirc -- AI-powered IRC agent using OpenAI-compatible Responses APIs."""
"""agentirc -- AI-powered IRC agent using Responses APIs."""

from .bot import ChatBot
from .config import ChatConfig
Expand Down
29 changes: 4 additions & 25 deletions agentirc/api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
"""OpenAI-compatible Responses API client."""
"""Responses API client."""

from __future__ import annotations

import json
import logging
import re
from typing import Any, Iterable

import httpx
Expand All @@ -27,7 +26,7 @@ def __init__(
system_prompt: str,
max_tokens: int,
enabled_tools: list[str],
provider: str = "openai",
provider: str = "xai",
) -> None:
self.api_base = api_base.rstrip("/")
self.api_key = api_key
Expand All @@ -41,9 +40,7 @@ def __init__(
def _fallback_base_url(provider: str) -> str:
if provider == "lmstudio":
return "http://127.0.0.1:1234/v1"
if provider == "xai":
return "https://api.x.ai/v1"
return "https://api.openai.com/v1"
return "https://api.x.ai/v1"

def _base_url(self, provider: str, api_base: str | None = None) -> str:
configured = str(api_base or self.api_base or "").strip()
Expand Down Expand Up @@ -113,25 +110,7 @@ def _is_chat_model(provider: str, model_id: str) -> bool:
blocked_fragments = ("imagine", "image", "video", "voice", "vision")
return not any(fragment in lowered for fragment in blocked_fragments)

prefixes = ("gpt-", "o1", "o3", "o4")
if not model_id.startswith(prefixes):
return False

blocked_fragments = (
"preview",
"audio",
"computer-use",
"transcribe",
"tts",
"image",
)
if any(fragment in lowered for fragment in blocked_fragments):
return False

if re.search(r"-\d{4}-\d{2}-\d{2}$", lowered):
return False

return True
return False

@staticmethod
def build_input_items(
Expand Down
4 changes: 1 addition & 3 deletions agentirc/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def _provider_for_model(self, model: str) -> str:
configured = [p for p in KNOWN_PROVIDERS if self._base_url(p)]
if len(configured) == 1:
return configured[0]
return "openai"
return "xai"

def _base_url(self, provider: str) -> str:
return str(self.config.base_urls.get(provider, "") or "").strip()
Expand Down Expand Up @@ -324,8 +324,6 @@ def _is_valid_model(self, model: str) -> bool:
def _provider_label(provider: str) -> str:
if provider == "xai":
return "xAI"
if provider == "openai":
return "OpenAI"
if provider == "lmstudio":
return "LM Studio"
return provider
Expand Down
12 changes: 1 addition & 11 deletions agentirc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,27 @@ def make_default_prompt(self, *, verbose: bool = False) -> str:
def from_env(cls) -> ChatConfig:
"""Build config from environment variables."""
load_env()
openai_models = _parse_csv(os.environ.get("OPENAI_MODELS"))
xai_models = _parse_csv(os.environ.get("XAI_MODELS"))
lmstudio_models = _parse_csv(os.environ.get("LMSTUDIO_MODELS"))

legacy_openai_model = os.environ.get("OPENAI_MODEL", "").strip()
if legacy_openai_model and legacy_openai_model not in openai_models:
openai_models = [legacy_openai_model, *openai_models]

default_model = os.environ.get("DEFAULT_MODEL", "").strip()
if not default_model:
default_model = (
legacy_openai_model
or (openai_models[0] if openai_models else "")
or (xai_models[0] if xai_models else "")
(xai_models[0] if xai_models else "")
or (lmstudio_models[0] if lmstudio_models else "")
)

return cls(
irc=BotConfig.from_env(),
models={
"openai": openai_models,
"xai": xai_models,
"lmstudio": lmstudio_models,
},
api_keys={
"openai": os.environ.get("OPENAI_API_KEY", "").strip(),
"xai": os.environ.get("XAI_API_KEY", "").strip(),
"lmstudio": os.environ.get("LMSTUDIO_API_KEY", "").strip(),
},
base_urls={
"openai": os.environ.get("OPENAI_API_BASE", "https://api.openai.com").strip(),
"xai": os.environ.get("XAI_API_BASE", "https://api.x.ai/v1").strip(),
"lmstudio": os.environ.get("LMSTUDIO_BASE_URL", "http://127.0.0.1:1234/v1").strip(),
},
Expand Down
29 changes: 4 additions & 25 deletions agentirc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

import json
import logging
import re
from urllib.error import URLError
from urllib.request import Request, urlopen

log = logging.getLogger(__name__)

KNOWN_PROVIDERS = ("openai", "xai", "lmstudio")
KNOWN_PROVIDERS = ("xai", "lmstudio")


def _models_url(api_base: str) -> str:
Expand All @@ -34,25 +33,7 @@ def _is_chat_model(provider: str, model_id: str) -> bool:
blocked_fragments = ("imagine", "image", "video", "voice", "vision")
return not any(fragment in lowered for fragment in blocked_fragments)

prefixes = ("gpt-", "o1", "o3", "o4")
if not model_id.startswith(prefixes):
return False

blocked_fragments = (
"preview",
"audio",
"computer-use",
"transcribe",
"tts",
"image",
)
if any(fragment in lowered for fragment in blocked_fragments):
return False

if re.search(r"-\d{4}-\d{2}-\d{2}$", lowered):
return False

return True
return False


def provider_for_model(model: str, models: dict[str, list[str]]) -> str | None:
Expand All @@ -67,12 +48,10 @@ def provider_for_model(model: str, models: dict[str, list[str]]) -> str | None:
lowered = selected.lower()
if lowered.startswith("grok-"):
return "xai"
if lowered.startswith(("gpt-", "o1", "o3", "o4")):
return "openai"
return None


def fetch_models(api_base: str, api_key: str = "", provider: str = "openai") -> list[str]:
def fetch_models(api_base: str, api_key: str = "", provider: str = "xai") -> list[str]:
"""GET models and return filtered chat-capable model IDs."""
url = _models_url(api_base)
req = Request(url, method="GET")
Expand Down Expand Up @@ -137,7 +116,7 @@ def pick_default_model(models: dict[str, list[str]], preferred: str = "") -> str
raise RuntimeError("No models available from configured providers")


def pick_model(api_base: str, api_key: str = "", preferred: str = "", provider: str = "openai") -> str:
def pick_model(api_base: str, api_key: str = "", preferred: str = "", provider: str = "xai") -> str:
"""Legacy helper: pick a model from a single provider endpoint."""
available = fetch_models(api_base, api_key, provider=provider)

Expand Down
27 changes: 8 additions & 19 deletions agentirc/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Tool definitions for OpenAI-compatible Responses APIs."""
"""Tool definitions for Responses APIs."""

from __future__ import annotations

Expand All @@ -9,23 +9,22 @@

def build_tools(
enabled: list[str],
provider: str = "openai",
provider: str = "xai",
*,
web_search_country: str = "",
mcp_servers: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
"""Build the tools list for the Responses API request.

Supported tools:
- web_search (openai, xai)
- web_search (xai)
- x_search (xai)
- code_interpreter (openai, xai)
- mcp (all providers)
- code_interpreter (xai)
"""
tool_builders: dict[str, tuple[set[str], Any]] = {
"web_search": ({"openai", "xai"}, _web_search_tool),
"web_search": ({"xai"}, _web_search_tool),
"x_search": ({"xai"}, _x_search_tool),
"code_interpreter": ({"openai", "xai"}, _code_interpreter_tool),
"code_interpreter": ({"xai"}, _code_interpreter_tool),
}

tools = []
Expand Down Expand Up @@ -67,18 +66,8 @@ def _x_search_tool(_provider: str) -> dict[str, Any]:
return {"type": "x_search"}


def _mcp_tool(server: dict[str, Any]) -> dict[str, Any]:
tool: dict[str, Any] = {"type": "mcp"}
tool.update(server)
tool.setdefault("require_approval", "never")
return tool


def _code_interpreter_tool(provider: str) -> dict[str, Any]:
tool = {"type": "code_interpreter"}
if provider == "openai":
tool["container"] = {"type": "auto"}
return tool
def _code_interpreter_tool(_provider: str) -> dict[str, Any]:
return {"type": "code_interpreter"}


def xai_model_supports_hosted_tools(model: str) -> bool:
Expand Down
12 changes: 4 additions & 8 deletions docs/ai-agent.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AI Agent

The `agentirc` package extends the base `ircbot` framework with multi-provider AI chat capabilities via OpenAI-compatible Responses APIs.
The `agentirc` package extends the base `ircbot` framework with multi-provider AI chat capabilities via Responses APIs.

## Architecture

Expand All @@ -19,11 +19,10 @@ agentirc/

## Providers

Three providers are supported out of the box:
Two providers are supported out of the box:

| Provider | Env Prefix | API Base Default |
|---|---|---|
| OpenAI | `OPENAI_*` | `https://api.openai.com` |
| xAI | `XAI_*` | `https://api.x.ai/v1` |
| LM Studio | `LMSTUDIO_*` | `http://127.0.0.1:1234/v1` |

Expand All @@ -35,9 +34,6 @@ All agentirc-specific variables (in addition to the [IRC variables](configuratio

| Variable | Default | Description |
|---|---|---|
| `OPENAI_API_BASE` | `https://api.openai.com` | OpenAI API base URL |
| `OPENAI_API_KEY` | | OpenAI API key |
| `OPENAI_MODELS` | | Comma-separated model list |
| `XAI_API_BASE` | `https://api.x.ai/v1` | xAI API base URL |
| `XAI_API_KEY` | | xAI API key |
| `XAI_MODELS` | | Comma-separated model list |
Expand All @@ -63,9 +59,9 @@ Tools are passed to the Responses API and executed server-side by the provider:

| Tool | Providers | Description |
|---|---|---|
| `web_search` | OpenAI, xAI | Web search with optional country bias |
| `web_search` | xAI | Web search with optional country bias |
| `x_search` | xAI | Search X/Twitter posts |
| `code_interpreter` | OpenAI, xAI | Execute code in a sandbox |
| `code_interpreter` | xAI | Execute code in a sandbox |

Tools are automatically filtered per-provider. xAI hosted tools (`web_search`, `x_search`, `code_interpreter`) require Grok 4+ models.

Expand Down
2 changes: 1 addition & 1 deletion docs/ai-output-disclaimer.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document explains the limits of responsibility for the agentirc bot ("the A
## 2) Definitions

- "App": This IRC chatbot and accompanying materials.
- "LLMs/Providers": Third-party large language models and services (e.g., OpenAI, xAI, LM Studio, or any OpenAI-compatible endpoint) that you enable and supply credentials for.
- "LLMs/Providers": Third-party large language models and services (e.g., xAI, LM Studio, or any compatible endpoint) that you enable and supply credentials for.
- "Outputs": Any text generated or triggered by the models.
- "You": Any user, administrator, or organization using or distributing the App.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ build-backend = "hatchling.build"

[project]
name = "agentirc"
version = "1.1.1"
version = "1.2.0"
description = "AI-powered IRC agent with multi-provider LLM support"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
keywords = ["irc", "bot", "ai", "llm", "chatbot", "agent", "openai", "ollama"]
keywords = ["irc", "bot", "ai", "llm", "chatbot", "agent", "xai", "grok"]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3",
Expand Down
Loading