Skip to content

Commit da15cde

Browse files
committed
Merge branch 'main' into feat/agent-menu
2 parents e6ed371 + 5e1aaf5 commit da15cde

21 files changed

+1569
-232
lines changed

README.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@
4949
- [x] Manually updating chunks in the app UI (Feb 2025)
5050
- [x] Devcontainer for easy development (Feb 2025)
5151
- [x] ReACT agent (March 2025)
52-
- [ ] Anthropic Tool compatibility
53-
- [ ] New input box in the conversation menu
54-
- [ ] Add triggerable actions / tools (webhook)
52+
- [ ] Chatbots menu re-design to handle tools, agent types, and more (April 2025)
53+
- [ ] New input box in the conversation menu (April 2025)
54+
- [ ] Anthropic Tool compatibility (April 2025)
55+
- [ ] Add triggerable actions / tools (webhook) (April 2025)
5556
- [ ] Add OAuth 2.0 authentication for tools and sources
56-
- [ ] Chatbots menu re-design to handle tools, agent types, and more
5757
- [ ] Agent scheduling
5858

5959
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!
@@ -95,13 +95,15 @@ A more detailed [Quickstart](https://docs.docsgpt.cloud/quickstart) is available
9595
./setup.sh
9696
```
9797

98-
This interactive script will guide you through setting up DocsGPT. It offers four options: using the public API, running locally, connecting to a local inference engine, or using a cloud API provider. The script will automatically configure your `.env` file and handle necessary downloads and installations based on your chosen option.
99-
10098
**For Windows:**
10199

102-
2. **Follow the Docker Deployment Guide:**
100+
2. **Run the PowerShell setup script:**
101+
102+
```powershell
103+
PowerShell -ExecutionPolicy Bypass -File .\setup.ps1
104+
```
103105

104-
Please refer to the [Docker Deployment documentation](https://docs.docsgpt.cloud/Deploying/Docker-Deploying) for detailed step-by-step instructions on setting up DocsGPT using Docker.
106+
Either script will guide you through setting up DocsGPT. Four options available: using the public API, running locally, connecting to a local inference engine, or using a cloud API provider. Scripts will automatically configure your `.env` file and handle necessary downloads and installations based on your chosen option.
105107

106108
**Navigate to http://localhost:5173/**
107109

@@ -110,7 +112,7 @@ To stop DocsGPT, open a terminal in the `DocsGPT` directory and run:
110112
```bash
111113
docker compose -f deployment/docker-compose.yaml down
112114
```
113-
(or use the specific `docker compose down` command shown after running `setup.sh`).
115+
(or use the specific `docker compose down` command shown after running the setup script).
114116

115117
> [!Note]
116118
> For development environment setup instructions, please refer to the [Development Environment Guide](https://docs.docsgpt.cloud/Deploying/Development-Environment).

application/agents/classic_agent.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,13 @@ def _gen_inner(
4848
):
4949
yield {"answer": resp.message.content}
5050
else:
51-
completion = self.llm.gen_stream(
52-
model=self.gpt_model, messages=messages, tools=self.tools
53-
)
54-
for line in completion:
51+
# completion = self.llm.gen_stream(
52+
# model=self.gpt_model, messages=messages, tools=self.tools
53+
# )
54+
# log type of resp
55+
logger.info(f"Response type: {type(resp)}")
56+
logger.info(f"Response: {resp}")
57+
for line in resp:
5558
if isinstance(line, str):
5659
yield {"answer": line}
5760

application/agents/llm_handler.py

+58-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self):
1515
@abstractmethod
1616
def handle_response(self, agent, resp, tools_dict, messages, attachments=None, **kwargs):
1717
pass
18-
18+
1919
def prepare_messages_with_attachments(self, agent, messages, attachments=None):
2020
"""
2121
Prepare messages with attachment content if available.
@@ -33,15 +33,53 @@ def prepare_messages_with_attachments(self, agent, messages, attachments=None):
3333

3434
logger.info(f"Preparing messages with {len(attachments)} attachments")
3535

36-
# Check if the LLM has its own custom attachment handling implementation
37-
if hasattr(agent.llm, "prepare_messages_with_attachments") and agent.llm.__class__.__name__ != "BaseLLM":
38-
logger.info(f"Using {agent.llm.__class__.__name__}'s own prepare_messages_with_attachments method")
39-
return agent.llm.prepare_messages_with_attachments(messages, attachments)
36+
supported_types = agent.llm.get_supported_attachment_types()
37+
38+
supported_attachments = []
39+
unsupported_attachments = []
40+
41+
for attachment in attachments:
42+
mime_type = attachment.get('mime_type')
43+
if not mime_type:
44+
import mimetypes
45+
file_path = attachment.get('path')
46+
if file_path:
47+
mime_type = mimetypes.guess_type(file_path)[0] or 'application/octet-stream'
48+
else:
49+
unsupported_attachments.append(attachment)
50+
continue
51+
52+
if mime_type in supported_types:
53+
supported_attachments.append(attachment)
54+
else:
55+
unsupported_attachments.append(attachment)
56+
57+
# Process supported attachments with the LLM's custom method
58+
prepared_messages = messages
59+
if supported_attachments:
60+
logger.info(f"Processing {len(supported_attachments)} supported attachments with {agent.llm.__class__.__name__}'s method")
61+
prepared_messages = agent.llm.prepare_messages_with_attachments(messages, supported_attachments)
62+
63+
# Process unsupported attachments with the default method
64+
if unsupported_attachments:
65+
logger.info(f"Processing {len(unsupported_attachments)} unsupported attachments with default method")
66+
prepared_messages = self._append_attachment_content_to_system(prepared_messages, unsupported_attachments)
67+
68+
return prepared_messages
69+
70+
def _append_attachment_content_to_system(self, messages, attachments):
71+
"""
72+
Default method to append attachment content to the system prompt.
4073
41-
# Otherwise, append attachment content to the system prompt
74+
Args:
75+
messages (list): List of message dictionaries.
76+
attachments (list): List of attachment dictionaries with content.
77+
78+
Returns:
79+
list: Messages with attachment context added to the system prompt.
80+
"""
4281
prepared_messages = messages.copy()
4382

44-
# Build attachment content string
4583
attachment_texts = []
4684
for attachment in attachments:
4785
logger.info(f"Adding attachment {attachment.get('id')} to context")
@@ -122,12 +160,13 @@ def handle_response(self, agent, resp, tools_dict, messages, attachments=None, s
122160
return resp
123161

124162
else:
125-
163+
text_buffer = ""
126164
while True:
127165
tool_calls = {}
128166
for chunk in resp:
129167
if isinstance(chunk, str) and len(chunk) > 0:
130-
return
168+
yield chunk
169+
continue
131170
elif hasattr(chunk, "delta"):
132171
chunk_delta = chunk.delta
133172

@@ -206,12 +245,17 @@ def handle_response(self, agent, resp, tools_dict, messages, attachments=None, s
206245
}
207246
)
208247
tool_calls = {}
248+
if hasattr(chunk_delta, "content") and chunk_delta.content:
249+
# Add to buffer or yield immediately based on your preference
250+
text_buffer += chunk_delta.content
251+
yield text_buffer
252+
text_buffer = ""
209253

210254
if (
211255
hasattr(chunk, "finish_reason")
212256
and chunk.finish_reason == "stop"
213257
):
214-
return
258+
return resp
215259
elif isinstance(chunk, str) and len(chunk) == 0:
216260
continue
217261

@@ -227,7 +271,7 @@ def handle_response(self, agent, resp, tools_dict, messages, attachments=None, s
227271
from google.genai import types
228272

229273
messages = self.prepare_messages_with_attachments(agent, messages, attachments)
230-
274+
231275
while True:
232276
if not stream:
233277
response = agent.llm.gen(
@@ -298,6 +342,9 @@ def handle_response(self, agent, resp, tools_dict, messages, attachments=None, s
298342
"content": [function_response_part.to_json_dict()],
299343
}
300344
)
345+
else:
346+
tool_call_found = False
347+
yield result
301348

302349
if not tool_call_found:
303350
return response

application/api/answer/routes.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ def post(self):
657657
source_log_docs = []
658658
tool_calls = []
659659
stream_ended = False
660+
thought = ""
660661

661662
for line in complete_stream(
662663
question=question,
@@ -679,6 +680,8 @@ def post(self):
679680
source_log_docs = event["source"]
680681
elif event["type"] == "tool_calls":
681682
tool_calls = event["tool_calls"]
683+
elif event["type"] == "thought":
684+
thought = event["thought"]
682685
elif event["type"] == "error":
683686
logger.error(f"Error from stream: {event['error']}")
684687
return bad_request(500, event["error"])
@@ -710,6 +713,7 @@ def post(self):
710713
conversation_id,
711714
question,
712715
response_full,
716+
thought,
713717
source_log_docs,
714718
tool_calls,
715719
llm,
@@ -876,14 +880,7 @@ def get_attachments_content(attachment_ids, user):
876880
)
877881

878882
if attachment_doc:
879-
attachments.append(
880-
{
881-
"id": str(attachment_doc["_id"]),
882-
"content": attachment_doc["content"],
883-
"token_count": attachment_doc.get("token_count", 0),
884-
"path": attachment_doc.get("path", ""),
885-
}
886-
)
883+
attachments.append(attachment_doc)
887884
except Exception as e:
888885
logger.error(f"Error retrieving attachment {attachment_id}: {e}")
889886

application/api/user/routes.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -2792,25 +2792,25 @@ def post(self):
27922792
user = secure_filename(decoded_token.get("sub"))
27932793

27942794
try:
2795+
attachment_id = ObjectId()
27952796
original_filename = secure_filename(file.filename)
2796-
folder_name = original_filename
2797+
27972798
save_dir = os.path.join(
2798-
current_dir, settings.UPLOAD_FOLDER, user, "attachments", folder_name
2799+
current_dir,
2800+
settings.UPLOAD_FOLDER,
2801+
user,
2802+
"attachments",
2803+
str(attachment_id),
27992804
)
28002805
os.makedirs(save_dir, exist_ok=True)
2801-
# Create directory structure: user/attachments/filename/
2802-
file_path = os.path.join(save_dir, original_filename)
28032806

2804-
# Handle filename conflicts
2805-
if os.path.exists(file_path):
2806-
name_parts = os.path.splitext(original_filename)
2807-
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
2808-
new_filename = f"{name_parts[0]}_{timestamp}{name_parts[1]}"
2809-
file_path = os.path.join(save_dir, new_filename)
2810-
original_filename = new_filename
2807+
file_path = os.path.join(save_dir, original_filename)
28112808

28122809
file.save(file_path)
2813-
file_info = {"folder": folder_name, "filename": original_filename}
2810+
file_info = {
2811+
"filename": original_filename,
2812+
"attachment_id": str(attachment_id),
2813+
}
28142814
current_app.logger.info(f"Saved file: {file_path}")
28152815

28162816
# Start async task to process single file

application/llm/base.py

+9
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,12 @@ def supports_tools(self):
5555

5656
def _supports_tools(self):
5757
raise NotImplementedError("Subclass must implement _supports_tools method")
58+
59+
def get_supported_attachment_types(self):
60+
"""
61+
Return a list of MIME types supported by this LLM for file uploads.
62+
63+
Returns:
64+
list: List of supported MIME types
65+
"""
66+
return [] # Default: no attachments supported

0 commit comments

Comments
 (0)