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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- bug/370-anthropic-streaming (2025-09-16)

### Changed
- feat/464-update-input-support-multiple-keys (2025-11-11)
- feat/490-can-read-deepagents-agent-files (2025-11-10)
- feat/485-user-env (2025-11-08)
- feat/482-teams-webhook (2025-11-05)
Expand Down
129 changes: 84 additions & 45 deletions backend/src/constants/llm.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,97 @@
from enum import Enum
import os
from enum import Enum
from src.services.llm import llm_service
from src.constants import (
OPENAI_API_KEY,
ANTHROPIC_API_KEY,
OLLAMA_BASE_URL,
GROQ_API_KEY,
GOOGLE_API_KEY,
XAI_API_KEY,
OPENAI_API_KEY,
ANTHROPIC_API_KEY,
OLLAMA_BASE_URL,
GROQ_API_KEY,
GOOGLE_API_KEY,
XAI_API_KEY,
)
from src.utils.logger import logger


class ChatModels(str, Enum):
if OPENAI_API_KEY:
OPENAI_REASONING_03 = "openai:o3"
OPENAI_REASONING_04_MINI = "openai:o4-mini"
OPENAI_GPT_5_NANO = "openai:gpt-5-nano"
OPENAI_GPT_5_MINI = "openai:gpt-5-mini"
OPENAI_GPT_5 = "openai:gpt-5"
# OPENAI_GPT_5_CODEX = "openai:gpt-5-codex"
if ANTHROPIC_API_KEY:
ANTHROPIC_CLAUDE_3_7_SONNET = "anthropic:claude-3-7-sonnet-latest"
ANTHROPIC_CLAUDE_4_SONNET = "anthropic:claude-sonnet-4"
ANTHROPIC_CLAUDE_4_OPUS = "anthropic:claude-opus-4-1"
ANTHROPIC_CLAUDE_4_5_HAIKU = "anthropic:claude-haiku-4-5"
ANTHROPIC_CLAUDE_4_5_SONNET = "anthropic:claude-sonnet-4-5"
if XAI_API_KEY:
XAI_GROK_4 = "xai:grok-4"
XAI_GROK_4_FAST = "xai:grok-4-fast"
XAI_GROK_4_FAST_NON_REASONING = "xai:grok-4-fast-non-reasoning"
XAI_GROK_CODE_FAST_1 = "xai:grok-code-fast-1"
if GOOGLE_API_KEY:
GOOGLE_GEMINI_2_5_FLASH_LITE = "google_genai:gemini-2.5-flash-lite"
GOOGLE_GEMINI_2_5_FLASH = "google_genai:gemini-2.5-flash"
GOOGLE_GEMINI_2_5_PRO = "google_genai:gemini-2.5-pro"
if GROQ_API_KEY:
GROQ_OPENAI_GPT_OSS_120B = "groq:openai/gpt-oss-120b"
GROQ_LLAMA_3_3_70B_VERSATILE = "groq:llama-3.3-70b-versatile"
if OLLAMA_BASE_URL:
OLLAMA_QWEN3 = "ollama:qwen3"

if OPENAI_API_KEY:
OPENAI_REASONING_03 = "openai:o3"
OPENAI_REASONING_04_MINI = "openai:o4-mini"
OPENAI_GPT_5_NANO = "openai:gpt-5-nano"
OPENAI_GPT_5_MINI = "openai:gpt-5-mini"
OPENAI_GPT_5 = "openai:gpt-5"
# OPENAI_GPT_5_CODEX = "openai:gpt-5-codex"
if ANTHROPIC_API_KEY:
ANTHROPIC_CLAUDE_3_7_SONNET = "anthropic:claude-3-7-sonnet-latest"
ANTHROPIC_CLAUDE_4_SONNET = "anthropic:claude-sonnet-4"
ANTHROPIC_CLAUDE_4_OPUS = "anthropic:claude-opus-4-1"
ANTHROPIC_CLAUDE_4_5_HAIKU = "anthropic:claude-haiku-4-5"
ANTHROPIC_CLAUDE_4_5_SONNET = "anthropic:claude-sonnet-4-5"
if XAI_API_KEY:
XAI_GROK_4 = "xai:grok-4"
XAI_GROK_4_FAST = "xai:grok-4-fast"
XAI_GROK_4_FAST_NON_REASONING = "xai:grok-4-fast-non-reasoning"
XAI_GROK_CODE_FAST_1 = "xai:grok-code-fast-1"
if GOOGLE_API_KEY:
GOOGLE_GEMINI_2_5_FLASH_LITE = "google_genai:gemini-2.5-flash-lite"
GOOGLE_GEMINI_2_5_FLASH = "google_genai:gemini-2.5-flash"
GOOGLE_GEMINI_2_5_PRO = "google_genai:gemini-2.5-pro"
if GROQ_API_KEY:
GROQ_OPENAI_GPT_OSS_120B = "groq:openai/gpt-oss-120b"
GROQ_LLAMA_3_3_70B_VERSATILE = "groq:llama-3.3-70b-versatile"


def get_ollama_models():
models = []
try:
import requests
response = requests.get(f"{OLLAMA_BASE_URL.rstrip('/')}/api/tags", timeout=3)
if response.ok:
data = response.json()
tags = data.get("models", []) if isinstance(data, dict) else []
for tag in tags:
model_name = tag.get("name")
if model_name:
models.append(f"ollama:{model_name}")
except Exception as e:
logger.error(f"Error getting Ollama models: {e}")
pass
return models

