随着大模型技术的日新月异,开源LLM模型迎来了激动人心的发展。特别是对于长期从事复杂代码生成任务的开发者而言,这些模型为替代API驱动的LLM提供了一种极具吸引力的选择。本文将深入探讨四款备受关注的本地代码LLM:Devstral、Qwen3、Gemma3以及Deepseek R1,通过实际的MCP集成案例,对比评测它们在代码生成、项目规划和问题解决方面的能力,为开发者选择合适的本地代码LLM提供参考。
本地代码LLM评测环境搭建与模型选择
为了保证评测的公平性和可复现性,我们搭建了统一的测试环境。硬件方面,我们采用了配备24GB Radeon 7900 XTX显卡的机器,确保所有模型都能在本地运行。软件方面,我们使用了LMStudio 0.3.16作为API托管平台,ROCm llama.cpp 1.33.0作为底层推理引擎,以及Dir-Assistant 1.6.0作为用户界面。Dir-Assistant的独特之处在于其两步上下文引导RAG(CGRAG)技术,能够更好地理解代码库的上下文信息,这对于长文本代码生成至关重要。
在模型选择方面,我们考虑了模型的参数规模和量化程度。为了让模型能够在有限的显存下运行,我们对模型进行了不同程度的量化,选择了能够完全容纳在24GB显存中,并支持128k token上下文的模型。具体模型如下:
- Devstral Small 2505 Unsloth IQ4_XS
- Qwen3 32B Unsloth IQ3_XXS
- Gemma3 27B Unsloth IQ3_XXS
- Deepseek R1 0528 Qwen3 8B Unsloth Q8
值得注意的是,除了Gemma3之外,所有模型都启用了“思考”模式,允许模型在生成代码之前进行初步的规划和推理。
测试用例设计:MCP集成到Dir-Assistant
为了充分评估这些本地代码LLM的能力,我们设计了一个复杂的测试用例:要求模型规划如何将MCP(Model Context Protocol)客户端功能集成到Dir-Assistant的自身代码库中。MCP允许LLM在回答提示之前选择使用工具,从而增强了其功能。具体提示如下:
“创建一个计划,将MCP(模型上下文协议)客户端功能添加到此项目中,以便LLM可以在回答提示之前选择通过MCP使用工具。使用fastmcp库,并将您的计划写入mcp-plan.md。”
这个提示具有一定的模糊性,旨在让LLM有更大的发挥空间,从而更好地展现其个性。为了帮助模型理解fastmcp库的使用方式,我们在代码库中创建了一个docs目录,并添加了fastmcp的多个文档页面。
评测标准:功能、质量与速度
我们从三个维度对每个模型的响应进行了评估:
- 功能实现: 模型是否能够生成可行的解决方案,并满足基本需求。
- 代码质量: 模型生成的代码是否遵循现有的代码架构和风格,是否具有良好的可读性和可维护性,以及是否考虑了性能因素。我们采用A到F的评分体系,A代表“我会这样写”,F代表“这甚至不是我想要的”。
- 响应速度: 模型生成解决方案所需的时间。
评测结果:各有千秋,Deepseek R1 0528 表现出色
Gemini 2.5 Pro (Benchmark)
- 功能实现: 是
- 代码质量: B+
- 备注: 作为基准模型,Gemini 2.5 Pro的表现非常出色,几乎完美地完成了任务。唯一的不足之处在于解决方案的结构不够模块化,没有采用基于类的实现方式。
Qwen3 32B
- 功能实现: 是
- 代码质量: C+
- 备注: 生成冗长的回复,其中只有一小部分有用,其余部分无关紧要甚至错误。小量化可能是导致问题的原因。遵循了文件写入指令并正确使用了fastmcp,采用了正确的项目架构。响应速度最慢,总耗时约一小时,大部分时间花费在提示处理上。
Deepseek R1 0528 Qwen3 8B
- 功能实现: 是
- 代码质量: C
- 备注: 令人惊讶地彻底且结构良好的解决方案,但存在不完整的部分,过于复杂,并且存在一些奇怪的冗余/幻觉。正确使用了fastmcp,但没有遵循Dir-Assistant关于如何写入文件的说明。思考时间较长。
Deepseek R1 0528 Qwen3 8B 在本次评测中表现相对出色,这得益于其对复杂任务的分解能力和清晰的逻辑。例如,它将MCP集成过程分解为多个步骤,包括配置支持、提示处理、上下文管理和工具处理等。每个步骤都提供了详细的说明和代码示例。此外,它还考虑了错误处理、性能优化和测试等方面,体现了较强的工程能力。以下是Deepseek R1 0528 Qwen3 8B生成的MCP集成步骤的详细说明:
-
修改基础助手类以进行工具集成:更新核心助手基础设施以正确处理工具调用。例如,定义
MCPAssistant
类,并添加call_tool_mcp
方法,该方法允许执行来自外部MCP服务器的工具。from fastmcp import Client, FastMCP class MCPAssistant(BaseAssistant): def __init__(self, mcp_servers_config=None, ...): # Add support for external tool execution via MCP servers self.mcp_servers = {} if mcp_servers_config: client = Client(mcp_servers_config) # Auto-connect to all configured servers when initializing the assistant
async def call_tool_mcp(self, tool_name, arguments=None): """Execute a tool from an external MCP server.""" try: result = await self.mcp_client.call_tool(tool_name, arguments) return { 'tool': tool_name, 'arguments': arguments, 'result': result } except Exception as e: # Handle errors appropriately raise ToolError(f"Failed to execute {tool_name}: {str(e)}")
-
添加MCP配置支持:创建配置选项,用于指定外部服务器及其传输方式。例如,在
config.toml
文件中添加MCP_SERVERS
配置项,用于指定MCP服务器的URL和传输方式。[DIR_ASSISTANT] # ... existing settings ... MCP_SERVERS = { "default": {"transport": "streamable-http", "url": "http://localhost:8000/mcp"} }
-
修改提示处理以识别工具调用:更新提示处理流程,以处理工具调用模式。例如,定义
process_mcp_tool_call
方法,该方法使用正则表达式匹配LLM输出中的工具调用模式,并执行相应的工具。def process_mcp_tool_call(self, content): """Identify and execute MCP tools within assistant responses.""" # Regular expression matching pattern for tool calls in LLM output import re from fastmcp.client.transports import TransportFactory
mcp_tool_pattern = r'```\s*tool\s+([a-zA-Z_0-9]+)(?:\s+(\S+))?\s*(.*?)```' # Find all potential tool calls in the output content matches = re.findall(mcp_tool_pattern, self.content) for match in matches: try: tool_name = match[1] if len(match) > 1 and match[1] else "default" result_data = await TransportFactory.get_transport().execute_tool(tool_name, ...) # Replace the detected tool call with its output return self.replace_tool_call(match_index, result_data) except Exception as e: # Handle failures while maintaining conversation flow return self.handle_tool_error(match_index, str(e)) return None # No tools found in this response
def replace_tool_call(self, match_index, result):
"""Replace tool call matches with their actual outputs."""
if not isinstance(result, list) or (len([match for _,_,result in result])):
raise ValueError("Invalid tool output format")self.chat_history.append({ 'role': 'assistant', 'content': f'```tool_output\n{json.dumps(result)}\n```' })
def handle_tool_error(self, match_index, error_message):
"""Handle failed tool executions and provide feedback."""
# Add appropriate error context to the chat history
self.chat_history.append({
'role': 'assistant',
'content': f'Error executing tool: {error_message}. Please try another approach.'
})
-
为工具参数添加上下文管理:修改现有的上下文系统,以处理从工具传递的参数。例如,定义
ToolContextManager
类,该类用于注册和检索工具参数。class ToolContextManager: def __init__(self): self.tool_parameters = {}
async def set_tool_context(self, tool_name, parameter_value): """Register a parameter used by an external tool.""" self.tool_parameters[tool_name] = parameter_value async def get_tool_context(self): return your_tool_context
-
更新客户端实现以支持工具执行:修改客户端实现(在
fastmcp_clients.txt
中找到)以正确处理工具执行。例如,定义MCPClient
类,并添加call_tool
方法,该方法用于在MCP服务器上执行工具。from fastmcp import Client class MCPClient: @classmethod async def from_url(cls, url): """Create a new MCP client for external servers.""" if is_streamable_http(url): transport = StreamableHttpTransport(url) elif ends_with('.py'): # Check if file exists and infer as Python script pass # Simplified
return await Client(transport).call_tool(...) async def call_tool(self, tool_name=None, arguments=None): """Execute a tool on the MCP server.""" full_tool_call = self._get_full_tool_signature(tool_name) if not is_valid_mcp_format(full_tool_call): raise ValueError("Invalid tool execution format") # Separate internal and external tools for line in full_tool_call.splitlines(): if matches_external(line): return await ExternalToolProcessor.process_line(line, arguments) def _get_full_tool_signature(self, tool_name=None): """Generate a complete tool call template.""" base_template = """%(thinking)sUser: I need help with %(user_input)sAvailable tools:- %(tool_list)sWhich tool should be used?""" # Build appropriate prompt and execute through FastMCP client
-
添加特定于工具的处理逻辑:为不同类型的工具实施专门的处理程序。例如,定义
CodeToolHandler
和DocumentationToolHandler
类,这些类分别用于处理来自代码生成工具和文档工具的响应。class CodeToolHandler(ProtocolHandler): def process_tool_response(self, response): """Format responses from code generation tools.""" if 'function' in response: # Handle structured function call output return json.dumps(response) elif matches_code_template(response): # Process standard tool execution pass else: raise ValueError("Unexpected format") class DocumentationToolHandler(ProtocolHandler): def process_tool_response(self, response): """Format responses from documentation tools.""" if has_valid_doc_structure(response): return structured_documentation
Devstral Small 2505
- 功能实现: 是
- 代码质量: D+
- 备注: 一个简短的、总体上可行的计划,正确使用了fastmcp,但代码示例留下了很多空白。没有遵循Dir-Assistant关于如何写入文件的说明,也没有很好地遵循项目架构。响应速度最快,总耗时约10分钟。
Gemma3 27B
- 功能实现: 是
- 代码质量: D
- 备注: 最短的回复,因此我认为是最没用的,并且响应时间第二长。也就是说,该计划是有效的,尽管缺乏许多细节或如何最好地执行它的线索。至少它确实遵循了Dir-Assistant的文件写入说明。小的量化肯定在糟糕的性能中发挥了作用。
结论与展望:本地代码LLM潜力巨大
总体而言,参与测试的本地代码LLM都展现出了不俗的实力,它们与最先进的模型之间的差距正在缩小。只要加以更具体的提示和指导,这些模型都有望生成有用的结果,并最终解决实际问题。
然而,这些模型在大型上下文下的运行速度仍然是一个瓶颈。为了使其能够在实际工作流程中发挥作用,需要显著提升性能。
考虑到运行时的因素,Devstral在本次测试中脱颖而出。尽管其响应质量并非最佳,但其速度优势使其成为最实用的选择。通过将提示分解为更小、更具体的部分,Devstral有望在相同时间内优于其他模型。
本次评测结果表明,本地代码LLM在MCP集成等复杂代码生成任务中具有巨大的潜力。随着技术的不断发展,我们有理由相信,这些模型将在未来的软件开发中扮演越来越重要的角色。未来的研究方向包括:
- 探索更有效的量化技术,以在保证模型性能的同时降低显存占用。
- 开发更智能的提示工程策略,以引导LLM生成更高质量的代码。
- 优化推理引擎,以提升LLM的响应速度。
- 研究如何将本地代码LLM与现有的开发工具和工作流程更好地集成。
通过不断地探索和优化,我们有望充分释放本地代码LLM的潜力,从而推动软件开发领域的创新。