diff --git a/ai-hub/app/core/pipelines/code_changer.py b/ai-hub/app/core/pipelines/code_changer.py index 6c48779..cb2f9c0 100644 --- a/ai-hub/app/core/pipelines/code_changer.py +++ b/ai-hub/app/core/pipelines/code_changer.py @@ -2,7 +2,7 @@ import json import os from typing import List, Dict, Any, Tuple, Optional, Callable - +from app.core.pipelines.validator import Validator,TokenLimitExceededError class CodeChanger(dspy.Signature): """ @@ -51,20 +51,16 @@ ----- ### 2\. Code Generation Rules + Please provide **one complete and functional code change** for the specified `file_path`. You must output the **entire, modified file**. - Your task is to output **one full-file code change** for the specified `file_path`, using the instruction and file context. - + * **Identical Code:** For sections of code that remain unchanged from the original file, use the single-line syntax `#[unchanged]|||`. For example, `#[unchanged]|/app/main.py|267|334` means the code is identical to lines 267 through 334 of the original file at `/app/main.py`. + * **Completeness:** The provided code must be self-contained, including all necessary dependencies, imports, and fully resolved definitions. + + Don't abuse the identidical syntax, it should be only used when a big chunk of duplicated code is in the updated file. ### **Code Quality Requirements** - * **No placeholders or pseudocode.** All code must be complete, functional, and ready to execute. - * **Provide the full code.** When making changes, always output the entire, modified file. Do not use abbreviations, placeholders, or `TODO` comments. - * **Ensure code integrity.** The updated code, when combined with the existing project files, must form a seamless, complete, and fully executable codebase. It should be both readable and extensible. - * **Include all dependencies and imports.** The code must be self-contained and immediately executable without requiring the user to add missing imports. - * **All definitions must be resolvable.** Ensure that all functions, variables, and return values are defined within the scope of the provided file. - * **Modular and well-structured.** The code should be modular, logically organized, and include clear, concise comments where necessary. - * **Follow best practices.** Adhere to good naming conventions and ensure a logical flow throughout the code. - + "Please provide complete and functional code that is ready to execute. When I request a code change, you must output the **entire, modified file**. Do not use abbreviations, placeholders, or comments like `...` or `# Existing code remains the same`. The code you provide should include all necessary dependencies and imports, and have all definitions fully resolved within the provided scope. Ensure the code is modular, well-structured, and follows best practices, including good naming conventions and clear, concise comments where necessary." #### 🔹 Change Types * **File Modification**: diff --git a/ai-hub/app/core/pipelines/question_decider.py b/ai-hub/app/core/pipelines/question_decider.py index 7ec258e..d9ae328 100644 --- a/ai-hub/app/core/pipelines/question_decider.py +++ b/ai-hub/app/core/pipelines/question_decider.py @@ -1,6 +1,7 @@ import dspy import json import os +from app.core.pipelines.validator import Validator,TokenLimitExceededError from app.db import models from typing import List, Dict, Any, Tuple, Optional, Callable @@ -149,13 +150,11 @@ The response must be a pure JSON array containing only the file paths you want to retrieve. Do not include any nested objects, additional keys, or conversational text. * **Example:** - ```json [ "/app/core/services/tts.py", "/app/core/services/stt.py", "/app/main.py" ] - ``` **Constraints & Selection Criteria:** @@ -213,6 +212,7 @@ # Initializes the dspy Predict module with the refined system prompt self.decider = dspy.ChainOfThought(QuestionDecider) self.history_formatter = history_formatter or self._default_history_formatter + self.validator = Validator() def _default_history_formatter(self, history: List[models.Message]) -> str: @@ -273,6 +273,12 @@ "retrieved_paths_with_content": retrieved_with_content_json, "retrieved_paths_without_content": retrieved_without_content_json, } + + try: + self.validator.precheck_tokensize(input_payload) + except TokenLimitExceededError as e: + raise e + prediction = await self.decider.acall(**input_payload) # Defensive handling and a clean way to access prediction fields diff --git a/ai-hub/app/core/pipelines/validator.py b/ai-hub/app/core/pipelines/validator.py new file mode 100644 index 0000000..e78d085 --- /dev/null +++ b/ai-hub/app/core/pipelines/validator.py @@ -0,0 +1,44 @@ +import tiktoken +import json +from typing import Dict, Any + +class TokenLimitExceededError(Exception): + """Custom exception raised when the input payload exceeds the token limit.""" + + def __init__(self, message: str, token_count: int, token_limit: int): + super().__init__(message) + self.token_count = token_count + self.token_limit = token_limit + + +class Validator: + def __init__(self, token_limit: int = 100000, encoding_name: str = "cl100k_base"): + """ + Initializes the Validator with a token limit and encoding. + + Args: + token_limit (int): The maximum number of tokens allowed. + encoding_name (str): The name of the tokenizer encoding to use. + """ + self.token_limit = token_limit + self.encoding = tiktoken.get_encoding(encoding_name=encoding_name) + + def precheck_tokensize(self, input_payload: Dict[str, Any]) -> None: + """ + Checks if the input payload's token count exceeds the configured limit. + + Args: + input_payload (Dict[str, Any]): The payload to be checked. + + Raises: + TokenLimitExceededError: If the payload's token count is too high. + """ + payload_string: str = json.dumps(input_payload) + token_count: int = len(self.encoding.encode(payload_string)) + + if token_count > self.token_limit: + raise TokenLimitExceededError( + f"Input payload token count ({token_count}) exceeds the limit of {self.token_limit} tokens.", + token_count, + self.token_limit, + ) \ No newline at end of file diff --git a/ai-hub/app/core/services/utils/code_change.py b/ai-hub/app/core/services/utils/code_change.py index c2eaad0..78d0fe3 100644 --- a/ai-hub/app/core/services/utils/code_change.py +++ b/ai-hub/app/core/services/utils/code_change.py @@ -17,7 +17,7 @@ A helper class to process and manage a sequence of code change instructions. """ - def __init__(self, db: Session, provider_name: str, input_data: str, request_id: uuid.UUID): + def __init__(self, db: Session, provider_name: str, input_data: str, reasoning: str,request_id: uuid.UUID): """ Initializes the CodeChangeHelper, parsing the input and setting up dependencies. @@ -28,6 +28,7 @@ """ self.db = db self.input_data = input_data + self.reasoning = reasoning self.llm_provider = get_llm_provider(provider_name) self.code_changer = CodeRagCodeChanger() @@ -151,6 +152,7 @@ client_log: Dict[str, Any] = { "type": "code_change", "content": formatted_content, + "reasoning": self.reasoning, "done": False, } await websocket.send_text(json.dumps(client_log)) diff --git a/ai-hub/app/core/services/workspace.py b/ai-hub/app/core/services/workspace.py index e309094..b40548c 100644 --- a/ai-hub/app/core/services/workspace.py +++ b/ai-hub/app/core/services/workspace.py @@ -17,7 +17,7 @@ from app.core.pipelines.dspy_rag import DspyRagPipeline from app.core.pipelines.question_decider import CodeRagQuestionDecider from app.core.services.utils.code_change import CodeChangeHelper - +from app.core.pipelines.validator import Validator,TokenLimitExceededError # A type hint for our handler functions MessageHandler = Callable[[WebSocket, Dict[str, Any]], Awaitable[None]] # Configure logging @@ -502,6 +502,14 @@ "content": "Error: request_id is missing in the response." })) return + try: + Validator().precheck_tokensize(files) + except TokenLimitExceededError as e: + await websocket.send_text(json.dumps({ + "type": "error", + "content": f"Error: {e}" + })) + return file_request = await self._get_file_request_by_id(uuid.UUID(request_id)) if not file_request: await websocket.send_text(json.dumps({ @@ -611,12 +619,19 @@ # Use the LLM to make a decision with dspy.context(lm=get_llm_provider(provider_name="gemini")): crqd = CodeRagQuestionDecider() - raw_answer_text, reasoning, decision = await crqd( - question=context_data.get("question", ""), - history=session.messages, - retrieved_data=context_data - ) - dspy.inspect_history(n=1) + try: + raw_answer_text, reasoning, decision = await crqd( + question=context_data.get("question", ""), + history=session.messages, + retrieved_data=context_data + ) + except ValueError as e: + await websocket.send_text(json.dumps({ + "type": "error", + "content": f"Failed to process AI decision request. Error: {e}" + })) + return + # dspy.inspect_history(n=1) if decision == "answer": # Handle regular answer @@ -670,7 +685,7 @@ try: # The input_data is a JSON string of code change instructions - cch = CodeChangeHelper(db=self.db, provider_name="gemini", input_data=raw_answer_text,request_id= uuid.UUID(request_id)) + cch = CodeChangeHelper(db=self.db, provider_name="gemini", input_data=raw_answer_text, reasoning = reasoning,request_id= uuid.UUID(request_id)) # Use the CodeChangeHelper to process all code changes final_changes = await cch.process(websocket=websocket) diff --git a/ui/client-app/src/components/ChatWindow.css b/ui/client-app/src/components/ChatWindow.css index c356690..0e6c37e 100644 --- a/ui/client-app/src/components/ChatWindow.css +++ b/ui/client-app/src/components/ChatWindow.css @@ -39,14 +39,14 @@ /* Style for each list item, simulating the file list item's look */ ol li { background-color: #f7f9fc; - border: 1px solid #e0e6ed; + /* border: 1px solid #e0e6ed; */ border-radius: 8px; padding: 1rem; margin-bottom: 0.75rem; display: flex; align-items: flex-start; gap: 1rem; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: pointer; /* Added a cursor pointer to make it feel clickable */ } @@ -54,14 +54,12 @@ /* Add a hover effect to match the file list */ ol li:hover { background-color: #e6e9ef; /* Adjusted hover color to be slightly darker */ - transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + /* transform: translateY(-2px); */ + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } /* The magic for the step prefix using a pseudo-element */ ol li::before { - content: "Step " counter(step-counter) "."; /* Added a period for clarity */ - counter-increment: step-counter; font-size: 0.9rem; font-weight: bold; color: #3b82f6; /* Used the vibrant blue from the icon */ diff --git a/ui/client-app/src/hooks/useCodeAssistant.js b/ui/client-app/src/hooks/useCodeAssistant.js index a8bfcf9..90075f0 100644 --- a/ui/client-app/src/hooks/useCodeAssistant.js +++ b/ui/client-app/src/hooks/useCodeAssistant.js @@ -34,7 +34,8 @@ setChatHistory((prev) => [...prev, { isUser: false, text: message.content, - code_changes: message.code_changes + code_changes: message.code_changes, + reasoning: message.reasoning }]); if (message.done === true){ setIsProcessing(false);