mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Merge branch 'main' of https://github.com/manishmadan2882/docsgpt
This commit is contained in:
@@ -48,10 +48,13 @@
|
|||||||
- [x] Add tools (Jan 2025)
|
- [x] Add tools (Jan 2025)
|
||||||
- [x] Manually updating chunks in the app UI (Feb 2025)
|
- [x] Manually updating chunks in the app UI (Feb 2025)
|
||||||
- [x] Devcontainer for easy development (Feb 2025)
|
- [x] Devcontainer for easy development (Feb 2025)
|
||||||
|
- [x] ReACT agent (March 2025)
|
||||||
- [ ] Anthropic Tool compatibility
|
- [ ] Anthropic Tool compatibility
|
||||||
|
- [ ] New input box in the conversation menu
|
||||||
- [ ] Add triggerable actions / tools (webhook)
|
- [ ] Add triggerable actions / tools (webhook)
|
||||||
- [ ] Add OAuth 2.0 authentication for tools and sources
|
- [ ] Add OAuth 2.0 authentication for tools and sources
|
||||||
- [ ] Chatbots menu re-design to handle tools, scheduling, and more
|
- [ ] Chatbots menu re-design to handle tools, agent types, and more
|
||||||
|
- [ ] Agent scheduling
|
||||||
|
|
||||||
You can find our full roadmap [here](https://github.com/orgs/arc53/projects/2). Please don't hesitate to contribute or create issues, it helps us improve DocsGPT!
|
You can find our full roadmap [here](https://github.com/orgs/arc53/projects/2). Please don't hesitate to contribute or create issues, it helps us improve DocsGPT!
|
||||||
|
|
||||||
|
|||||||
0
application/agents/__init__.py
Normal file
0
application/agents/__init__.py
Normal file
@@ -1,9 +1,11 @@
|
|||||||
from application.agents.classic_agent import ClassicAgent
|
from application.agents.classic_agent import ClassicAgent
|
||||||
|
from application.agents.react_agent import ReActAgent
|
||||||
|
|
||||||
|
|
||||||
class AgentCreator:
|
class AgentCreator:
|
||||||
agents = {
|
agents = {
|
||||||
"classic": ClassicAgent,
|
"classic": ClassicAgent,
|
||||||
|
"react": ReActAgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from typing import Dict, Generator
|
import uuid
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, Generator, List, Optional
|
||||||
|
|
||||||
from application.agents.llm_handler import get_llm_handler
|
from application.agents.llm_handler import get_llm_handler
|
||||||
from application.agents.tools.tool_action_parser import ToolActionParser
|
from application.agents.tools.tool_action_parser import ToolActionParser
|
||||||
@@ -6,20 +8,35 @@ from application.agents.tools.tool_manager import ToolManager
|
|||||||
|
|
||||||
from application.core.mongo_db import MongoDB
|
from application.core.mongo_db import MongoDB
|
||||||
from application.llm.llm_creator import LLMCreator
|
from application.llm.llm_creator import LLMCreator
|
||||||
|
from application.logging import build_stack_data, log_activity, LogContext
|
||||||
|
from application.retriever.base import BaseRetriever
|
||||||
|
|
||||||
|
|
||||||
class BaseAgent:
|
class BaseAgent(ABC):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
endpoint,
|
endpoint: str,
|
||||||
llm_name,
|
llm_name: str,
|
||||||
gpt_model,
|
gpt_model: str,
|
||||||
api_key,
|
api_key: str,
|
||||||
user_api_key=None,
|
user_api_key: Optional[str] = None,
|
||||||
decoded_token=None,
|
prompt: str = "",
|
||||||
attachments=None,
|
chat_history: Optional[List[Dict]] = None,
|
||||||
|
decoded_token: Optional[Dict] = None,
|
||||||
|
attachments: Optional[str]=None,
|
||||||
):
|
):
|
||||||
self.endpoint = endpoint
|
self.endpoint = endpoint
|
||||||
|
self.llm_name = llm_name
|
||||||
|
self.gpt_model = gpt_model
|
||||||
|
self.api_key = api_key
|
||||||
|
self.user_api_key = user_api_key
|
||||||
|
self.prompt = prompt
|
||||||
|
self.decoded_token = decoded_token or {}
|
||||||
|
self.user: str = decoded_token.get("sub")
|
||||||
|
self.tool_config: Dict = {}
|
||||||
|
self.tools: List[Dict] = []
|
||||||
|
self.tool_calls: List[Dict] = []
|
||||||
|
self.chat_history: List[Dict] = chat_history if chat_history is not None else []
|
||||||
self.llm = LLMCreator.create_llm(
|
self.llm = LLMCreator.create_llm(
|
||||||
llm_name,
|
llm_name,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
@@ -27,14 +44,19 @@ class BaseAgent:
|
|||||||
decoded_token=decoded_token,
|
decoded_token=decoded_token,
|
||||||
)
|
)
|
||||||
self.llm_handler = get_llm_handler(llm_name)
|
self.llm_handler = get_llm_handler(llm_name)
|
||||||
self.gpt_model = gpt_model
|
set.attachments = attachments or []
|
||||||
self.tools = []
|
|
||||||
self.tool_config = {}
|
|
||||||
self.tool_calls = []
|
|
||||||
self.attachments = attachments or []
|
|
||||||
|
|
||||||
def gen(self, *args, **kwargs) -> Generator[Dict, None, None]:
|
@log_activity()
|
||||||
raise NotImplementedError('Method "gen" must be implemented in the child class')
|
def gen(
|
||||||
|
self, query: str, retriever: BaseRetriever, log_context: LogContext = None
|
||||||
|
) -> Generator[Dict, None, None]:
|
||||||
|
yield from self._gen_inner(query, retriever, log_context)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _gen_inner(
|
||||||
|
self, query: str, retriever: BaseRetriever, log_context: LogContext
|
||||||
|
) -> Generator[Dict, None, None]:
|
||||||
|
pass
|
||||||
|
|
||||||
def _get_user_tools(self, user="local"):
|
def _get_user_tools(self, user="local"):
|
||||||
mongo = MongoDB.get_client()
|
mongo = MongoDB.get_client()
|
||||||
@@ -111,14 +133,12 @@ class BaseAgent:
|
|||||||
for param, details in action_data[param_type]["properties"].items():
|
for param, details in action_data[param_type]["properties"].items():
|
||||||
if param not in call_args and "value" in details:
|
if param not in call_args and "value" in details:
|
||||||
target_dict[param] = details["value"]
|
target_dict[param] = details["value"]
|
||||||
|
|
||||||
for param, value in call_args.items():
|
for param, value in call_args.items():
|
||||||
for param_type, target_dict in param_types.items():
|
for param_type, target_dict in param_types.items():
|
||||||
if param_type in action_data and param in action_data[param_type].get(
|
if param_type in action_data and param in action_data[param_type].get(
|
||||||
"properties", {}
|
"properties", {}
|
||||||
):
|
):
|
||||||
target_dict[param] = value
|
target_dict[param] = value
|
||||||
|
|
||||||
tm = ToolManager(config={})
|
tm = ToolManager(config={})
|
||||||
tool = tm.load_tool(
|
tool = tm.load_tool(
|
||||||
tool_data["name"],
|
tool_data["name"],
|
||||||
@@ -153,3 +173,79 @@ class BaseAgent:
|
|||||||
self.tool_calls.append(tool_call_data)
|
self.tool_calls.append(tool_call_data)
|
||||||
|
|
||||||
return result, call_id
|
return result, call_id
|
||||||
|
|
||||||
|
def _build_messages(
|
||||||
|
self,
|
||||||
|
system_prompt: str,
|
||||||
|
query: str,
|
||||||
|
retrieved_data: List[Dict],
|
||||||
|
) -> List[Dict]:
|
||||||
|
docs_together = "\n".join([doc["text"] for doc in retrieved_data])
|
||||||
|
p_chat_combine = system_prompt.replace("{summaries}", docs_together)
|
||||||
|
messages_combine = [{"role": "system", "content": p_chat_combine}]
|
||||||
|
|
||||||
|
for i in self.chat_history:
|
||||||
|
if "prompt" in i and "response" in i:
|
||||||
|
messages_combine.append({"role": "user", "content": i["prompt"]})
|
||||||
|
messages_combine.append({"role": "assistant", "content": i["response"]})
|
||||||
|
if "tool_calls" in i:
|
||||||
|
for tool_call in i["tool_calls"]:
|
||||||
|
call_id = tool_call.get("call_id") or str(uuid.uuid4())
|
||||||
|
|
||||||
|
function_call_dict = {
|
||||||
|
"function_call": {
|
||||||
|
"name": tool_call.get("action_name"),
|
||||||
|
"args": tool_call.get("arguments"),
|
||||||
|
"call_id": call_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function_response_dict = {
|
||||||
|
"function_response": {
|
||||||
|
"name": tool_call.get("action_name"),
|
||||||
|
"response": {"result": tool_call.get("result")},
|
||||||
|
"call_id": call_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messages_combine.append(
|
||||||
|
{"role": "assistant", "content": [function_call_dict]}
|
||||||
|
)
|
||||||
|
messages_combine.append(
|
||||||
|
{"role": "tool", "content": [function_response_dict]}
|
||||||
|
)
|
||||||
|
messages_combine.append({"role": "user", "content": query})
|
||||||
|
return messages_combine
|
||||||
|
|
||||||
|
def _retriever_search(
|
||||||
|
self,
|
||||||
|
retriever: BaseRetriever,
|
||||||
|
query: str,
|
||||||
|
log_context: Optional[LogContext] = None,
|
||||||
|
) -> List[Dict]:
|
||||||
|
retrieved_data = retriever.search(query)
|
||||||
|
if log_context:
|
||||||
|
data = build_stack_data(retriever, exclude_attributes=["llm"])
|
||||||
|
log_context.stacks.append({"component": "retriever", "data": data})
|
||||||
|
return retrieved_data
|
||||||
|
|
||||||
|
def _llm_gen(self, messages: List[Dict], log_context: Optional[LogContext] = None):
|
||||||
|
resp = self.llm.gen_stream(
|
||||||
|
model=self.gpt_model, messages=messages, tools=self.tools
|
||||||
|
)
|
||||||
|
if log_context:
|
||||||
|
data = build_stack_data(self.llm)
|
||||||
|
log_context.stacks.append({"component": "llm", "data": data})
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def _llm_handler(
|
||||||
|
self,
|
||||||
|
resp,
|
||||||
|
tools_dict: Dict,
|
||||||
|
messages: List[Dict],
|
||||||
|
log_context: Optional[LogContext] = None,
|
||||||
|
):
|
||||||
|
resp = self.llm_handler.handle_response(self, resp, tools_dict, messages)
|
||||||
|
if log_context:
|
||||||
|
data = build_stack_data(self.llm_handler)
|
||||||
|
log_context.stacks.append({"component": "llm_handler", "data": data})
|
||||||
|
return resp
|
||||||
|
|||||||
@@ -1,88 +1,26 @@
|
|||||||
import uuid
|
|
||||||
from typing import Dict, Generator
|
from typing import Dict, Generator
|
||||||
|
|
||||||
from application.agents.base import BaseAgent
|
from application.agents.base import BaseAgent
|
||||||
from application.logging import build_stack_data, log_activity, LogContext
|
from application.logging import LogContext
|
||||||
|
|
||||||
from application.retriever.base import BaseRetriever
|
from application.retriever.base import BaseRetriever
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ClassicAgent(BaseAgent):
|
class ClassicAgent(BaseAgent):
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
endpoint,
|
|
||||||
llm_name,
|
|
||||||
gpt_model,
|
|
||||||
api_key,
|
|
||||||
user_api_key=None,
|
|
||||||
prompt="",
|
|
||||||
chat_history=None,
|
|
||||||
decoded_token=None,
|
|
||||||
attachments=None,
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
endpoint, llm_name, gpt_model, api_key, user_api_key, decoded_token, attachments
|
|
||||||
)
|
|
||||||
self.user = decoded_token.get("sub")
|
|
||||||
self.prompt = prompt
|
|
||||||
self.chat_history = chat_history if chat_history is not None else []
|
|
||||||
|
|
||||||
@log_activity()
|
|
||||||
def gen(
|
|
||||||
self, query: str, retriever: BaseRetriever, log_context: LogContext = None
|
|
||||||
) -> Generator[Dict, None, None]:
|
|
||||||
yield from self._gen_inner(query, retriever, log_context)
|
|
||||||
|
|
||||||
def _gen_inner(
|
def _gen_inner(
|
||||||
self, query: str, retriever: BaseRetriever, log_context: LogContext
|
self, query: str, retriever: BaseRetriever, log_context: LogContext
|
||||||
) -> Generator[Dict, None, None]:
|
) -> Generator[Dict, None, None]:
|
||||||
retrieved_data = self._retriever_search(retriever, query, log_context)
|
retrieved_data = self._retriever_search(retriever, query, log_context)
|
||||||
|
|
||||||
docs_together = "\n".join([doc["text"] for doc in retrieved_data])
|
|
||||||
p_chat_combine = self.prompt.replace("{summaries}", docs_together)
|
|
||||||
messages_combine = [{"role": "system", "content": p_chat_combine}]
|
|
||||||
|
|
||||||
if len(self.chat_history) > 0:
|
|
||||||
for i in self.chat_history:
|
|
||||||
if "prompt" in i and "response" in i:
|
|
||||||
messages_combine.append({"role": "user", "content": i["prompt"]})
|
|
||||||
messages_combine.append(
|
|
||||||
{"role": "assistant", "content": i["response"]}
|
|
||||||
)
|
|
||||||
if "tool_calls" in i:
|
|
||||||
for tool_call in i["tool_calls"]:
|
|
||||||
call_id = tool_call.get("call_id")
|
|
||||||
if call_id is None or call_id == "None":
|
|
||||||
call_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
function_call_dict = {
|
|
||||||
"function_call": {
|
|
||||||
"name": tool_call.get("action_name"),
|
|
||||||
"args": tool_call.get("arguments"),
|
|
||||||
"call_id": call_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function_response_dict = {
|
|
||||||
"function_response": {
|
|
||||||
"name": tool_call.get("action_name"),
|
|
||||||
"response": {"result": tool_call.get("result")},
|
|
||||||
"call_id": call_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
messages_combine.append(
|
|
||||||
{"role": "assistant", "content": [function_call_dict]}
|
|
||||||
)
|
|
||||||
messages_combine.append(
|
|
||||||
{"role": "tool", "content": [function_response_dict]}
|
|
||||||
)
|
|
||||||
messages_combine.append({"role": "user", "content": query})
|
|
||||||
|
|
||||||
tools_dict = self._get_user_tools(self.user)
|
tools_dict = self._get_user_tools(self.user)
|
||||||
self._prepare_tools(tools_dict)
|
self._prepare_tools(tools_dict)
|
||||||
|
|
||||||
resp = self._llm_gen(messages_combine, log_context)
|
messages = self._build_messages(self.prompt, query, retrieved_data)
|
||||||
|
|
||||||
|
resp = self._llm_gen(messages, log_context)
|
||||||
|
|
||||||
|
attachments = self.attachments
|
||||||
|
|
||||||
if isinstance(resp, str):
|
if isinstance(resp, str):
|
||||||
yield {"answer": resp}
|
yield {"answer": resp}
|
||||||
@@ -95,7 +33,7 @@ class ClassicAgent(BaseAgent):
|
|||||||
yield {"answer": resp.message.content}
|
yield {"answer": resp.message.content}
|
||||||
return
|
return
|
||||||
|
|
||||||
resp = self._llm_handler(resp, tools_dict, messages_combine, log_context, self.attachments)
|
resp = self._llm_handler(resp, tools_dict, messages, log_context,attachments)
|
||||||
|
|
||||||
if isinstance(resp, str):
|
if isinstance(resp, str):
|
||||||
yield {"answer": resp}
|
yield {"answer": resp}
|
||||||
@@ -107,7 +45,7 @@ class ClassicAgent(BaseAgent):
|
|||||||
yield {"answer": resp.message.content}
|
yield {"answer": resp.message.content}
|
||||||
else:
|
else:
|
||||||
completion = self.llm.gen_stream(
|
completion = self.llm.gen_stream(
|
||||||
model=self.gpt_model, messages=messages_combine, tools=self.tools
|
model=self.gpt_model, messages=messages, tools=self.tools
|
||||||
)
|
)
|
||||||
for line in completion:
|
for line in completion:
|
||||||
if isinstance(line, str):
|
if isinstance(line, str):
|
||||||
@@ -115,29 +53,3 @@ class ClassicAgent(BaseAgent):
|
|||||||
|
|
||||||
yield {"sources": retrieved_data}
|
yield {"sources": retrieved_data}
|
||||||
yield {"tool_calls": self.tool_calls.copy()}
|
yield {"tool_calls": self.tool_calls.copy()}
|
||||||
|
|
||||||
def _retriever_search(self, retriever, query, log_context):
|
|
||||||
retrieved_data = retriever.search(query)
|
|
||||||
if log_context:
|
|
||||||
data = build_stack_data(retriever, exclude_attributes=["llm"])
|
|
||||||
log_context.stacks.append({"component": "retriever", "data": data})
|
|
||||||
return retrieved_data
|
|
||||||
|
|
||||||
def _llm_gen(self, messages_combine, log_context):
|
|
||||||
resp = self.llm.gen_stream(
|
|
||||||
model=self.gpt_model, messages=messages_combine, tools=self.tools
|
|
||||||
)
|
|
||||||
if log_context:
|
|
||||||
data = build_stack_data(self.llm)
|
|
||||||
log_context.stacks.append({"component": "llm", "data": data})
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def _llm_handler(self, resp, tools_dict, messages_combine, log_context, attachments=None):
|
|
||||||
logger.info(f"Handling LLM response with {len(attachments) if attachments else 0} attachments")
|
|
||||||
resp = self.llm_handler.handle_response(
|
|
||||||
self, resp, tools_dict, messages_combine, attachments=attachments
|
|
||||||
)
|
|
||||||
if log_context:
|
|
||||||
data = build_stack_data(self.llm_handler)
|
|
||||||
log_context.stacks.append({"component": "llm_handler", "data": data})
|
|
||||||
return resp
|
|
||||||
|
|||||||
121
application/agents/react_agent.py
Normal file
121
application/agents/react_agent.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import os
|
||||||
|
from typing import Dict, Generator, List
|
||||||
|
|
||||||
|
from application.agents.base import BaseAgent
|
||||||
|
from application.logging import build_stack_data, LogContext
|
||||||
|
from application.retriever.base import BaseRetriever
|
||||||
|
|
||||||
|
current_dir = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
)
|
||||||
|
with open(
|
||||||
|
os.path.join(current_dir, "application/prompts", "react_planning_prompt.txt"), "r"
|
||||||
|
) as f:
|
||||||
|
planning_prompt = f.read()
|
||||||
|
with open(
|
||||||
|
os.path.join(current_dir, "application/prompts", "react_final_prompt.txt"),
|
||||||
|
"r",
|
||||||
|
) as f:
|
||||||
|
final_prompt = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
class ReActAgent(BaseAgent):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.plan = ""
|
||||||
|
self.observations: List[str] = []
|
||||||
|
|
||||||
|
def _gen_inner(
|
||||||
|
self, query: str, retriever: BaseRetriever, log_context: LogContext
|
||||||
|
) -> Generator[Dict, None, None]:
|
||||||
|
retrieved_data = self._retriever_search(retriever, query, log_context)
|
||||||
|
|
||||||
|
tools_dict = self._get_user_tools(self.user)
|
||||||
|
self._prepare_tools(tools_dict)
|
||||||
|
|
||||||
|
docs_together = "\n".join([doc["text"] for doc in retrieved_data])
|
||||||
|
plan = self._create_plan(query, docs_together, log_context)
|
||||||
|
for line in plan:
|
||||||
|
if isinstance(line, str):
|
||||||
|
self.plan += line
|
||||||
|
yield {"thought": line}
|
||||||
|
|
||||||
|
prompt = self.prompt + f"\nFollow this plan: {self.plan}"
|
||||||
|
messages = self._build_messages(prompt, query, retrieved_data)
|
||||||
|
|
||||||
|
resp = self._llm_gen(messages, log_context)
|
||||||
|
|
||||||
|
if isinstance(resp, str):
|
||||||
|
self.observations.append(resp)
|
||||||
|
if (
|
||||||
|
hasattr(resp, "message")
|
||||||
|
and hasattr(resp.message, "content")
|
||||||
|
and resp.message.content is not None
|
||||||
|
):
|
||||||
|
self.observations.append(resp.message.content)
|
||||||
|
|
||||||
|
resp = self._llm_handler(resp, tools_dict, messages, log_context)
|
||||||
|
|
||||||
|
for tool_call in self.tool_calls:
|
||||||
|
observation = (
|
||||||
|
f"Action '{tool_call['action_name']}' of tool '{tool_call['tool_name']}' "
|
||||||
|
f"with arguments '{tool_call['arguments']}' returned: '{tool_call['result']}'"
|
||||||
|
)
|
||||||
|
self.observations.append(observation)
|
||||||
|
|
||||||
|
if isinstance(resp, str):
|
||||||
|
self.observations.append(resp)
|
||||||
|
elif (
|
||||||
|
hasattr(resp, "message")
|
||||||
|
and hasattr(resp.message, "content")
|
||||||
|
and resp.message.content is not None
|
||||||
|
):
|
||||||
|
self.observations.append(resp.message.content)
|
||||||
|
else:
|
||||||
|
completion = self.llm.gen_stream(
|
||||||
|
model=self.gpt_model, messages=messages, tools=self.tools
|
||||||
|
)
|
||||||
|
for line in completion:
|
||||||
|
if isinstance(line, str):
|
||||||
|
self.observations.append(line)
|
||||||
|
|
||||||
|
yield {"sources": retrieved_data}
|
||||||
|
yield {"tool_calls": self.tool_calls.copy()}
|
||||||
|
|
||||||
|
final_answer = self._create_final_answer(query, self.observations, log_context)
|
||||||
|
for line in final_answer:
|
||||||
|
if isinstance(line, str):
|
||||||
|
yield {"answer": line}
|
||||||
|
|
||||||
|
def _create_plan(
|
||||||
|
self, query: str, docs_data: str, log_context: LogContext = None
|
||||||
|
) -> Generator[str, None, None]:
|
||||||
|
plan_prompt = planning_prompt.replace("{query}", query)
|
||||||
|
if "{summaries}" in planning_prompt:
|
||||||
|
summaries = docs_data
|
||||||
|
plan_prompt = plan_prompt.replace("{summaries}", summaries)
|
||||||
|
|
||||||
|
messages = [{"role": "user", "content": plan_prompt}]
|
||||||
|
print(self.tools)
|
||||||
|
plan = self.llm.gen_stream(
|
||||||
|
model=self.gpt_model, messages=messages, tools=self.tools
|
||||||
|
)
|
||||||
|
if log_context:
|
||||||
|
data = build_stack_data(self.llm)
|
||||||
|
log_context.stacks.append({"component": "planning_llm", "data": data})
|
||||||
|
return plan
|
||||||
|
|
||||||
|
def _create_final_answer(
|
||||||
|
self, query: str, observations: List[str], log_context: LogContext = None
|
||||||
|
) -> str:
|
||||||
|
observation_string = "\n".join(observations)
|
||||||
|
final_answer_prompt = final_prompt.format(
|
||||||
|
query=query, observations=observation_string
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = [{"role": "user", "content": final_answer_prompt}]
|
||||||
|
final_answer = self.llm.gen_stream(model=self.gpt_model, messages=messages)
|
||||||
|
if log_context:
|
||||||
|
data = build_stack_data(self.llm)
|
||||||
|
log_context.stacks.append({"component": "final_answer_llm", "data": data})
|
||||||
|
return final_answer
|
||||||
@@ -122,30 +122,28 @@ def save_conversation(
|
|||||||
conversation_id,
|
conversation_id,
|
||||||
question,
|
question,
|
||||||
response,
|
response,
|
||||||
|
thought,
|
||||||
source_log_docs,
|
source_log_docs,
|
||||||
tool_calls,
|
tool_calls,
|
||||||
llm,
|
llm,
|
||||||
decoded_token,
|
decoded_token,
|
||||||
index=None,
|
index=None,
|
||||||
api_key=None,
|
api_key=None
|
||||||
attachment_ids=None,
|
|
||||||
):
|
):
|
||||||
current_time = datetime.datetime.now(datetime.timezone.utc)
|
current_time = datetime.datetime.now(datetime.timezone.utc)
|
||||||
if conversation_id is not None and index is not None:
|
if conversation_id is not None and index is not None:
|
||||||
update_data = {
|
conversations_collection.update_one(
|
||||||
|
{"_id": ObjectId(conversation_id), f"queries.{index}": {"$exists": True}},
|
||||||
|
{
|
||||||
|
"$set": {
|
||||||
f"queries.{index}.prompt": question,
|
f"queries.{index}.prompt": question,
|
||||||
f"queries.{index}.response": response,
|
f"queries.{index}.response": response,
|
||||||
|
f"queries.{index}.thought": thought,
|
||||||
f"queries.{index}.sources": source_log_docs,
|
f"queries.{index}.sources": source_log_docs,
|
||||||
f"queries.{index}.tool_calls": tool_calls,
|
f"queries.{index}.tool_calls": tool_calls,
|
||||||
f"queries.{index}.timestamp": current_time,
|
f"queries.{index}.timestamp": current_time,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
if attachment_ids:
|
|
||||||
update_data[f"queries.{index}.attachments"] = attachment_ids
|
|
||||||
|
|
||||||
conversations_collection.update_one(
|
|
||||||
{"_id": ObjectId(conversation_id), f"queries.{index}": {"$exists": True}},
|
|
||||||
{"$set": update_data},
|
|
||||||
)
|
)
|
||||||
##remove following queries from the array
|
##remove following queries from the array
|
||||||
conversations_collection.update_one(
|
conversations_collection.update_one(
|
||||||
@@ -153,20 +151,20 @@ def save_conversation(
|
|||||||
{"$push": {"queries": {"$each": [], "$slice": index + 1}}},
|
{"$push": {"queries": {"$each": [], "$slice": index + 1}}},
|
||||||
)
|
)
|
||||||
elif conversation_id is not None and conversation_id != "None":
|
elif conversation_id is not None and conversation_id != "None":
|
||||||
query_data = {
|
conversations_collection.update_one(
|
||||||
|
{"_id": ObjectId(conversation_id)},
|
||||||
|
{
|
||||||
|
"$push": {
|
||||||
|
"queries": {
|
||||||
"prompt": question,
|
"prompt": question,
|
||||||
"response": response,
|
"response": response,
|
||||||
|
"thought": thought,
|
||||||
"sources": source_log_docs,
|
"sources": source_log_docs,
|
||||||
"tool_calls": tool_calls,
|
"tool_calls": tool_calls,
|
||||||
"timestamp": current_time,
|
"timestamp": current_time,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if attachment_ids:
|
},
|
||||||
query_data["attachments"] = attachment_ids
|
|
||||||
|
|
||||||
conversations_collection.update_one(
|
|
||||||
{"_id": ObjectId(conversation_id)},
|
|
||||||
{"$push": {"queries": query_data}},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -196,6 +194,7 @@ def save_conversation(
|
|||||||
{
|
{
|
||||||
"prompt": question,
|
"prompt": question,
|
||||||
"response": response,
|
"response": response,
|
||||||
|
"thought": thought,
|
||||||
"sources": source_log_docs,
|
"sources": source_log_docs,
|
||||||
"tool_calls": tool_calls,
|
"tool_calls": tool_calls,
|
||||||
"timestamp": current_time,
|
"timestamp": current_time,
|
||||||
@@ -237,9 +236,7 @@ def complete_stream(
|
|||||||
attachments=None,
|
attachments=None,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
response_full = ""
|
response_full, thought, source_log_docs, tool_calls = "", "", [], []
|
||||||
source_log_docs = []
|
|
||||||
tool_calls = []
|
|
||||||
attachment_ids = []
|
attachment_ids = []
|
||||||
|
|
||||||
if attachments:
|
if attachments:
|
||||||
@@ -270,6 +267,10 @@ def complete_stream(
|
|||||||
tool_calls = line["tool_calls"]
|
tool_calls = line["tool_calls"]
|
||||||
data = json.dumps({"type": "tool_calls", "tool_calls": tool_calls})
|
data = json.dumps({"type": "tool_calls", "tool_calls": tool_calls})
|
||||||
yield f"data: {data}\n\n"
|
yield f"data: {data}\n\n"
|
||||||
|
elif "thought" in line:
|
||||||
|
thought += line["thought"]
|
||||||
|
data = json.dumps({"type": "thought", "thought": line["thought"]})
|
||||||
|
yield f"data: {data}\n\n"
|
||||||
|
|
||||||
if isNoneDoc:
|
if isNoneDoc:
|
||||||
for doc in source_log_docs:
|
for doc in source_log_docs:
|
||||||
@@ -287,13 +288,13 @@ def complete_stream(
|
|||||||
conversation_id,
|
conversation_id,
|
||||||
question,
|
question,
|
||||||
response_full,
|
response_full,
|
||||||
|
thought,
|
||||||
source_log_docs,
|
source_log_docs,
|
||||||
tool_calls,
|
tool_calls,
|
||||||
llm,
|
llm,
|
||||||
decoded_token,
|
decoded_token,
|
||||||
index,
|
index,
|
||||||
api_key=user_api_key,
|
api_key=user_api_key
|
||||||
attachment_ids=attachment_ids,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conversation_id = None
|
conversation_id = None
|
||||||
|
|||||||
3
application/prompts/react_final_prompt.txt
Normal file
3
application/prompts/react_final_prompt.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Query: {query}
|
||||||
|
Observations: {observations}
|
||||||
|
Now, using the insights from the observations, formulate a well-structured and precise final answer.
|
||||||
10
application/prompts/react_planning_prompt.txt
Normal file
10
application/prompts/react_planning_prompt.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
You are an AI assistant and talk like you're thinking out loud. Given the following query, outline a concise thought process that includes key steps and considerations necessary for effective analysis and response. Avoid pointwise formatting. The goal is to break down the query into manageable components without excessive detail, focusing on clarity and logical progression.
|
||||||
|
|
||||||
|
Include the following elements in your thought process:
|
||||||
|
1. Identify the main objective of the query.
|
||||||
|
2. Determine any relevant context or background information needed to understand the query.
|
||||||
|
3. List potential approaches or methods to address the query.
|
||||||
|
4. Highlight any critical factors or constraints that may influence the outcome.
|
||||||
|
|
||||||
|
Query: {query}
|
||||||
|
Summaries: {summaries}
|
||||||
@@ -35,7 +35,7 @@ langchain-community==0.3.19
|
|||||||
langchain-core==0.3.45
|
langchain-core==0.3.45
|
||||||
langchain-openai==0.3.8
|
langchain-openai==0.3.8
|
||||||
langchain-text-splitters==0.3.6
|
langchain-text-splitters==0.3.6
|
||||||
langsmith==0.3.15
|
langsmith==0.3.19
|
||||||
lazy-object-proxy==1.10.0
|
lazy-object-proxy==1.10.0
|
||||||
lxml==5.3.1
|
lxml==5.3.1
|
||||||
markupsafe==3.0.2
|
markupsafe==3.0.2
|
||||||
@@ -65,7 +65,7 @@ py==1.11.0
|
|||||||
pydantic==2.10.6
|
pydantic==2.10.6
|
||||||
pydantic-core==2.27.2
|
pydantic-core==2.27.2
|
||||||
pydantic-settings==2.7.1
|
pydantic-settings==2.7.1
|
||||||
pymongo==4.10.1
|
pymongo==4.11.3
|
||||||
pypdf==5.2.0
|
pypdf==5.2.0
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
|
|||||||
11
frontend/src/assets/cloud.svg
Normal file
11
frontend/src/assets/cloud.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2 16C2.53043 16 3.03914 16.2107 3.41421 16.5858C3.78929 16.9609 4 17.4696 4 18C4 18.5304 3.78929 19.0391 3.41421 19.4142C3.03914 19.7893 2.53043 20 2 20C1.46957 20 0.960859 19.7893 0.585786 19.4142C0.210714 19.0391 0 18.5304 0 18C0 17.4696 0.210714 16.9609 0.585786 16.5858C0.960859 16.2107 1.46957 16 2 16ZM7.5 13C8.16304 13 8.79893 13.2634 9.26777 13.7322C9.73661 14.2011 10 14.837 10 15.5C10 16.163 9.73661 16.7989 9.26777 17.2678C8.79893 17.7366 8.16304 18 7.5 18C6.83696 18 6.20107 17.7366 5.73223 17.2678C5.26339 16.7989 5 16.163 5 15.5C5 14.837 5.26339 14.2011 5.73223 13.7322C6.20107 13.2634 6.83696 13 7.5 13ZM10 0C11.272 0.000250351 12.5033 0.448355 13.4779 1.26572C14.4525 2.08308 15.1082 3.2175 15.33 4.47H15.412C16.4105 4.47 17.3682 4.86667 18.0743 5.57274C18.7803 6.27882 19.177 7.23646 19.177 8.235C19.177 9.23354 18.7803 10.1912 18.0743 10.8973C17.3682 11.6033 16.4105 12 15.412 12H4.588C3.58946 12 2.63182 11.6033 1.92574 10.8973C1.21967 10.1912 0.823 9.23354 0.823 8.235C0.823 7.23646 1.21967 6.27882 1.92574 5.57274C2.63182 4.86667 3.58946 4.47 4.588 4.47H4.67C4.89179 3.2175 5.54749 2.08308 6.52211 1.26572C7.49673 0.448355 8.72801 0.000250351 10 0Z" fill="url(#paint0_linear_6161_11984)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_6161_11984" x1="0" y1="10" x2="19.177" y2="10" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#70FDF7"/>
|
||||||
|
<stop offset="0.325" stop-color="#747696"/>
|
||||||
|
<stop offset="0.68" stop-color="#BD5372"/>
|
||||||
|
<stop offset="1" stop-color="#F5A06C"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -6,16 +6,16 @@ import ReactMarkdown from 'react-markdown';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import {
|
import {
|
||||||
vscDarkPlus,
|
|
||||||
oneLight,
|
oneLight,
|
||||||
|
vscDarkPlus,
|
||||||
} from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
} from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||||
import rehypeKatex from 'rehype-katex';
|
import rehypeKatex from 'rehype-katex';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
import { useDarkTheme } from '../hooks';
|
|
||||||
|
|
||||||
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
|
|
||||||
import ChevronDown from '../assets/chevron-down.svg';
|
import ChevronDown from '../assets/chevron-down.svg';
|
||||||
|
import Cloud from '../assets/cloud.svg';
|
||||||
|
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
|
||||||
import Dislike from '../assets/dislike.svg?react';
|
import Dislike from '../assets/dislike.svg?react';
|
||||||
import Document from '../assets/document.svg';
|
import Document from '../assets/document.svg';
|
||||||
import Edit from '../assets/edit.svg';
|
import Edit from '../assets/edit.svg';
|
||||||
@@ -28,7 +28,7 @@ import Avatar from '../components/Avatar';
|
|||||||
import CopyButton from '../components/CopyButton';
|
import CopyButton from '../components/CopyButton';
|
||||||
import Sidebar from '../components/Sidebar';
|
import Sidebar from '../components/Sidebar';
|
||||||
import SpeakButton from '../components/TextToSpeechButton';
|
import SpeakButton from '../components/TextToSpeechButton';
|
||||||
import { useOutsideAlerter } from '../hooks';
|
import { useDarkTheme, useOutsideAlerter } from '../hooks';
|
||||||
import {
|
import {
|
||||||
selectChunks,
|
selectChunks,
|
||||||
selectSelectedDocs,
|
selectSelectedDocs,
|
||||||
@@ -42,11 +42,12 @@ const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false;
|
|||||||
const ConversationBubble = forwardRef<
|
const ConversationBubble = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
{
|
{
|
||||||
message: string;
|
message?: string;
|
||||||
type: MESSAGE_TYPE;
|
type: MESSAGE_TYPE;
|
||||||
className?: string;
|
className?: string;
|
||||||
feedback?: FEEDBACK;
|
feedback?: FEEDBACK;
|
||||||
handleFeedback?: (feedback: FEEDBACK) => void;
|
handleFeedback?: (feedback: FEEDBACK) => void;
|
||||||
|
thought?: string;
|
||||||
sources?: { title: string; text: string; source: string }[];
|
sources?: { title: string; text: string; source: string }[];
|
||||||
toolCalls?: ToolCallsType[];
|
toolCalls?: ToolCallsType[];
|
||||||
retryBtn?: React.ReactElement;
|
retryBtn?: React.ReactElement;
|
||||||
@@ -65,6 +66,7 @@ const ConversationBubble = forwardRef<
|
|||||||
className,
|
className,
|
||||||
feedback,
|
feedback,
|
||||||
handleFeedback,
|
handleFeedback,
|
||||||
|
thought,
|
||||||
sources,
|
sources,
|
||||||
toolCalls,
|
toolCalls,
|
||||||
retryBtn,
|
retryBtn,
|
||||||
@@ -160,7 +162,7 @@ const ConversationBubble = forwardRef<
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsEditClicked(true);
|
setIsEditClicked(true);
|
||||||
setEditInputBox(message);
|
setEditInputBox(message ?? '');
|
||||||
}}
|
}}
|
||||||
className={`flex-shrink-0 h-fit mt-3 p-2 cursor-pointer rounded-full hover:bg-light-silver dark:hover:bg-[#35363B] flex items-center ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
|
className={`flex-shrink-0 h-fit mt-3 p-2 cursor-pointer rounded-full hover:bg-light-silver dark:hover:bg-[#35363B] flex items-center ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
|
||||||
>
|
>
|
||||||
@@ -364,6 +366,10 @@ const ConversationBubble = forwardRef<
|
|||||||
{toolCalls && toolCalls.length > 0 && (
|
{toolCalls && toolCalls.length > 0 && (
|
||||||
<ToolCalls toolCalls={toolCalls} />
|
<ToolCalls toolCalls={toolCalls} />
|
||||||
)}
|
)}
|
||||||
|
{thought && (
|
||||||
|
<Thought thought={thought} preprocessLaTeX={preprocessLaTeX} />
|
||||||
|
)}
|
||||||
|
{message && (
|
||||||
<div className="flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
<div className="flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
||||||
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -481,6 +487,8 @@ const ConversationBubble = forwardRef<
|
|||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{message && (
|
||||||
<div className="my-2 ml-2 flex justify-start">
|
<div className="my-2 ml-2 flex justify-start">
|
||||||
<div
|
<div
|
||||||
className={`relative mr-2 block items-center justify-center lg:invisible
|
className={`relative mr-2 block items-center justify-center lg:invisible
|
||||||
@@ -588,6 +596,7 @@ const ConversationBubble = forwardRef<
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{sources && (
|
{sources && (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
isOpen={isSidebarOpen}
|
isOpen={isSidebarOpen}
|
||||||
@@ -734,3 +743,136 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Thought({
|
||||||
|
thought,
|
||||||
|
preprocessLaTeX,
|
||||||
|
}: {
|
||||||
|
thought: string;
|
||||||
|
preprocessLaTeX: (content: string) => string;
|
||||||
|
}) {
|
||||||
|
const [isDarkTheme] = useDarkTheme();
|
||||||
|
const [isThoughtOpen, setIsThoughtOpen] = useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-4 w-full flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
||||||
|
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
||||||
|
<Avatar
|
||||||
|
className="h-[26px] w-[30px] text-xl"
|
||||||
|
avatar={
|
||||||
|
<img
|
||||||
|
src={Cloud}
|
||||||
|
alt={'Thought'}
|
||||||
|
className="h-full w-full object-fill"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="flex flex-row items-center gap-2"
|
||||||
|
onClick={() => setIsThoughtOpen(!isThoughtOpen)}
|
||||||
|
>
|
||||||
|
<p className="text-base font-semibold">Reasoning</p>
|
||||||
|
<img
|
||||||
|
src={ChevronDown}
|
||||||
|
alt="ChevronDown"
|
||||||
|
className={`h-4 w-4 transform transition-transform duration-200 dark:invert ${isThoughtOpen ? 'rotate-180' : ''}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{isThoughtOpen && (
|
||||||
|
<div className="fade-in ml-2 mr-5 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||||
|
<div className="rounded-[28px] bg-gray-1000 py-[18px] px-7 dark:bg-gun-metal">
|
||||||
|
<ReactMarkdown
|
||||||
|
className="fade-in whitespace-pre-wrap break-words leading-normal"
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeKatex]}
|
||||||
|
components={{
|
||||||
|
code(props) {
|
||||||
|
const { children, className, node, ref, ...rest } = props;
|
||||||
|
const match = /language-(\w+)/.exec(className || '');
|
||||||
|
const language = match ? match[1] : '';
|
||||||
|
|
||||||
|
return match ? (
|
||||||
|
<div className="group relative rounded-[14px] overflow-hidden border border-light-silver dark:border-raisin-black">
|
||||||
|
<div className="flex justify-between items-center px-2 py-1 bg-platinum dark:bg-eerie-black-2">
|
||||||
|
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
|
||||||
|
{language}
|
||||||
|
</span>
|
||||||
|
<CopyButton
|
||||||
|
text={String(children).replace(/\n$/, '')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SyntaxHighlighter
|
||||||
|
{...rest}
|
||||||
|
PreTag="div"
|
||||||
|
language={language}
|
||||||
|
style={isDarkTheme ? vscDarkPlus : oneLight}
|
||||||
|
className="!mt-0"
|
||||||
|
customStyle={{
|
||||||
|
margin: 0,
|
||||||
|
borderRadius: 0,
|
||||||
|
scrollbarWidth: 'thin',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{String(children).replace(/\n$/, '')}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<code className="whitespace-pre-line rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal dark:bg-independence dark:text-bright-gray">
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ul({ children }) {
|
||||||
|
return (
|
||||||
|
<ul className="list-inside list-disc whitespace-normal pl-4">
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ol({ children }) {
|
||||||
|
return (
|
||||||
|
<ol className="list-inside list-decimal whitespace-normal pl-4">
|
||||||
|
{children}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
table({ children }) {
|
||||||
|
return (
|
||||||
|
<div className="relative overflow-x-auto rounded-lg border border-silver/40 dark:border-silver/40">
|
||||||
|
<table className="w-full text-left text-gray-700 dark:text-bright-gray">
|
||||||
|
{children}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
thead({ children }) {
|
||||||
|
return (
|
||||||
|
<thead className="text-xs uppercase text-gray-900 dark:text-bright-gray bg-gray-50 dark:bg-[#26272E]/50">
|
||||||
|
{children}
|
||||||
|
</thead>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tr({ children }) {
|
||||||
|
return (
|
||||||
|
<tr className="border-b border-gray-200 dark:border-silver/40 odd:bg-white dark:odd:bg-[#26272E] even:bg-gray-50 dark:even:bg-[#26272E]/50">
|
||||||
|
{children}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
th({ children }) {
|
||||||
|
return <th className="px-6 py-3">{children}</th>;
|
||||||
|
},
|
||||||
|
td({ children }) {
|
||||||
|
return <td className="px-6 py-3">{children}</td>;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{preprocessLaTeX(thought ?? '')}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Fragment, useEffect, useRef, useState } from 'react';
|
import { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import ConversationBubble from './ConversationBubble';
|
|
||||||
import Hero from '../Hero';
|
|
||||||
import { FEEDBACK, Query, Status } from './conversationModels';
|
|
||||||
import ArrowDown from '../assets/arrow-down.svg';
|
import ArrowDown from '../assets/arrow-down.svg';
|
||||||
import RetryIcon from '../components/RetryIcon';
|
import RetryIcon from '../components/RetryIcon';
|
||||||
|
import Hero from '../Hero';
|
||||||
import { useDarkTheme } from '../hooks';
|
import { useDarkTheme } from '../hooks';
|
||||||
|
import ConversationBubble from './ConversationBubble';
|
||||||
|
import { FEEDBACK, Query, Status } from './conversationModels';
|
||||||
|
|
||||||
interface ConversationMessagesProps {
|
interface ConversationMessagesProps {
|
||||||
handleQuestion: (params: {
|
handleQuestion: (params: {
|
||||||
@@ -83,13 +84,14 @@ export default function ConversationMessages({
|
|||||||
|
|
||||||
const prepResponseView = (query: Query, index: number) => {
|
const prepResponseView = (query: Query, index: number) => {
|
||||||
let responseView;
|
let responseView;
|
||||||
if (query.response) {
|
if (query.thought || query.response) {
|
||||||
responseView = (
|
responseView = (
|
||||||
<ConversationBubble
|
<ConversationBubble
|
||||||
className={`${index === queries.length - 1 ? 'mb-32' : 'mb-7'}`}
|
className={`${index === queries.length - 1 ? 'mb-32' : 'mb-7'}`}
|
||||||
key={`${index}ANSWER`}
|
key={`${index}ANSWER`}
|
||||||
message={query.response}
|
message={query.response}
|
||||||
type={'ANSWER'}
|
type={'ANSWER'}
|
||||||
|
thought={query.thought}
|
||||||
sources={query.sources}
|
sources={query.sources}
|
||||||
toolCalls={query.tool_calls}
|
toolCalls={query.tool_calls}
|
||||||
feedback={query.feedback}
|
feedback={query.feedback}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function handleFetchAnswer(
|
|||||||
| {
|
| {
|
||||||
result: any;
|
result: any;
|
||||||
answer: any;
|
answer: any;
|
||||||
|
thought: any;
|
||||||
sources: any;
|
sources: any;
|
||||||
toolCalls: ToolCallsType[];
|
toolCalls: ToolCallsType[];
|
||||||
conversationId: any;
|
conversationId: any;
|
||||||
@@ -26,6 +27,7 @@ export function handleFetchAnswer(
|
|||||||
| {
|
| {
|
||||||
result: any;
|
result: any;
|
||||||
answer: any;
|
answer: any;
|
||||||
|
thought: any;
|
||||||
sources: any;
|
sources: any;
|
||||||
toolCalls: ToolCallsType[];
|
toolCalls: ToolCallsType[];
|
||||||
query: string;
|
query: string;
|
||||||
@@ -74,9 +76,11 @@ export function handleFetchAnswer(
|
|||||||
answer: result,
|
answer: result,
|
||||||
query: question,
|
query: question,
|
||||||
result,
|
result,
|
||||||
|
thought: data.thought,
|
||||||
sources: data.sources,
|
sources: data.sources,
|
||||||
toolCalls: data.tool_calls,
|
toolCalls: data.tool_calls,
|
||||||
conversationId: data.conversation_id,
|
conversationId: data.conversation_id,
|
||||||
|
title: data.title || null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -99,6 +103,7 @@ export function handleFetchAnswerSteaming(
|
|||||||
return {
|
return {
|
||||||
prompt: item.prompt,
|
prompt: item.prompt,
|
||||||
response: item.response,
|
response: item.response,
|
||||||
|
thought: item.thought,
|
||||||
tool_calls: item.tool_calls,
|
tool_calls: item.tool_calls,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface Answer {
|
|||||||
result: string;
|
result: string;
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
title: string | null;
|
title: string | null;
|
||||||
|
thought: string;
|
||||||
sources: { title: string; text: string; source: string }[];
|
sources: { title: string; text: string; source: string }[];
|
||||||
tool_calls: ToolCallsType[];
|
tool_calls: ToolCallsType[];
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,7 @@ export interface Query {
|
|||||||
feedback?: FEEDBACK;
|
feedback?: FEEDBACK;
|
||||||
conversationId?: string | null;
|
conversationId?: string | null;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
|
thought?: string;
|
||||||
sources?: { title: string; text: string; source: string }[];
|
sources?: { title: string; text: string; source: string }[];
|
||||||
tool_calls?: ToolCallsType[];
|
tool_calls?: ToolCallsType[];
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
query: { conversationId: data.id },
|
query: { conversationId: data.id },
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
} else if (data.type === 'thought') {
|
||||||
|
const result = data.thought;
|
||||||
|
console.log('thought', result);
|
||||||
|
dispatch(
|
||||||
|
updateThought({
|
||||||
|
index: indx ?? state.conversation.queries.length - 1,
|
||||||
|
query: { thought: result },
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (data.type === 'source') {
|
} else if (data.type === 'source') {
|
||||||
isSourceUpdated = true;
|
isSourceUpdated = true;
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -146,6 +155,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
index: indx ?? state.conversation.queries.length - 1,
|
index: indx ?? state.conversation.queries.length - 1,
|
||||||
query: {
|
query: {
|
||||||
response: answer.answer,
|
response: answer.answer,
|
||||||
|
thought: answer.thought,
|
||||||
sources: sourcesPrepped,
|
sources: sourcesPrepped,
|
||||||
tool_calls: answer.toolCalls,
|
tool_calls: answer.toolCalls,
|
||||||
},
|
},
|
||||||
@@ -173,6 +183,7 @@ export const fetchAnswer = createAsyncThunk<
|
|||||||
answer: '',
|
answer: '',
|
||||||
query: question,
|
query: question,
|
||||||
result: '',
|
result: '',
|
||||||
|
thought: '',
|
||||||
sources: [],
|
sources: [],
|
||||||
tool_calls: [],
|
tool_calls: [],
|
||||||
};
|
};
|
||||||
@@ -220,6 +231,21 @@ export const conversationSlice = createSlice({
|
|||||||
state.conversationId = action.payload.query.conversationId ?? null;
|
state.conversationId = action.payload.query.conversationId ?? null;
|
||||||
state.status = 'idle';
|
state.status = 'idle';
|
||||||
},
|
},
|
||||||
|
updateThought(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
if (query.thought != undefined) {
|
||||||
|
state.queries[index].thought =
|
||||||
|
(state.queries[index].thought || '') + query.thought;
|
||||||
|
} else {
|
||||||
|
state.queries[index] = {
|
||||||
|
...state.queries[index],
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
updateStreamingSource(
|
updateStreamingSource(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||||
@@ -292,6 +318,7 @@ export const {
|
|||||||
resendQuery,
|
resendQuery,
|
||||||
updateStreamingQuery,
|
updateStreamingQuery,
|
||||||
updateConversationId,
|
updateConversationId,
|
||||||
|
updateThought,
|
||||||
updateStreamingSource,
|
updateStreamingSource,
|
||||||
updateToolCalls,
|
updateToolCalls,
|
||||||
setConversation,
|
setConversation,
|
||||||
|
|||||||
@@ -44,6 +44,15 @@ export const fetchSharedAnswer = createAsyncThunk<Answer, { question: string }>(
|
|||||||
// set status to 'idle'
|
// set status to 'idle'
|
||||||
dispatch(sharedConversationSlice.actions.setStatus('idle'));
|
dispatch(sharedConversationSlice.actions.setStatus('idle'));
|
||||||
dispatch(saveToLocalStorage());
|
dispatch(saveToLocalStorage());
|
||||||
|
} else if (data.type === 'thought') {
|
||||||
|
const result = data.thought;
|
||||||
|
console.log('thought', result);
|
||||||
|
dispatch(
|
||||||
|
updateThought({
|
||||||
|
index: state.sharedConversation.queries.length - 1,
|
||||||
|
query: { thought: result },
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (data.type === 'source') {
|
} else if (data.type === 'source') {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateStreamingSource({
|
updateStreamingSource({
|
||||||
@@ -113,6 +122,7 @@ export const fetchSharedAnswer = createAsyncThunk<Answer, { question: string }>(
|
|||||||
answer: '',
|
answer: '',
|
||||||
query: question,
|
query: question,
|
||||||
result: '',
|
result: '',
|
||||||
|
thought: '',
|
||||||
sources: [],
|
sources: [],
|
||||||
tool_calls: [],
|
tool_calls: [],
|
||||||
};
|
};
|
||||||
@@ -183,6 +193,21 @@ export const sharedConversationSlice = createSlice({
|
|||||||
...query,
|
...query,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
updateThought(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||||
|
) {
|
||||||
|
const { index, query } = action.payload;
|
||||||
|
if (query.thought != undefined) {
|
||||||
|
state.queries[index].thought =
|
||||||
|
(state.queries[index].thought || '') + query.thought;
|
||||||
|
} else {
|
||||||
|
state.queries[index] = {
|
||||||
|
...state.queries[index],
|
||||||
|
...query,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
updateStreamingSource(
|
updateStreamingSource(
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||||
@@ -243,6 +268,7 @@ export const {
|
|||||||
setClientApiKey,
|
setClientApiKey,
|
||||||
updateQuery,
|
updateQuery,
|
||||||
updateStreamingQuery,
|
updateStreamingQuery,
|
||||||
|
updateThought,
|
||||||
updateToolCalls,
|
updateToolCalls,
|
||||||
addQuery,
|
addQuery,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
|
|||||||
Reference in New Issue
Block a user