def get_all_models():
models = []
if OPENAI_API_KEY:
models.extend(llm_service.model_by_provider(provider="openai"))
if ANTHROPIC_API_KEY:
models.extend(llm_service.model_by_provider(provider="anthropic"))
if GOOGLE_API_KEY:
models.extend(llm_service.model_by_provider(provider="google"))
if GROQ_API_KEY:
models.extend(llm_service.model_by_provider(provider="groq"))
if XAI_API_KEY:
models.extend(llm_service.model_by_provider(provider="xai"))
if OLLAMA_BASE_URL:
models.extend(get_ollama_models())
return sorted(models)

def get_free_models():
return [
ChatModels.ANTHROPIC_CLAUDE_4_5_HAIKU.value,
ChatModels.OPENAI_GPT_5_NANO.value,
ChatModels.GOOGLE_GEMINI_2_5_FLASH_LITE.value,
ChatModels.GROQ_OPENAI_GPT_OSS_120B.value,
ChatModels.GROQ_LLAMA_3_3_70B_VERSATILE.value,
]
models = []
if OPENAI_API_KEY:
models.append(ChatModels.OPENAI_GPT_5_NANO.value)
if ANTHROPIC_API_KEY:
models.append(ChatModels.ANTHROPIC_CLAUDE_4_5_HAIKU.value)
if GOOGLE_API_KEY:
models.append(ChatModels.GOOGLE_GEMINI_2_5_FLASH_LITE.value)
if GROQ_API_KEY:
models.append(ChatModels.GROQ_OPENAI_GPT_OSS_120B.value)
if OLLAMA_BASE_URL:
models.extend(get_ollama_models())
return sorted(models)


def get_system_prompt():
path = "src/static/prompts/md"
with open(os.path.join(path, "default.md"), "r") as file:
return file.read()
path = "src/static/prompts/md"
with open(os.path.join(path, "default.md"), "r") as file:
return file.read()


DEFAULT_SYSTEM_PROMPT = get_system_prompt()
43 changes: 23 additions & 20 deletions backend/src/flows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
from deepagents import SubAgent, create_deep_agent


from src.schemas.models.auth import ProtectedUser
from src.services.memory import memory_service
from src.services.tool import tool_service
from src.tools.memory import MEMORY_TOOLS
from src.schemas.entities import LLMRequest, LLMStreamRequest
from src.schemas.entities import LLMRequest
from src.utils.logger import logger
from src.utils.format import init_system_prompt
from src.schemas.contexts import ContextSchema
from src.schemas.entities.a2a import A2AServers
from src.utils.middleware import add_ai_message_metadata, pii_middleware


async def add_memories_to_system():
Expand Down Expand Up @@ -50,7 +52,7 @@ def graph_builder(
subagents: list[SubAgent] = [],
prompt: str = "You are a helpful assistant.",
model: str = "openai:gpt-5-nano",
context_schema: Type[Any] | None = None,
context_schema: Type[ContextSchema] | None = None,
checkpointer: BaseCheckpointSaver | None = None,
store: BaseStore | None = None,
graph_id: Literal[
Expand All @@ -74,6 +76,7 @@ def graph_builder(
system_prompt=prompt,
checkpointer=checkpointer,
context_schema=context_schema,
middleware=[add_ai_message_metadata] + pii_middleware(),
store=store,
)
return deep_agent
Expand All @@ -96,7 +99,7 @@ async def init_tools(
return tools


async def init_subagents(params: LLMRequest | LLMStreamRequest) -> list[SubAgent]:
async def init_subagents(params: LLMRequest) -> list[SubAgent]:
result = []
for subagent in params.subagents:
subagent_dict = {
Expand All @@ -120,22 +123,28 @@ async def init_memories(system_prompt: str, tools: list[BaseTool]):
return tools + MEMORY_TOOLS, prompt


def init_config(params: LLMRequest | LLMStreamRequest):
if params.metadata:
return RunnableConfig(
configurable=params.metadata.model_dump(),
max_concurrency=10,
recursion_limit=100,
)
else:
return None
def init_config(
params: LLMRequest,
user: ProtectedUser | None = None,
max_concurrency: int = 4,
recursion_limit: int = 100,
) -> RunnableConfig:
return RunnableConfig(
configurable={
"user_id": user.id if user else None,
"thread_id": params.metadata.thread_id or str(uuid4()),
"assistant_id": params.metadata.assistant_id or None,
},
max_concurrency=max_concurrency,
recursion_limit=recursion_limit,
metadata={**params.metadata.model_dump()},
)


################################################################################
### Construct Agent
################################################################################
async def construct_agent(
# params: LLMRequest | LLMStreamRequest,
system_prompt: str,
tools: list[BaseTool],
model: BaseChatModel,
Expand Down Expand Up @@ -218,10 +227,4 @@ def astream(
) -> AsyncGenerator[BaseMessage, None]:
return self.graph.astream(
messages, config=config, stream_mode=stream_mode, context=context
)

async def aget_state(self, config: RunnableConfig = None):
# if config is None:
# config = self.config
state = await self.graph.aget_state(config)
return state
)
1 change: 0 additions & 1 deletion backend/src/routes/v0/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from src.utils.auth import verify_credentials
from src.utils.logger import logger
from src.services.assistant import (
assistant_service,
AssistantSearch,
Assistant,
ASSISTANT_EXAMPLES,
Expand Down
Loading