Merge branch 'main' into UIRedesignStaging
This commit is contained in:
@@ -65,6 +65,7 @@ README*
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!engine/.env
|
||||
|
||||
# Misc
|
||||
*.swp
|
||||
|
||||
@@ -165,6 +165,7 @@ __pycache__/
|
||||
# Virtual environments
|
||||
.env*
|
||||
!.env*.example
|
||||
!engine/.env
|
||||
.venv*
|
||||
env*/
|
||||
venv*/
|
||||
|
||||
@@ -20,9 +20,8 @@ tasks:
|
||||
- uv run scripts/setup_env.py
|
||||
sources:
|
||||
- scripts/setup_env.py
|
||||
- config/.env.example
|
||||
generates:
|
||||
- .env
|
||||
- .env.local
|
||||
|
||||
run:
|
||||
desc: "Run engine server"
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package stirling.software.proprietary.model.api.ai;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "A prior message in the chat conversation")
|
||||
public class AiConversationMessage {
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Schema(description = "The role of the message sender", example = "user")
|
||||
private String role;
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "The content of the message")
|
||||
private String content;
|
||||
}
|
||||
+7
@@ -1,5 +1,6 @@
|
||||
package stirling.software.proprietary.model.api.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -20,4 +21,10 @@ public class AiWorkflowRequest {
|
||||
@NotBlank
|
||||
@Schema(description = "The user message to orchestrate", example = "Summarise these documents")
|
||||
private String userMessage;
|
||||
|
||||
@Schema(
|
||||
description =
|
||||
"Prior chat messages exchanged between the user and the assistant, ordered"
|
||||
+ " oldest-first. Excludes the current userMessage.")
|
||||
private List<AiConversationMessage> conversationHistory = new ArrayList<>();
|
||||
}
|
||||
|
||||
+7
@@ -34,6 +34,7 @@ import stirling.software.common.util.ExceptionUtils;
|
||||
import stirling.software.common.util.TempFile;
|
||||
import stirling.software.common.util.TempFileManager;
|
||||
import stirling.software.common.util.ZipExtractionUtils;
|
||||
import stirling.software.proprietary.model.api.ai.AiConversationMessage;
|
||||
import stirling.software.proprietary.model.api.ai.AiWorkflowFileInput;
|
||||
import stirling.software.proprietary.model.api.ai.AiWorkflowFileRequest;
|
||||
import stirling.software.proprietary.model.api.ai.AiWorkflowOutcome;
|
||||
@@ -92,6 +93,10 @@ public class AiWorkflowService {
|
||||
WorkflowTurnRequest initialRequest = new WorkflowTurnRequest();
|
||||
initialRequest.setUserMessage(request.getUserMessage().trim());
|
||||
initialRequest.setFileNames(new ArrayList<>(filesByName.keySet()));
|
||||
initialRequest.setConversationHistory(
|
||||
request.getConversationHistory() == null
|
||||
? new ArrayList<>()
|
||||
: new ArrayList<>(request.getConversationHistory()));
|
||||
|
||||
listener.onProgress(AiWorkflowProgressEvent.of(AiWorkflowPhase.ANALYZING));
|
||||
|
||||
@@ -183,6 +188,7 @@ public class AiWorkflowService {
|
||||
WorkflowTurnRequest nextRequest = new WorkflowTurnRequest();
|
||||
nextRequest.setUserMessage(request.getUserMessage());
|
||||
nextRequest.setFileNames(request.getFileNames());
|
||||
nextRequest.setConversationHistory(request.getConversationHistory());
|
||||
nextRequest.setArtifacts(pdfContentExtractor.buildArtifacts(contentResults));
|
||||
nextRequest.setResumeWith(response.getResumeWith());
|
||||
return new WorkflowState.Pending(nextRequest);
|
||||
@@ -415,6 +421,7 @@ public class AiWorkflowService {
|
||||
private static class WorkflowTurnRequest {
|
||||
private String userMessage;
|
||||
private List<String> fileNames = new ArrayList<>();
|
||||
private List<AiConversationMessage> conversationHistory = new ArrayList<>();
|
||||
private List<WorkflowArtifact> artifacts = new ArrayList<>();
|
||||
private String resumeWith;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
###############################################################################
|
||||
# Environment variables used within the AI Engine.
|
||||
# Values can be overridden in the uncommitted sibling `.env.local` file.
|
||||
# Note: This file is committed to Git, so should not contain any private keys.
|
||||
###############################################################################
|
||||
|
||||
# Configure the model strings passed to pydantic-ai. Provider credentials are handled by
|
||||
# pydantic-ai and should be set using the provider's native environment variables, for example
|
||||
# ANTHROPIC_API_KEY or OPENAI_API_KEY.
|
||||
@@ -19,7 +19,6 @@ yarn-error.log*
|
||||
.vite/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# LaTeX outputs
|
||||
|
||||
+1
-2
@@ -12,9 +12,8 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml uv.lock Taskfile.yml ./
|
||||
COPY pyproject.toml uv.lock Taskfile.yml .env ./
|
||||
COPY .taskfiles/ ./.taskfiles/
|
||||
COPY config/ ./config/
|
||||
COPY scripts/ ./scripts/
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-dev
|
||||
|
||||
+13
-37
@@ -1,48 +1,24 @@
|
||||
"""
|
||||
Copies .env from .env.example if missing, and errors if any keys from the example
|
||||
are absent from the actual .env file.
|
||||
Ensures `.env.local` exists so developers have a place to put overrides
|
||||
(API keys, local model choices, etc.) without touching the committed `.env`.
|
||||
|
||||
Usage:
|
||||
uv run scripts/setup_env.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import dotenv_values
|
||||
|
||||
ROOT = Path(__file__).parent.parent
|
||||
EXAMPLE_FILE = ROOT / "config" / ".env.example"
|
||||
ENV_FILE = ROOT / ".env"
|
||||
ENV_LOCAL_FILE = ROOT / ".env.local"
|
||||
|
||||
print("setup-env: see engine/config/.env.example for documentation")
|
||||
TEMPLATE = """\
|
||||
###############################################################################
|
||||
# Local overrides for `engine/.env`
|
||||
# Put API keys and machine-specific settings here. Any variable defined here
|
||||
# takes precedence over the committed `.env`
|
||||
###############################################################################
|
||||
"""
|
||||
|
||||
if not EXAMPLE_FILE.exists():
|
||||
print(f"setup-env: {EXAMPLE_FILE.name} not found, skipping", file=sys.stderr)
|
||||
sys.exit(0)
|
||||
|
||||
if not ENV_FILE.exists():
|
||||
shutil.copy(EXAMPLE_FILE, ENV_FILE)
|
||||
print("setup-env: created .env from .env.example")
|
||||
|
||||
env_keys = set(dotenv_values(ENV_FILE).keys()) | set(os.environ.keys())
|
||||
example_keys = set(dotenv_values(EXAMPLE_FILE).keys())
|
||||
missing = sorted(example_keys - env_keys)
|
||||
|
||||
if missing:
|
||||
sys.exit(
|
||||
"setup-env: .env is missing keys from .env.example:\n"
|
||||
+ "\n".join(f" {k}" for k in missing)
|
||||
+ "\n Add them manually or delete your local .env to re-copy from config/.env.example."
|
||||
)
|
||||
|
||||
extra = sorted(k for k in dotenv_values(ENV_FILE) if k.startswith("STIRLING_") and k not in example_keys)
|
||||
if extra:
|
||||
print(
|
||||
"setup-env: .env contains STIRLING_ keys not in config/.env.example:\n"
|
||||
+ "\n".join(f" {k}" for k in extra)
|
||||
+ "\n Add them to config/.env.example if they are intentional.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if not ENV_LOCAL_FILE.exists():
|
||||
ENV_LOCAL_FILE.write_text(TEMPLATE)
|
||||
print("setup-env: created empty .env.local for local overrides")
|
||||
|
||||
@@ -23,6 +23,7 @@ from stirling.contracts import (
|
||||
SupportedCapability,
|
||||
ToolOperationStep,
|
||||
UnsupportedCapabilityResponse,
|
||||
format_conversation_history,
|
||||
)
|
||||
from stirling.contracts.pdf_edit import EditPlanResponse
|
||||
from stirling.models.agent_tool_models import AgentToolId, MathAuditorAgentParams
|
||||
@@ -117,7 +118,11 @@ class OrchestratorAgent:
|
||||
|
||||
async def _run_pdf_edit(self, request: OrchestratorRequest) -> PdfEditResponse:
|
||||
return await PdfEditAgent(self.runtime).handle(
|
||||
PdfEditRequest(user_message=request.user_message, file_names=request.file_names)
|
||||
PdfEditRequest(
|
||||
user_message=request.user_message,
|
||||
file_names=request.file_names,
|
||||
conversation_history=request.conversation_history,
|
||||
)
|
||||
)
|
||||
|
||||
async def delegate_pdf_question(self, ctx: RunContext[OrchestratorDeps]) -> PdfQuestionResponse:
|
||||
@@ -130,6 +135,7 @@ class OrchestratorAgent:
|
||||
question=request.user_message,
|
||||
file_names=request.file_names,
|
||||
page_text=extracted_text.files if extracted_text is not None else [],
|
||||
conversation_history=request.conversation_history,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -137,7 +143,12 @@ class OrchestratorAgent:
|
||||
return await self._run_agent_draft(ctx.deps.request)
|
||||
|
||||
async def _run_agent_draft(self, request: OrchestratorRequest) -> AgentDraftWorkflowResponse:
|
||||
return await UserSpecAgent(self.runtime).draft(AgentDraftRequest(user_message=request.user_message))
|
||||
return await UserSpecAgent(self.runtime).draft(
|
||||
AgentDraftRequest(
|
||||
user_message=request.user_message,
|
||||
conversation_history=request.conversation_history,
|
||||
)
|
||||
)
|
||||
|
||||
async def math_auditor_agent(self, ctx: RunContext[OrchestratorDeps]) -> EditPlanResponse:
|
||||
return EditPlanResponse(
|
||||
@@ -167,7 +178,13 @@ class OrchestratorAgent:
|
||||
def _build_prompt(self, request: OrchestratorRequest) -> str:
|
||||
artifact_summary = self._describe_artifacts(request)
|
||||
file_names = ", ".join(request.file_names) if request.file_names else "Unknown files"
|
||||
return f"User message: {request.user_message}\nFiles: {file_names}\nAvailable artifacts:\n{artifact_summary}"
|
||||
history = format_conversation_history(request.conversation_history)
|
||||
return (
|
||||
f"Conversation history:\n{history}\n"
|
||||
f"User message: {request.user_message}\n"
|
||||
f"Files: {file_names}\n"
|
||||
f"Available artifacts:\n{artifact_summary}"
|
||||
)
|
||||
|
||||
def _describe_artifacts(self, request: OrchestratorRequest) -> str:
|
||||
if not request.artifacts:
|
||||
|
||||
@@ -13,6 +13,7 @@ from stirling.contracts import (
|
||||
PdfEditRequest,
|
||||
PdfEditResponse,
|
||||
ToolOperationStep,
|
||||
format_conversation_history,
|
||||
)
|
||||
from stirling.models import OPERATIONS, ApiModel, ParamToolModel, ToolEndpoint
|
||||
from stirling.services import AppRuntime
|
||||
@@ -146,6 +147,7 @@ class PdfEditAgent:
|
||||
def _build_selection_prompt(self, request: PdfEditRequest) -> str:
|
||||
file_names = ", ".join(request.file_names) if request.file_names else "No file names were provided."
|
||||
return (
|
||||
f"Conversation history:\n{format_conversation_history(request.conversation_history)}\n"
|
||||
f"User request: {request.user_message}\n"
|
||||
f"Files: {file_names}\n"
|
||||
f"Supported operations: {self._supported_operations_prompt()}\n"
|
||||
|
||||
@@ -12,6 +12,7 @@ from stirling.contracts import (
|
||||
PdfQuestionNotFoundResponse,
|
||||
PdfQuestionRequest,
|
||||
PdfQuestionResponse,
|
||||
format_conversation_history,
|
||||
)
|
||||
from stirling.services import AppRuntime
|
||||
|
||||
@@ -70,7 +71,13 @@ class PdfQuestionAgent:
|
||||
for selection in file_text.pages
|
||||
]
|
||||
pages = "\n\n".join(sections)
|
||||
return f"Files: {file_names}\nQuestion: {request.question}\nExtracted page text:\n{pages}"
|
||||
history = format_conversation_history(request.conversation_history)
|
||||
return (
|
||||
f"Conversation history:\n{history}\n"
|
||||
f"Files: {file_names}\n"
|
||||
f"Question: {request.question}\n"
|
||||
f"Extracted page text:\n{pages}"
|
||||
)
|
||||
|
||||
def _has_page_text(self, page_text: list[ExtractedFileText]) -> bool:
|
||||
return any(selection.text.strip() for file_text in page_text for selection in file_text.pages)
|
||||
|
||||
@@ -18,6 +18,7 @@ from stirling.contracts import (
|
||||
EditClarificationRequest,
|
||||
EditPlanResponse,
|
||||
PdfEditRequest,
|
||||
format_conversation_history,
|
||||
)
|
||||
from stirling.models import ApiModel
|
||||
from stirling.services import AppRuntime
|
||||
@@ -45,14 +46,15 @@ class UserSpecAgent:
|
||||
)
|
||||
|
||||
async def draft(self, request: AgentDraftRequest) -> AgentDraftWorkflowResponse:
|
||||
edit_plan = await self._build_edit_plan(request.user_message)
|
||||
edit_plan = await self._build_edit_plan(request.user_message, request.conversation_history)
|
||||
if not isinstance(edit_plan, EditPlanResponse):
|
||||
return edit_plan
|
||||
return AgentDraftResponse(draft=await self._run_draft_agent(request, edit_plan))
|
||||
|
||||
async def revise(self, request: AgentRevisionRequest) -> AgentRevisionWorkflowResponse:
|
||||
edit_plan = await self._build_edit_plan(
|
||||
f"Current objective: {request.current_draft.objective}\nRevision request: {request.user_message}"
|
||||
f"Current objective: {request.current_draft.objective}\nRevision request: {request.user_message}",
|
||||
request.conversation_history,
|
||||
)
|
||||
if not isinstance(edit_plan, EditPlanResponse):
|
||||
return edit_plan
|
||||
@@ -80,7 +82,7 @@ class UserSpecAgent:
|
||||
def _build_draft_prompt(self, request: AgentDraftRequest, edit_plan: EditPlanResponse) -> str:
|
||||
return (
|
||||
f"User request:\n{request.user_message}\n\n"
|
||||
f"Conversation history:\n{self._format_conversation_history(request.conversation_history)}\n\n"
|
||||
f"Conversation history:\n{format_conversation_history(request.conversation_history)}\n\n"
|
||||
f"Edit plan summary:\n{edit_plan.summary}\n\n"
|
||||
f"Edit plan rationale:\n{edit_plan.rationale or 'None'}\n\n"
|
||||
f"Edit plan steps:\n{edit_plan.model_dump_json(indent=2)}"
|
||||
@@ -89,20 +91,18 @@ class UserSpecAgent:
|
||||
def _build_revision_prompt(self, request: AgentRevisionRequest, edit_plan: EditPlanResponse) -> str:
|
||||
return (
|
||||
f"Revision request:\n{request.user_message}\n\n"
|
||||
f"Conversation history:\n{self._format_conversation_history(request.conversation_history)}\n\n"
|
||||
f"Conversation history:\n{format_conversation_history(request.conversation_history)}\n\n"
|
||||
f"Current draft:\n{request.current_draft.model_dump_json(indent=2)}\n\n"
|
||||
f"Edit plan summary:\n{edit_plan.summary}\n\n"
|
||||
f"Edit plan rationale:\n{edit_plan.rationale or 'None'}\n\n"
|
||||
f"Edit plan steps:\n{edit_plan.model_dump_json(indent=2)}"
|
||||
)
|
||||
|
||||
def _format_conversation_history(self, conversation_history: list[ConversationMessage]) -> str:
|
||||
if not conversation_history:
|
||||
return "None"
|
||||
return "\n".join(f"- {message.role}: {message.content}" for message in conversation_history)
|
||||
|
||||
async def _build_edit_plan(
|
||||
self,
|
||||
user_message: str,
|
||||
conversation_history: list[ConversationMessage],
|
||||
) -> EditPlanResponse | EditClarificationRequest | EditCannotDoResponse:
|
||||
return await self.pdf_edit_agent.handle(PdfEditRequest(user_message=user_message))
|
||||
return await self.pdf_edit_agent.handle(
|
||||
PdfEditRequest(user_message=user_message, conversation_history=conversation_history)
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
ENGINE_ROOT = Path(__file__).resolve().parents[3]
|
||||
ENV_FILE = ENGINE_ROOT / ".env"
|
||||
ENV_LOCAL_FILE = ENGINE_ROOT / ".env.local"
|
||||
|
||||
|
||||
class RagBackend(StrEnum):
|
||||
@@ -20,7 +21,7 @@ class RagBackend(StrEnum):
|
||||
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=ENV_FILE, extra="ignore", populate_by_name=True)
|
||||
model_config = SettingsConfigDict(env_file=(ENV_FILE, ENV_LOCAL_FILE), extra="ignore", populate_by_name=True)
|
||||
|
||||
smart_model_name: str = Field(validation_alias="STIRLING_SMART_MODEL")
|
||||
fast_model_name: str = Field(validation_alias="STIRLING_FAST_MODEL")
|
||||
@@ -74,6 +75,7 @@ def _configure_logging(level_name: str, log_file: str) -> None:
|
||||
@lru_cache(maxsize=1)
|
||||
def load_settings() -> AppSettings:
|
||||
load_dotenv(ENV_FILE)
|
||||
load_dotenv(ENV_LOCAL_FILE, override=True)
|
||||
settings = AppSettings.model_validate({})
|
||||
_configure_logging(settings.log_level, settings.log_file)
|
||||
return settings
|
||||
|
||||
@@ -18,6 +18,7 @@ from .common import (
|
||||
SupportedCapability,
|
||||
ToolOperationStep,
|
||||
WorkflowOutcome,
|
||||
format_conversation_history,
|
||||
)
|
||||
from .execution import (
|
||||
AgentExecutionRequest,
|
||||
@@ -104,6 +105,7 @@ __all__ = [
|
||||
"Folio",
|
||||
"FolioManifest",
|
||||
"FolioType",
|
||||
"format_conversation_history",
|
||||
"HealthResponse",
|
||||
"NeedContentFileRequest",
|
||||
"NextExecutionAction",
|
||||
|
||||
@@ -91,6 +91,12 @@ class ConversationMessage(ApiModel):
|
||||
content: str
|
||||
|
||||
|
||||
def format_conversation_history(conversation_history: list[ConversationMessage]) -> str:
|
||||
if not conversation_history:
|
||||
return "None"
|
||||
return "\n".join(f"- {message.role}: {message.content}" for message in conversation_history)
|
||||
|
||||
|
||||
class PdfTextSelection(ApiModel):
|
||||
page_number: int | None = None
|
||||
text: str
|
||||
|
||||
@@ -7,7 +7,13 @@ from pydantic import Field
|
||||
from stirling.models import ApiModel
|
||||
|
||||
from .agent_drafts import AgentDraftResponse
|
||||
from .common import ArtifactKind, ExtractedFileText, SupportedCapability, WorkflowOutcome
|
||||
from .common import (
|
||||
ArtifactKind,
|
||||
ConversationMessage,
|
||||
ExtractedFileText,
|
||||
SupportedCapability,
|
||||
WorkflowOutcome,
|
||||
)
|
||||
from .execution import NextExecutionAction
|
||||
from .pdf_edit import PdfEditResponse
|
||||
from .pdf_questions import PdfQuestionResponse
|
||||
@@ -24,6 +30,7 @@ WorkflowArtifact = Annotated[ExtractedTextArtifact, Field(discriminator="kind")]
|
||||
class OrchestratorRequest(ApiModel):
|
||||
user_message: str
|
||||
file_names: list[str]
|
||||
conversation_history: list[ConversationMessage] = Field(default_factory=list)
|
||||
artifacts: list[WorkflowArtifact] = Field(default_factory=list)
|
||||
resume_with: SupportedCapability | None = None
|
||||
|
||||
|
||||
@@ -6,12 +6,13 @@ from pydantic import Field
|
||||
|
||||
from stirling.models import ApiModel
|
||||
|
||||
from .common import ToolOperationStep, WorkflowOutcome
|
||||
from .common import ConversationMessage, ToolOperationStep, WorkflowOutcome
|
||||
|
||||
|
||||
class PdfEditRequest(ApiModel):
|
||||
user_message: str
|
||||
file_names: list[str] = Field(default_factory=list)
|
||||
conversation_history: list[ConversationMessage] = Field(default_factory=list)
|
||||
|
||||
|
||||
class EditPlanResponse(ApiModel):
|
||||
|
||||
@@ -6,13 +6,20 @@ from pydantic import Field
|
||||
|
||||
from stirling.models import ApiModel
|
||||
|
||||
from .common import ExtractedFileText, PdfContentType, SupportedCapability, WorkflowOutcome
|
||||
from .common import (
|
||||
ConversationMessage,
|
||||
ExtractedFileText,
|
||||
PdfContentType,
|
||||
SupportedCapability,
|
||||
WorkflowOutcome,
|
||||
)
|
||||
|
||||
|
||||
class PdfQuestionRequest(ApiModel):
|
||||
question: str
|
||||
page_text: list[ExtractedFileText] = Field(default_factory=list)
|
||||
file_names: list[str]
|
||||
conversation_history: list[ConversationMessage] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PdfQuestionAnswerResponse(ApiModel):
|
||||
|
||||
@@ -37,7 +37,9 @@ multi = RagCapability(runtime.rag_service, collections=["company-docs", "product
|
||||
everything = RagCapability(runtime.rag_service)
|
||||
```
|
||||
|
||||
## Config (.env)
|
||||
## Config
|
||||
|
||||
Non-secret defaults live in the committed `engine/.env`:
|
||||
|
||||
```
|
||||
STIRLING_RAG_BACKEND=sqlite # or "pgvector"
|
||||
@@ -47,6 +49,11 @@ STIRLING_RAG_PGVECTOR_DSN= # used when backend=pgvector
|
||||
STIRLING_RAG_CHUNK_SIZE=512
|
||||
STIRLING_RAG_CHUNK_OVERLAP=64
|
||||
STIRLING_RAG_TOP_K=5
|
||||
```
|
||||
|
||||
Provider credentials (and any local overrides) go in the uncommitted `engine/.env.local`:
|
||||
|
||||
```
|
||||
VOYAGE_API_KEY=your-key
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ class StubUserSpecAgent(UserSpecAgent):
|
||||
],
|
||||
)
|
||||
|
||||
async def _build_edit_plan(self, user_message: str) -> EditPlanResponse:
|
||||
async def _build_edit_plan(
|
||||
self, user_message: str, conversation_history: list[ConversationMessage]
|
||||
) -> EditPlanResponse:
|
||||
return self.edit_plan
|
||||
|
||||
async def _run_draft_agent(self, request: AgentDraftRequest, edit_plan: EditPlanResponse) -> AgentDraft:
|
||||
@@ -46,7 +48,9 @@ class ClarifyingUserSpecAgent(UserSpecAgent):
|
||||
def __init__(self, runtime: AppRuntime) -> None:
|
||||
super().__init__(runtime)
|
||||
|
||||
async def _build_edit_plan(self, user_message: str) -> EditClarificationRequest:
|
||||
async def _build_edit_plan(
|
||||
self, user_message: str, conversation_history: list[ConversationMessage]
|
||||
) -> EditClarificationRequest:
|
||||
return EditClarificationRequest(
|
||||
question="Which pages should be changed?",
|
||||
reason="The request does not specify the target pages.",
|
||||
|
||||
@@ -238,6 +238,8 @@ export function ChatProvider({ children }: { children: ReactNode }) {
|
||||
const { files: activeFiles, fileStubs: activeFileStubs } = useAllFiles();
|
||||
const { actions: fileActions } = useFileActions();
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const messagesRef = useRef<ChatMessage[]>(state.messages);
|
||||
messagesRef.current = state.messages;
|
||||
|
||||
// Download a File from the Stirling files endpoint.
|
||||
const downloadFile = useCallback(
|
||||
@@ -323,6 +325,8 @@ export function ChatProvider({ children }: { children: ReactNode }) {
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
const priorMessages = messagesRef.current;
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "user",
|
||||
@@ -339,6 +343,10 @@ export function ChatProvider({ children }: { children: ReactNode }) {
|
||||
activeFiles.forEach((file, i) => {
|
||||
formData.append(`fileInputs[${i}].fileInput`, file);
|
||||
});
|
||||
priorMessages.forEach((message, i) => {
|
||||
formData.append(`conversationHistory[${i}].role`, message.role);
|
||||
formData.append(`conversationHistory[${i}].content`, message.content);
|
||||
});
|
||||
|
||||
const response = await fetch("/api/v1/ai/orchestrate/stream", {
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user