随着大模型技术的日新月异,开源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集成步骤的详细说明:

  1. 修改基础助手类以进行工具集成:更新核心助手基础设施以正确处理工具调用。例如,定义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)}")
    

  2. 添加MCP配置支持:创建配置选项,用于指定外部服务器及其传输方式。例如,在config.toml文件中添加MCP_SERVERS配置项,用于指定MCP服务器的URL和传输方式。

    [DIR_ASSISTANT]
    # ... existing settings ...
    MCP_SERVERS = {
        "default": {"transport": "streamable-http", "url": "http://localhost:8000/mcp"}
    }
    
  3. 修改提示处理以识别工具调用:更新提示处理流程,以处理工具调用模式。例如,定义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.'
    })

  4. 为工具参数添加上下文管理:修改现有的上下文系统,以处理从工具传递的参数。例如,定义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
    

  5. 更新客户端实现以支持工具执行:修改客户端实现(在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
    

  6. 添加特定于工具的处理逻辑:为不同类型的工具实施专门的处理程序。例如,定义CodeToolHandlerDocumentationToolHandler类,这些类分别用于处理来自代码生成工具和文档工具的响应。

    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有望在相同时间内优于其他模型。

本次评测结果表明,本地代码LLMMCP集成等复杂代码生成任务中具有巨大的潜力。随着技术的不断发展,我们有理由相信,这些模型将在未来的软件开发中扮演越来越重要的角色。未来的研究方向包括:

  • 探索更有效的量化技术,以在保证模型性能的同时降低显存占用。
  • 开发更智能的提示工程策略,以引导LLM生成更高质量的代码。
  • 优化推理引擎,以提升LLM的响应速度。
  • 研究如何将本地代码LLM与现有的开发工具和工作流程更好地集成。

通过不断地探索和优化,我们有望充分释放本地代码LLM的潜力,从而推动软件开发领域的创新。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注