[Infra] CI/CD Fixes (#16937)

* Attempt CI/CD Fix

* Adding test for coverage

* Adding max depth to copilot and vertex

* Fixing mypy lint and docker database

* Fixing UI build issues

* Update playwright test
This commit is contained in:
yuneng-jiang
2025-11-21 13:58:19 -08:00
committed by GitHub
parent f9d8eeaf8e
commit 4b25398afe
20 changed files with 462 additions and 160 deletions
+4 -1
View File
@@ -12,7 +12,10 @@ WORKDIR /app
USER root
# Install build dependencies
RUN apk add --no-cache gcc python3-dev openssl openssl-dev
RUN apk add --no-cache \
build-base \
python3-dev \
openssl-dev
RUN pip install --upgrade pip && \
-1
View File
@@ -18,7 +18,6 @@ from typing import Any, Coroutine, Dict, Literal, Optional, Union, cast
import httpx
from openai.types.batch import BatchRequestCounts
from openai.types.batch import Metadata as BatchMetadata
import litellm
from litellm._logging import verbose_logger
+169 -88
View File
@@ -1246,18 +1246,168 @@ class AWSEventStreamDecoder:
thinking_blocks_list.append(_thinking_block)
return thinking_blocks_list
def _initialize_converse_response_id(self, chunk_data: dict):
"""Initialize response_id from chunk data if not already set."""
if self.response_id is None:
if "messageStart" in chunk_data:
conversation_id = chunk_data["messageStart"].get("conversationId")
if conversation_id:
self.response_id = f"chatcmpl-{conversation_id}"
else:
# Fallback to generating a UUID if the first chunk is not messageStart
self.response_id = f"chatcmpl-{uuid.uuid4()}"
def _handle_converse_start_event(
self,
start_obj: ContentBlockStartEvent,
) -> tuple[
Optional[ChatCompletionToolCallChunk],
dict,
Optional[
List[
Union[
ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock
]
]
],
]:
"""Handle 'start' event in converse chunk parsing."""
tool_use: Optional[ChatCompletionToolCallChunk] = None
provider_specific_fields: dict = {}
thinking_blocks: Optional[
List[
Union[
ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock
]
]
] = None
self.content_blocks = [] # reset
if start_obj is not None:
if "toolUse" in start_obj and start_obj["toolUse"] is not None:
## check tool name was formatted by litellm
_response_tool_name = start_obj["toolUse"]["name"]
response_tool_name = get_bedrock_tool_name(
response_tool_name=_response_tool_name
)
self.tool_calls_index = (
0
if self.tool_calls_index is None
else self.tool_calls_index + 1
)
tool_use = {
"id": start_obj["toolUse"]["toolUseId"],
"type": "function",
"function": {
"name": response_tool_name,
"arguments": "",
},
"index": self.tool_calls_index,
}
elif (
"reasoningContent" in start_obj
and start_obj["reasoningContent"] is not None
): # redacted thinking can be in start object
thinking_blocks = self.translate_thinking_blocks(
start_obj["reasoningContent"]
)
provider_specific_fields = {
"reasoningContent": start_obj["reasoningContent"],
}
return tool_use, provider_specific_fields, thinking_blocks
def _handle_converse_delta_event(
self,
delta_obj: ContentBlockDeltaEvent,
index: int,
) -> tuple[
str,
Optional[ChatCompletionToolCallChunk],
dict,
Optional[str],
Optional[
List[
Union[
ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock
]
]
],
]:
"""Handle 'delta' event in converse chunk parsing."""
text = ""
tool_use: Optional[ChatCompletionToolCallChunk] = None
provider_specific_fields: dict = {}
reasoning_content: Optional[str] = None
thinking_blocks: Optional[
List[
Union[
ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock
]
]
] = None
self.content_blocks.append(delta_obj)
if "text" in delta_obj:
text = delta_obj["text"]
elif "toolUse" in delta_obj:
tool_use = {
"id": None,
"type": "function",
"function": {
"name": None,
"arguments": delta_obj["toolUse"]["input"],
},
"index": (
self.tool_calls_index
if self.tool_calls_index is not None
else index
),
}
elif "reasoningContent" in delta_obj:
provider_specific_fields = {
"reasoningContent": delta_obj["reasoningContent"],
}
reasoning_content = self.extract_reasoning_content_str(
delta_obj["reasoningContent"]
)
thinking_blocks = self.translate_thinking_blocks(
delta_obj["reasoningContent"]
)
if (
thinking_blocks
and len(thinking_blocks) > 0
and reasoning_content is None
):
reasoning_content = "" # set to non-empty string to ensure consistency with Anthropic
return text, tool_use, provider_specific_fields, reasoning_content, thinking_blocks
def _handle_converse_stop_event(
self, index: int
) -> Optional[ChatCompletionToolCallChunk]:
"""Handle stop/contentBlockIndex event in converse chunk parsing."""
tool_use: Optional[ChatCompletionToolCallChunk] = None
is_empty = self.check_empty_tool_call_args()
if is_empty:
tool_use = {
"id": None,
"type": "function",
"function": {
"name": None,
"arguments": "{}",
},
"index": (
self.tool_calls_index
if self.tool_calls_index is not None
else index
),
}
return tool_use
def converse_chunk_parser(self, chunk_data: dict) -> ModelResponseStream:
try:
# Capture the conversationId from the first messageStart event
# and use it as the consistent ID for all subsequent chunks.
if self.response_id is None:
if "messageStart" in chunk_data:
conversation_id = chunk_data["messageStart"].get("conversationId")
if conversation_id:
self.response_id = f"chatcmpl-{conversation_id}"
else:
# Fallback to generating a UUID if the first chunk is not messageStart
self.response_id = f"chatcmpl-{uuid.uuid4()}"
self._initialize_converse_response_id(chunk_data)
verbose_logger.debug("\n\nRaw Chunk: {}\n\n".format(chunk_data))
text = ""
@@ -1277,91 +1427,22 @@ class AWSEventStreamDecoder:
index = int(chunk_data.get("contentBlockIndex", 0))
if "start" in chunk_data:
start_obj = ContentBlockStartEvent(**chunk_data["start"])
self.content_blocks = [] # reset
if start_obj is not None:
if "toolUse" in start_obj and start_obj["toolUse"] is not None:
## check tool name was formatted by litellm
_response_tool_name = start_obj["toolUse"]["name"]
response_tool_name = get_bedrock_tool_name(
response_tool_name=_response_tool_name
)
self.tool_calls_index = (
0
if self.tool_calls_index is None
else self.tool_calls_index + 1
)
tool_use = {
"id": start_obj["toolUse"]["toolUseId"],
"type": "function",
"function": {
"name": response_tool_name,
"arguments": "",
},
"index": self.tool_calls_index,
}
elif (
"reasoningContent" in start_obj
and start_obj["reasoningContent"] is not None
): # redacted thinking can be in start object
thinking_blocks = self.translate_thinking_blocks(
start_obj["reasoningContent"]
)
provider_specific_fields = {
"reasoningContent": start_obj["reasoningContent"],
}
tool_use, provider_specific_fields, thinking_blocks = (
self._handle_converse_start_event(start_obj)
)
elif "delta" in chunk_data:
delta_obj = ContentBlockDeltaEvent(**chunk_data["delta"])
self.content_blocks.append(delta_obj)
if "text" in delta_obj:
text = delta_obj["text"]
elif "toolUse" in delta_obj:
tool_use = {
"id": None,
"type": "function",
"function": {
"name": None,
"arguments": delta_obj["toolUse"]["input"],
},
"index": (
self.tool_calls_index
if self.tool_calls_index is not None
else index
),
}
elif "reasoningContent" in delta_obj:
provider_specific_fields = {
"reasoningContent": delta_obj["reasoningContent"],
}
reasoning_content = self.extract_reasoning_content_str(
delta_obj["reasoningContent"]
)
thinking_blocks = self.translate_thinking_blocks(
delta_obj["reasoningContent"]
)
if (
thinking_blocks
and len(thinking_blocks) > 0
and reasoning_content is None
):
reasoning_content = "" # set to non-empty string to ensure consistency with Anthropic
(
text,
tool_use,
provider_specific_fields,
reasoning_content,
thinking_blocks,
) = self._handle_converse_delta_event(delta_obj, index)
elif (
"contentBlockIndex" in chunk_data
): # stop block, no 'start' or 'delta' object
is_empty = self.check_empty_tool_call_args()
if is_empty:
tool_use = {
"id": None,
"type": "function",
"function": {
"name": None,
"arguments": "{}",
},
"index": (
self.tool_calls_index
if self.tool_calls_index is not None
else index
),
}
tool_use = self._handle_converse_stop_event(index)
elif "stopReason" in chunk_data:
finish_reason = map_finish_reason(chunk_data.get("stopReason", "stop"))
elif "usage" in chunk_data:
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from uuid import uuid4
from litellm._logging import verbose_logger
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
from litellm.exceptions import AuthenticationError
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
from litellm.types.llms.openai import (
@@ -273,18 +274,29 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
"""
return self._contains_vision_content(input_param)
def _contains_vision_content(self, value: Any) -> bool:
def _contains_vision_content(
self, value: Any, depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH
) -> bool:
"""
Recursively check if a value contains vision content.
Looks for items with type="input_image" in the structure.
"""
if depth > max_depth:
verbose_logger.warning(
f"[GitHub Copilot] Max recursion depth {max_depth} reached while checking for vision content"
)
return False
if value is None:
return False
# Check arrays
if isinstance(value, list):
return any(self._contains_vision_content(item) for item in value)
return any(
self._contains_vision_content(item, depth=depth + 1, max_depth=max_depth)
for item in value
)
# Only check dict/object types
if not isinstance(value, dict):
@@ -298,7 +310,8 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig):
# Check content field recursively
if "content" in value and isinstance(value["content"], list):
return any(
self._contains_vision_content(item) for item in value["content"]
self._contains_vision_content(item, depth=depth + 1, max_depth=max_depth)
for item in value["content"]
)
return False
@@ -10,6 +10,7 @@ from httpx._types import RequestFiles
import litellm
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
from litellm.llms.base_llm.image_edit.transformation import BaseImageEditConfig
from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import VertexLLM
from litellm.secret_managers.main import get_secret_str
@@ -286,11 +287,18 @@ class VertexAIImagenImageEditConfig(BaseImageEditConfig, VertexLLM):
return reference_images
def _read_all_bytes(self, image: Any) -> bytes:
def _read_all_bytes(
self, image: Any, depth: int = 0, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH
) -> bytes:
if depth > max_depth:
raise ValueError(
f"Max recursion depth {max_depth} reached while reading image bytes for Vertex AI Imagen image edit."
)
if isinstance(image, (list, tuple)):
for item in image:
if item is not None:
return self._read_all_bytes(item)
return self._read_all_bytes(item, depth=depth + 1, max_depth=max_depth)
raise ValueError("Unsupported image type for Vertex AI Imagen image edit.")
if isinstance(image, dict):
@@ -302,9 +310,9 @@ class VertexAIImagenImageEditConfig(BaseImageEditConfig, VertexLLM):
return base64.b64decode(value)
except Exception:
continue
return self._read_all_bytes(value)
return self._read_all_bytes(value, depth=depth + 1, max_depth=max_depth)
if "path" in image:
return self._read_all_bytes(image["path"])
return self._read_all_bytes(image["path"], depth=depth + 1, max_depth=max_depth)
if isinstance(image, bytes):
return image
@@ -647,7 +647,7 @@ if MCP_AVAILABLE:
allowed_mcp_server_ids = (
await global_mcp_server_manager.get_allowed_mcp_servers(user_api_key_auth)
)
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids(
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids( # type: ignore[attr-defined]
allowed_mcp_server_ids
)
@@ -1173,7 +1173,7 @@ if MCP_AVAILABLE:
)
)
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids(
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids( # type: ignore[attr-defined]
allowed_mcp_server_ids
)
@@ -22,7 +22,6 @@ from litellm.proxy.common_utils.openai_endpoint_utils import (
)
from litellm.proxy.openai_files_endpoints.common_utils import (
_is_base64_encoded_unified_file_id,
convert_b64_uid_to_unified_uid,
get_batch_id_from_unified_batch_id,
get_model_id_from_unified_batch_id,
get_models_from_unified_file_id,
@@ -58,7 +58,6 @@ if MCP_AVAILABLE:
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.management_endpoints.common_utils import _user_has_admin_view
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
from litellm.types.mcp_server.mcp_server_manager import MCPInfo
def _redact_mcp_credentials(
mcp_server: LiteLLM_MCPServerTable,
+1 -3
View File
@@ -554,8 +554,6 @@ async def update_prompt(
}'
```
"""
from datetime import datetime
from litellm.proxy.prompts.prompt_registry import IN_MEMORY_PROMPT_REGISTRY
from litellm.proxy.proxy_server import prisma_client
@@ -925,7 +923,7 @@ async def test_prompt(
# Use conversation history for user/assistant messages
messages = system_messages + request.conversation_history
else:
messages = rendered_messages
messages = rendered_messages # type: ignore[assignment]
# Use PromptTemplate's optional_params which already extracts all parameters
optional_params = template.optional_params.copy()
@@ -9,7 +9,6 @@ from litellm.proxy.public_endpoints.provider_create_metadata import (
)
from litellm.types.agents import AgentCard
from litellm.types.mcp import MCPPublicServer
from litellm.types.mcp_server.mcp_server_manager import MCPServer
from litellm.types.proxy.management_endpoints.model_management_endpoints import (
ModelGroupInfoProxy,
)
@@ -110,7 +110,7 @@ class LiteLLM_Proxy_MCP_Handler:
allowed_mcp_server_ids = (
await global_mcp_server_manager.get_allowed_mcp_servers(user_api_key_auth)
)
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids(
allowed_mcp_servers = global_mcp_server_manager.get_mcp_servers_from_ids( # type: ignore[attr-defined]
allowed_mcp_server_ids
)
+21 -2
View File
@@ -835,8 +835,8 @@ class Router:
litellm.acancel_batch, call_type="acancel_batch"
)
def _initialize_specialized_endpoints(self):
"""Helper to initialize specialized router endpoints (vector store, OCR, search, video, container)."""
def _initialize_vector_store_endpoints(self):
"""Initialize vector store endpoints."""
from litellm.vector_stores.main import acreate, asearch, create, search
self.avector_store_search = self.factory_function(
@@ -852,6 +852,8 @@ class Router:
create, call_type="vector_store_create"
)
def _initialize_vector_store_file_endpoints(self):
"""Initialize vector store file endpoints."""
from litellm.vector_store_files.main import (
acreate as avector_store_file_create_fn,
)
@@ -921,6 +923,8 @@ class Router:
vector_store_file_delete_fn, call_type="vector_store_file_delete"
)
def _initialize_google_genai_endpoints(self):
"""Initialize Google GenAI endpoints."""
from litellm.google_genai import (
agenerate_content,
agenerate_content_stream,
@@ -941,6 +945,8 @@ class Router:
generate_content_stream, call_type="generate_content_stream"
)
def _initialize_ocr_search_endpoints(self):
"""Initialize OCR and search endpoints."""
from litellm.ocr import aocr, ocr
self.aocr = self.factory_function(aocr, call_type="aocr")
@@ -951,6 +957,8 @@ class Router:
self.asearch = self.factory_function(asearch, call_type="asearch")
self.search = self.factory_function(search, call_type="search")
def _initialize_video_endpoints(self):
"""Initialize video endpoints."""
from litellm.videos import (
avideo_content,
avideo_generation,
@@ -989,6 +997,8 @@ class Router:
)
self.video_remix = self.factory_function(video_remix, call_type="video_remix")
def _initialize_container_endpoints(self):
"""Initialize container endpoints."""
from litellm.containers import (
acreate_container,
adelete_container,
@@ -1025,6 +1035,15 @@ class Router:
delete_container, call_type="delete_container"
)
def _initialize_specialized_endpoints(self):
"""Helper to initialize specialized router endpoints (vector store, OCR, search, video, container)."""
self._initialize_vector_store_endpoints()
self._initialize_vector_store_file_endpoints()
self._initialize_google_genai_endpoints()
self._initialize_ocr_search_endpoints()
self._initialize_video_endpoints()
self._initialize_container_endpoints()
def initialize_router_endpoints(self):
self._initialize_core_endpoints()
self._initialize_specialized_endpoints()
+1 -1
View File
@@ -2728,7 +2728,7 @@ class LiteLLMFineTuningJob(FineTuningJob):
class LiteLLMBatch(Batch):
_hidden_params: dict = {}
usage: Optional[Usage] = None
usage: Optional[Usage] = None # type: ignore[assignment]
def __contains__(self, key):
# Define custom behavior for the 'in' operator
+1 -1
View File
@@ -55,7 +55,7 @@ jinja2==3.1.6 # for prompt templates
aiohttp==3.12.14 # for network calls
aioboto3==13.4.0 # for async sagemaker calls
tenacity==8.5.0 # for retrying requests, when litellm.num_retries set
pydantic==2.11.0 # proxy + openai req. + mcp
pydantic>=2.11,<3 # proxy + openai req. + mcp
jsonschema==4.22.0 # validating json schema
websockets==13.1.0 # for realtime API
soundfile==0.12.1 # for audio file processing
@@ -30,6 +30,8 @@ IGNORE_FUNCTIONS = [
"_fix_enum_empty_strings", # max depth set.,
"get_access_token", # max depth set.,
"_redact_base64", # max depth set.
"_contains_vision_content", # max depth set.
"_read_all_bytes", # max depth set.
]
@@ -26,7 +26,7 @@ test("admin login test", async ({ page }) => {
await loginButton.click();
const tabs = [
"Virtual Keys",
"Test Key",
"Playground",
"Models",
"Usage",
"Teams",
@@ -872,3 +872,201 @@ def test_initialize_specialized_endpoints():
for endpoint in specialized_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_vector_store_endpoints():
"""
Test that _initialize_vector_store_endpoints correctly sets up vector store endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_vector_store_endpoints()
vector_store_endpoints = [
"avector_store_search",
"avector_store_create",
"vector_store_search",
"vector_store_create",
]
for endpoint in vector_store_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_vector_store_file_endpoints():
"""
Test that _initialize_vector_store_file_endpoints correctly sets up vector store file endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_vector_store_file_endpoints()
vector_store_file_endpoints = [
"avector_store_file_create",
"vector_store_file_create",
"avector_store_file_list",
"vector_store_file_list",
"avector_store_file_retrieve",
"vector_store_file_retrieve",
"avector_store_file_content",
"vector_store_file_content",
"avector_store_file_update",
"vector_store_file_update",
"avector_store_file_delete",
"vector_store_file_delete",
]
for endpoint in vector_store_file_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_google_genai_endpoints():
"""
Test that _initialize_google_genai_endpoints correctly sets up Google GenAI endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_google_genai_endpoints()
google_genai_endpoints = [
"agenerate_content",
"generate_content",
"agenerate_content_stream",
"generate_content_stream",
]
for endpoint in google_genai_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_ocr_search_endpoints():
"""
Test that _initialize_ocr_search_endpoints correctly sets up OCR and search endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_ocr_search_endpoints()
ocr_search_endpoints = [
"aocr",
"ocr",
"asearch",
"search",
]
for endpoint in ocr_search_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_video_endpoints():
"""
Test that _initialize_video_endpoints correctly sets up video endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_video_endpoints()
video_endpoints = [
"avideo_generation",
"video_generation",
"avideo_list",
"video_list",
"avideo_status",
"video_status",
"avideo_content",
"video_content",
"avideo_remix",
"video_remix",
]
for endpoint in video_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
def test_initialize_container_endpoints():
"""
Test that _initialize_container_endpoints correctly sets up container endpoints.
"""
router = Router(
model_list=[
{
"model_name": "test-model",
"litellm_params": {
"model": "openai/test-model",
"api_key": "fake-api-key",
},
}
]
)
router._initialize_container_endpoints()
container_endpoints = [
"acreate_container",
"create_container",
"alist_containers",
"list_containers",
"aretrieve_container",
"retrieve_container",
"adelete_container",
"delete_container",
]
for endpoint in container_endpoints:
assert hasattr(router, endpoint)
assert callable(getattr(router, endpoint))
@@ -1,7 +1,6 @@
import { Drawer, List, Skeleton, Tag, Typography } from "antd";
import React, { useEffect, useState } from "react";
import { Drawer, List, Tag, Typography, Skeleton, Button } from "antd";
import { getPromptVersions, PromptSpec } from "../../networking";
import NotificationsManager from "../../molecules/notifications_manager";
const { Text } = Typography;
@@ -70,9 +69,7 @@ const VersionHistorySidePanel: React.FC<VersionHistorySidePanelProps> = ({
{loading ? (
<Skeleton active paragraph={{ rows: 4 }} />
) : versions.length === 0 ? (
<div className="text-center py-8 text-gray-500">
No version history available.
</div>
<div className="text-center py-8 text-gray-500">No version history available.</div>
) : (
<List
dataSource={versions}
@@ -82,18 +79,18 @@ const VersionHistorySidePanel: React.FC<VersionHistorySidePanelProps> = ({
<div
key={item.prompt_id}
className={`mb-4 p-4 rounded-lg border cursor-pointer transition-all hover:shadow-md ${
isSelected
? "border-blue-500 bg-blue-50"
: "border-gray-200 bg-white hover:border-blue-300"
isSelected ? "border-blue-500 bg-blue-50" : "border-gray-200 bg-white hover:border-blue-300"
}`}
onClick={() => onSelectVersion?.(item)}
>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2">
<Tag className="m-0">
{getVersionNumber(item.prompt_id)}
</Tag>
{index === 0 && <Tag color="blue" className="m-0">Latest</Tag>}
<Tag className="m-0">{getVersionNumber(item.prompt_id)}</Tag>
{index === 0 && (
<Tag color="blue" className="m-0">
Latest
</Tag>
)}
</div>
{isSelected && (
<Tag color="green" className="m-0">
@@ -101,11 +98,9 @@ const VersionHistorySidePanel: React.FC<VersionHistorySidePanelProps> = ({
</Tag>
)}
</div>
<div className="flex flex-col gap-1">
<Text className="text-sm text-gray-600 font-medium">
{formatDate(item.created_at)}
</Text>
<Text className="text-sm text-gray-600 font-medium">{formatDate(item.created_at)}</Text>
<Text type="secondary" className="text-xs">
{item.prompt_info?.prompt_type === "db" ? "Saved to Database" : "Config Prompt"}
</Text>
@@ -120,4 +115,3 @@ const VersionHistorySidePanel: React.FC<VersionHistorySidePanelProps> = ({
};
export default VersionHistorySidePanel;
@@ -17,7 +17,7 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
const extractedVariables = extractVariables(prompt);
const allVariablesFilled = extractedVariables.every(
(varName) => variables[varName] && variables[varName].trim() !== ""
(varName) => variables[varName] && variables[varName].trim() !== "",
);
const scrollToBottom = () => {
@@ -115,6 +115,7 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
let usage: TokenUsage | undefined;
setMessages((prev) => [...prev, { role: "assistant", content: "" }]);
// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await reader.read();
if (done) break;
@@ -182,10 +183,7 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
setMessages((prev) => {
const lastMsg = prev[prev.length - 1];
if (lastMsg && lastMsg.role === "assistant" && lastMsg.content === "") {
return [
...prev.slice(0, -1),
{ role: "assistant", content: `Error: ${error.message}` },
];
return [...prev.slice(0, -1), { role: "assistant", content: `Error: ${error.message}` }];
}
return [...prev, { role: "assistant", content: `Error: ${error.message}` }];
});
@@ -242,4 +240,3 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
handleVariableChange,
};
};
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import ToolModal from "../tool_modal";
import NotificationsManager from "../../molecules/notifications_manager";
import { createPromptCall, updatePromptCall } from "../../networking";
@@ -25,29 +25,27 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
}
}
return {
name: "New prompt",
model: "gpt-4o",
config: {
temperature: 1,
max_tokens: 1000,
},
tools: [],
developerMessage: "",
messages: [
{
role: "user",
content: "Enter task specifics. Use {{template_variables}} for dynamic inputs",
name: "New prompt",
model: "gpt-4o",
config: {
temperature: 1,
max_tokens: 1000,
},
],
tools: [],
developerMessage: "",
messages: [
{
role: "user",
content: "Enter task specifics. Use {{template_variables}} for dynamic inputs",
},
],
};
};
const [prompt, setPrompt] = useState<PromptType>(getInitialPrompt());
const [editMode, setEditMode] = useState<boolean>(!!initialPromptData);
const [showHistoryModal, setShowHistoryModal] = useState(false);
const [activeVersionId, setActiveVersionId] = useState<string | undefined>(
initialPromptData?.prompt_spec?.prompt_id
);
const [activeVersionId, setActiveVersionId] = useState<string | undefined>(initialPromptData?.prompt_spec?.prompt_id);
const [showToolModal, setShowToolModal] = useState(false);
const [showNameModal, setShowNameModal] = useState(false);
@@ -194,8 +192,8 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
await updatePromptCall(accessToken, initialPromptData.prompt_spec.prompt_id, promptData);
NotificationsManager.success("Prompt updated successfully!");
} else {
await createPromptCall(accessToken, promptData);
NotificationsManager.success("Prompt created successfully!");
await createPromptCall(accessToken, promptData);
NotificationsManager.success("Prompt created successfully!");
}
onSuccess();
onClose();
@@ -258,9 +256,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
<div className="ml-auto inline-flex items-center bg-gray-200 rounded-full p-0.5">
<button
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
viewMode === "pretty"
? "bg-white text-gray-900 shadow-sm"
: "text-gray-600"
viewMode === "pretty" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600"
}`}
onClick={() => setViewMode("pretty")}
>
@@ -268,9 +264,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
</button>
<button
className={`px-3 py-1 text-xs font-medium rounded-full transition-colors ${
viewMode === "dotprompt"
? "bg-white text-gray-900 shadow-sm"
: "text-gray-600"
viewMode === "dotprompt" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600"
}`}
onClick={() => setViewMode("dotprompt")}
>
@@ -346,4 +340,3 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
};
export default PromptEditorView;