-
Notifications
You must be signed in to change notification settings - Fork 663
【Hackathon 9th Sprint No.60】【RFC】为 FastDeploy 新增支持 DeepSeek 模型的 Reason ing Parser & Tool Parser #5412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
【Hackathon 9th Sprint No.60】【RFC】为 FastDeploy 新增支持 DeepSeek 模型的 Reason ing Parser & Tool Parser #5412
Conversation
…ing Parser & Tool Parser
|
Thanks for your contribution! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds comprehensive support for DeepSeek models (V3.1, V3-0324, and R1) to FastDeploy by implementing both Reasoning Parser and Tool Parser functionality. The implementation follows existing patterns in the codebase and includes thorough test coverage.
Key Changes:
- Implements
DeepSeekReasoningParserto extract reasoning content from DeepSeek model outputs with support for both streaming and non-streaming modes - Implements
DeepSeekToolParserto parse tool calls from DeepSeek models, supporting two different formats (V3.1 and V3-0324/R1) - Adds comprehensive test suites for both parsers covering edge cases and protocol violations
- Updates documentation in both English and Chinese with examples and error handling guidance
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
fastdeploy/reasoning/deepseek_reasoning_parser.py |
New reasoning parser implementation for DeepSeek models with <think> tag extraction |
fastdeploy/entrypoints/openai/tool_parsers/deepseek_tool_parser.py |
New tool parser supporting V3.1 and V3-0324/R1 format variations with streaming support |
tests/reasoning/test_reasoning_parser.py |
Adds comprehensive test cases for DeepSeek reasoning parser with 18 test methods |
tests/entrypoints/openai/tool_parsers/test_deepseek_tool_parser.py |
New test file with 15 test cases covering both model formats and edge cases |
docs/zh/features/tool_calling.md |
Chinese documentation with DeepSeek model table and error handling examples |
docs/zh/features/reasoning_output.md |
Chinese documentation for reasoning output with model compatibility table |
docs/features/tool_calling.md |
English documentation with DeepSeek model table and error handling examples |
docs/features/reasoning_output.md |
English documentation for reasoning output with model compatibility table |
Comments suppressed due to low confidence (3)
fastdeploy/entrypoints/openai/tool_parsers/deepseek_tool_parser.py:164
- Except block directly handles BaseException.
except:
fastdeploy/entrypoints/openai/tool_parsers/deepseek_tool_parser.py:294
- Except block directly handles BaseException.
except:
fastdeploy/entrypoints/openai/tool_parsers/deepseek_tool_parser.py:322
- Except block directly handles BaseException.
except:
| if tool_calls_begin_pos > 0: | ||
| # 检查中间是否有非空白字符 | ||
| between_text = after_reasoning[:tool_calls_begin_pos] | ||
| if between_text.strip() and not between_text.strip().isspace(): |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic between_text.strip() and not between_text.strip().isspace() is redundant. After calling .strip(), the result is already stripped of whitespace, so .strip().isspace() will always return False for any non-empty string. Simply use:
if between_text.strip():
# 有非空白字符,协议不规范,不解析工具调用
return ExtractedToolCallInformation(tools_called=False, tool_calls=None, content=model_output)This achieves the same result more efficiently.
| if between_text.strip() and not between_text.strip().isspace(): | |
| if between_text.strip(): |
|
|
||
| def random_tool_call_id() -> str: | ||
| """Generate a random tool call ID""" | ||
| return f"chatcmpl-tool-{str(uuid.uuid4().hex)}" |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify the redundant str() conversion. uuid.uuid4().hex already returns a string, so wrapping it in str() is unnecessary:
return f"chatcmpl-tool-{uuid.uuid4().hex}"| return f"chatcmpl-tool-{str(uuid.uuid4().hex)}" | |
| return f"chatcmpl-tool-{uuid.uuid4().hex}" |
| self.current_tool_id = ( | ||
| max(self.current_tool_id, 0) if self.current_tool_id == -1 else self.current_tool_id + 1 | ||
| ) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The logic for initializing current_tool_id when it's -1 is unnecessarily complex. Consider simplifying:
if self.current_tool_id == -1:
self.current_tool_id = 0
else:
self.current_tool_id += 1This achieves the same result as max(self.current_tool_id, 0) if self.current_tool_id == -1 else self.current_tool_id + 1 but is clearer and more readable.
| self.current_tool_id = ( | |
| max(self.current_tool_id, 0) if self.current_tool_id == -1 else self.current_tool_id + 1 | |
| ) | |
| if self.current_tool_id == -1: | |
| self.current_tool_id = 0 | |
| else: | |
| self.current_tool_id += 1 |
|
|
||
| if not self.model_tokenizer: | ||
| raise ValueError( | ||
| "The model tokenizer must be passed to the ReasoningParser " "constructor during construction." |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The error message string is split across two lines using string concatenation. Consider using a single string or parentheses for better readability:
raise ValueError(
"The model tokenizer must be passed to the ReasoningParser constructor during construction."
)| "The model tokenizer must be passed to the ReasoningParser " "constructor during construction." | |
| "The model tokenizer must be passed to the ReasoningParser constructor during construction." |
|
|
||
| if self.think_end_token_id is None: | ||
| raise RuntimeError( | ||
| "DeepSeek reasoning parser could not locate think end " "tokens in the tokenizer!" |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The error message string is split across two lines using string concatenation. Consider using a single string or parentheses for better readability:
raise RuntimeError(
"DeepSeek reasoning parser could not locate think end tokens in the tokenizer!"
)| "DeepSeek reasoning parser could not locate think end " "tokens in the tokenizer!" | |
| "DeepSeek reasoning parser could not locate think end tokens in the tokenizer!" |
| """ | ||
| DeepSeek 系列模型的工具调用解析器(支持 V3.1、V3-0324、R1 三种模型) | ||
|
|
||
| 支持的格式: | ||
| - V3.1: <|tool▁call▁begin|>function_name<|tool▁sep|>{"arg": "value"}<|tool▁call▁end|> | ||
| - V3-0324/R1: <|tool▁call▁begin|>function<|tool▁sep|>function_name\n```json\n{"arg": "value"}\n```<|tool▁call▁end|> | ||
| """ |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider adding an English translation to the docstring for consistency with the rest of the codebase. Many projects follow bilingual documentation standards. For example:
"""
DeepSeek series model tool call parser (supports V3.1, V3-0324, R1 models)
DeepSeek 系列模型的工具调用解析器(支持 V3.1、V3-0324、R1 三种模型)
Supported formats:
支持的格式:
- V3.1: <|tool▁call▁begin|>function_name<|tool▁sep|>{"arg": "value"}<|tool▁call▁end|>
- V3-0324/R1: <|tool▁call▁begin|>function<|tool▁sep|>function_name\n```json\n{"arg": "value"}\n```<|tool▁call▁end|>
"""| # 尝试使用 partial_json_parser | ||
| try: | ||
| args_dict = partial_json_parser.loads(function_arguments, flags=Allow.ALL) | ||
| except: |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using bare except: clauses. Catch specific exception types to ensure proper error handling and avoid masking unexpected errors. Consider catching Exception or specific parser exceptions:
except Exception:
args_dict = {}This applies to all three bare except: clauses in the file (lines 164, 294, 322).
| except: | |
| except Exception: |
| try: | ||
| args_dict = partial_json_parser.loads(args_text, flags=Allow.ALL) | ||
| args_str = json.dumps(args_dict, ensure_ascii=False) | ||
| except: |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using bare except: clauses. Catch specific exception types to ensure proper error handling and avoid masking unexpected errors. Consider catching Exception or specific parser exceptions:
except Exception:
args_str = args_text| except: | |
| except Exception: |
| # 使用 partial_json_parser 解析部分 JSON | ||
| args_dict = partial_json_parser.loads(args_text, flags=Allow.ALL) | ||
| args_str = json.dumps(args_dict, ensure_ascii=False) | ||
| except: |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using bare except: clauses. Catch specific exception types to ensure proper error handling and avoid masking unexpected errors. Consider catching Exception or specific parser exceptions:
except Exception:
# 如果解析失败,直接使用原始文本
args_str = args_text| except: | |
| except Exception: |
| import json | ||
| import unittest | ||
|
|
||
| from fastdeploy.entrypoints.openai.protocol import ChatCompletionRequest, DeltaMessage |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'DeltaMessage' is not used.
| from fastdeploy.entrypoints.openai.protocol import ChatCompletionRequest, DeltaMessage | |
| from fastdeploy.entrypoints.openai.protocol import ChatCompletionRequest |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #5412 +/- ##
==========================================
Coverage ? 59.21%
==========================================
Files ? 329
Lines ? 40900
Branches ? 6224
==========================================
Hits ? 24219
Misses ? 14801
Partials ? 1880
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
No description provided.