Devin,一个价值500美元的AI程序员,虽然在国内互联网圈的热度只持续了几天,但使用过的老板都给予了高度评价。Devin像一个有条理的实习生,知道先制定计划,然后在执行过程中持续更新计划的进度,这使得我们可以更容易跟踪AI的当前进度,同时防止它偏离原始计划,实现更深入的思考和任务完成质量。 Devin的这个功能看起来很高大上,但是用cursor实现,其实真的蛮简单!对于Cursor,有一个特殊的文件叫做.cursorrules,位于打开文件夹的根目录中。它的特别之处在于允许我们修改Cursor的prompt到后端LLM。换句话说,这个文件中的所有内容都成为发送到后端AI(如GPT或Claude)的提示词的一部分。 比如说,我们可以将计划的内容放入这个文件中,这样每次与Cursor交互时,它都能接收到计划的最新版本。我们还可以在这个文件中给出更详细的指示,比如在任务开始时让它思考并制定计划,并在完成每个步骤后更新计划。 由于Cursor可以使用Agent来修改文件,而.cursorrules本身就是一个文件,这就形成了一个闭环。它每次都会自动读取文件内容,理解最新的更新,并在思考后,将更新的进度和下一步写入这个文件,确保我们始终拥有最新的更新。 自我进化(self-evolution)的能力,同样可以用类似的方法实现。在.cursorrules文件中,我们添加提示,让cursor在用户纠正错误时反思其错误,并考虑是否有可记录的可重复使用的经验教训。如果有,它将更新.cursorrules文件的相关部分。这样,它可以积累特定项目的知识。 如果使用windsurf,有一点点不一样,可能出于一些安全原因,它不允许AI直接修改.windsurfrules文件。因此,我们需要将其分成两部分,使用另一个文件如scratchpad.md。在.windsurfrules文件中,我们提到:在每次思考过程之前,你应该检查Scratchpad并更新那里的计划。这种间接方法可能不如直接在.cursorrules中修改它有效,因为它仍然需要AI调用Agent并根据反馈结果进行思考,但实际测试是可行的。 最后是扩展工具使用。与Cursor相比,Devin的一个主要优势是能够使用更多工具。例如,它可以调用浏览器进行搜索、浏览网页,甚至使用自己的大脑通过LLM智能分析内容。虽然Cursor默认不支持这些功能,但是因为我们可以直接通过.cursorrules控制Cursor的提示,并且它具有命令执行能力,这又形成了一个闭环。我们可以准备预写的程序,如Python库或命令行工具,然后在.cursorrules中引入它们的用法,让它边学边用,自然地理解如何使用这些工具来完成其任务。

<span data-cacheurl="" data-remoteid="" style="display: block; background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg&amp;tp=webp&amp;wxfrom=15&amp;wx_lazy=1&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52); height: 30px; width: 100%; margin-bottom: -7px; border-radius: 5px;" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg" class="" data-fail="0"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""># Instructions  
  
<span leaf="">During you interaction with the user, <span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> you find anything reusable <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> this project (e.g. version of a library, model name), especially about a fix to a mistake you made or a correction you received, you should take note <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> the `Lessons` section <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> the `.cursorrules` file so you will not make the same mistake again.   
  
<span leaf="">You should also use the `.cursorrules` file as a scratchpad to organize your thoughts. Especially when you receive a new task, you should first review the content of the scratchpad, clear old different task <span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> necessary, first explain the task, and plan the steps you need to take to complete the task. You can use todo markers to indicate the progress, e.g.  
<span leaf="">[X] Task 1  
<span leaf="">[ ] Task 2  
<span leaf="">Also update the progress of the task <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> the Scratchpad when you finish a subtask.  
<span leaf="">Especially when you finished a milestone, it will <span style="color: #e6c07b;line-height: 26px;"><span leaf="">help<span leaf=""> to improve your depth of task accomplishment to use the scratchpad to reflect and plan.  
<span leaf="">The goal is to <span style="color: #e6c07b;line-height: 26px;"><span leaf="">help<span leaf=""> you maintain a big picture as well as the progress of the task. Always refer to the Scratchpad when you plan the next step.  
  
<span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""># Tools  
  
<span leaf="">Note all the tools are <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> python. So <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> the <span style="color: #c678dd;line-height: 26px;"><span leaf="">case<span leaf=""> you need to <span style="color: #c678dd;line-height: 26px;"><span leaf="">do<span leaf=""> batch processing, you can always consult the python files and write your own script.  
  
<span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf="">## LLM  
  
<span leaf="">You always have an LLM at your side to <span style="color: #e6c07b;line-height: 26px;"><span leaf="">help<span leaf=""> you with the task. For simple tasks, you could invoke the LLM by running the following <span style="color: #e6c07b;line-height: 26px;"><span leaf="">command<span leaf="">:  
  
<span leaf="">py310/bin/python ./tools/llm_api.py --prompt <span style="color: #98c379;line-height: 26px;"><span leaf="">"What is the capital of France?"  
  
  
<span leaf="">But usually it<span style="color: #98c379;line-height: 26px;"><span leaf="">'s a better idea to check the content of the file and use the APIs in the `tools/llm_api.py` file to invoke the LLM if needed.  
  
<span leaf="">## Web browser  
  
<span leaf="">You could use the `tools/web_scraper.py` file to scrape the web.  
  
<span leaf="">py310/bin/python ./tools/web_scraper.py --max-concurrent 3 URL1 URL2 URL3  
  
  
<span leaf="">This will output the content of the web pages.  
  
<span leaf="">## Search engine  
  
<span leaf="">You could use the `tools/search_engine.py` file to search the web.  
  
<span leaf="">py310/bin/python ./tools/search_engine.py "your search keywords"  
  
<span leaf="">This will output the search results in the following format:  
  
  
<span leaf="">URL: https://example.com  
<span leaf="">Title: This is the title of the search result  
<span leaf="">Snippet: This is a snippet of the search result  
  
<span leaf="">If needed, you can further use the `web_scraper.py` file to scrape the web page content.  
  
<span leaf=""># Lessons  
  
<span leaf="">## User Specified Lessons  
  
<span leaf="">- You have a python venv in ./py310.  
<span leaf="">- Include info useful for debugging in the program output.  
<span leaf="">- Read the file before you try to edit it.  
<span leaf="">- Use LLM to perform flexible text understanding tasks. First test on a few files. After success, make it parallel.  
  
<span leaf="">## Cursor learned  
  
<span leaf="">- For website image paths, always use the correct relative path (e.g., '<span leaf="">images/filename.png<span style="color: #98c379;line-height: 26px;"><span leaf="">') and ensure the images directory exists  
<span leaf="">- For search results, ensure proper handling of different character encodings (UTF-8) for international queries  
<span leaf="">- Add debug information to stderr while keeping the main output clean in stdout for better pipeline integration  
<span leaf="">- When using seaborn styles in matplotlib, use '<span leaf="">seaborn-v0_8<span style="color: #98c379;line-height: 26px;"><span leaf="">' instead of '<span leaf="">seaborn<span style="color: #98c379;line-height: 26px;"><span leaf="">' as the style name due to recent seaborn version changes  
  
<span leaf=""># Scratchpad  

3个工具

  1. llm_api使用vllm部署的qwen
<span data-cacheurl="" data-remoteid="" style="display: block; background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg&amp;tp=webp&amp;wxfrom=15&amp;wx_lazy=1&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52); height: 30px; width: 100%; margin-bottom: -7px; border-radius: 5px;" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg" class="" data-fail="0"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">from<span leaf=""> openai <span style="color: #c678dd;line-height: 26px;"><span leaf="">import<span leaf=""> OpenAI  
<span style="color: #c678dd;line-height: 26px;"><span leaf="">import<span leaf=""> argparse  
  
<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">def<span leaf=""> <span style="color: #61aeee;line-height: 26px;"><span leaf="">create_llm_client<span style="line-height: 26px;"><span leaf="">()<span leaf="">:  
<span leaf="">    client = OpenAI(  
<span leaf="">        base_url=<span style="color: #98c379;line-height: 26px;"><span leaf="">"http://192.168.180.137:8006/v1"<span leaf="">,  
<span leaf="">        api_key=<span style="color: #98c379;line-height: 26px;"><span leaf="">"not-needed"<span leaf="">  <span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""># API key might not be needed for local deployment  
<span leaf="">    )  
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">return<span leaf=""> client  
  
<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">def<span leaf=""> <span style="color: #61aeee;line-height: 26px;"><span leaf="">query_llm<span style="line-height: 26px;"><span leaf="">(prompt, client=None, model=<span style="color: #98c379;line-height: 26px;"><span leaf="">"Qwen/Qwen2.5-32B-Instruct-AWQ"<span leaf="">)<span leaf="">:  
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> client <span style="color: #c678dd;line-height: 26px;"><span leaf="">is<span leaf=""> <span style="color: #56b6c2;line-height: 26px;"><span leaf="">None<span leaf="">:  
<span leaf="">        client = create_llm_client()  
<span leaf="">      
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">try<span leaf="">:  
<span leaf="">        response = client.chat.completions.create(  
<span leaf="">            model=model,  
<span leaf="">            messages=[  
<span leaf="">                {<span style="color: #98c379;line-height: 26px;"><span leaf="">"role"<span leaf="">: <span style="color: #98c379;line-height: 26px;"><span leaf="">"user"<span leaf="">, <span style="color: #98c379;line-height: 26px;"><span leaf="">"content"<span leaf="">: prompt}  
<span leaf="">            ],  
<span leaf="">            temperature=<span style="color: #d19a66;line-height: 26px;"><span leaf="">0.7<span leaf="">,  
<span leaf="">        )  
<span leaf="">        <span style="color: #c678dd;line-height: 26px;"><span leaf="">return<span leaf=""> response.choices[<span style="color: #d19a66;line-height: 26px;"><span leaf="">0<span leaf="">].message.content  
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">except<span leaf=""> Exception <span style="color: #c678dd;line-height: 26px;"><span leaf="">as<span leaf=""> e:  
<span leaf="">        print(<span style="color: #98c379;line-height: 26px;"><span leaf="">f"Error querying LLM: <span style="color: #e06c75;line-height: 26px;"><span leaf="">{e}<span leaf="">"<span leaf="">)  
<span leaf="">        <span style="color: #c678dd;line-height: 26px;"><span leaf="">return<span leaf=""> <span style="color: #56b6c2;line-height: 26px;"><span leaf="">None  
  
<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;"><span leaf="">def<span leaf=""> <span style="color: #61aeee;line-height: 26px;"><span leaf="">main<span style="line-height: 26px;"><span leaf="">()<span leaf="">:  
<span leaf="">    parser = argparse.ArgumentParser(description=<span style="color: #98c379;line-height: 26px;"><span leaf="">'Query an LLM with a prompt'<span leaf="">)  
<span leaf="">    parser.add_argument(<span style="color: #98c379;line-height: 26px;"><span leaf="">'--prompt'<span leaf="">, type=str, help=<span style="color: #98c379;line-height: 26px;"><span leaf="">'The prompt to send to the LLM'<span leaf="">, required=<span style="color: #56b6c2;line-height: 26px;"><span leaf="">True<span leaf="">)  
<span leaf="">    parser.add_argument(<span style="color: #98c379;line-height: 26px;"><span leaf="">'--model'<span leaf="">, type=str, default=<span style="color: #98c379;line-height: 26px;"><span leaf="">"Qwen/Qwen2.5-32B-Instruct-AWQ"<span leaf="">,  
<span leaf="">                       help=<span style="color: #98c379;line-height: 26px;"><span leaf="">'The model to use (default: Qwen/Qwen2.5-32B-Instruct-AWQ)'<span leaf="">)  
<span leaf="">    args = parser.parse_args()  
  
<span leaf="">    client = create_llm_client()  
<span leaf="">    response = query_llm(args.prompt, client, model=args.model)  
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> response:  
<span leaf="">        print(response)  
<span leaf="">    <span style="color: #c678dd;line-height: 26px;"><span leaf="">else<span leaf="">:  
<span leaf="">        print(<span style="color: #98c379;line-height: 26px;"><span leaf="">"Failed to get response from LLM"<span leaf="">)  
  
<span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> __name__ == <span style="color: #98c379;line-height: 26px;"><span leaf="">"__main__"<span leaf="">:  
<span leaf="">    main()  
  1. tools/search_engine 使用duckduckgo
<span data-cacheurl="" data-remoteid="" style="display: block; background: url(&quot;https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg&amp;tp=webp&amp;wxfrom=15&amp;wx_lazy=1&quot;) 10px 10px / 40px no-repeat rgb(40, 44, 52); height: 30px; width: 100%; margin-bottom: -7px; border-radius: 5px;" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&amp;from=appmsg" class="" data-fail="0"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span leaf="">import argparse  
<span leaf="">import sys  
<span leaf="">import traceback  
<span leaf="">from duckduckgo_search import DDGS  
  
<span leaf="">def search(query, max_results=10):  
<span leaf="">    <span style="color: #98c379;line-height: 26px;"><span leaf="">""<span style="color: #98c379;line-height: 26px;"><span leaf="">"  
<span leaf="">    Search using DuckDuckGo and return results with URLs and text snippets.  
<span leaf="">    Uses the HTML backend which has proven to be more reliable.  
<span leaf="">      
<span leaf="">    Args:  
<span leaf="">        query (str): Search query  
<span leaf="">        max_results (int): Maximum number of results to return  
<span leaf="">    "<span style="color: #98c379;line-height: 26px;"><span leaf="">""  
<span leaf="">    try:  
<span leaf="">        <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"DEBUG: Searching for query: {query}"<span leaf="">, file=sys.stderr)  
<span leaf="">          
<span leaf="">        with DDGS() as ddgs:  
<span leaf="">            results = list(ddgs.text(  
<span leaf="">                query,  
<span leaf="">                max_results=max_results,  
<span leaf="">                backend=<span style="color: #98c379;line-height: 26px;"><span leaf="">'html'<span leaf="">  <span style="color: #5c6370;font-style: italic;line-height: 26px;"><span leaf=""># Use only the HTML backend  
<span leaf="">            ))  
<span leaf="">              
<span leaf="">            <span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> not results:  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(<span style="color: #98c379;line-height: 26px;"><span leaf="">"DEBUG: No results found"<span leaf="">, file=sys.stderr)  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">return  
<span leaf="">              
<span leaf="">            <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"DEBUG: Found {len(results)} results"<span leaf="">, file=sys.stderr)  
<span leaf="">              
<span leaf="">            <span style="color: #c678dd;line-height: 26px;"><span leaf="">for<span leaf=""> i, r <span style="color: #c678dd;line-height: 26px;"><span leaf="">in<span leaf=""> enumerate(results, 1):  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"\n=== Result {i} ==="<span leaf="">)  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"URL: {r.get('link', r.get('href', 'N/A'))}"<span leaf="">)  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"Title: {r.get('title', 'N/A')}"<span leaf="">)  
<span leaf="">                <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"Snippet: {r.get('snippet', r.get('body', 'N/A'))}"<span leaf="">)  
<span leaf="">                  
<span leaf="">    except Exception as e:  
<span leaf="">        <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"ERROR: Search failed: {str(e)}"<span leaf="">, file=sys.stderr)  
<span leaf="">        <span style="color: #e6c07b;line-height: 26px;"><span leaf="">print<span leaf="">(f<span style="color: #98c379;line-height: 26px;"><span leaf="">"ERROR type: {type(e)}"<span leaf="">, file=sys.stderr)  
<span leaf="">        traceback.print_exc(file=sys.stderr)  
<span leaf="">        sys.exit(1)  
  
<span leaf="">def main():  
<span leaf="">    parser = argparse.ArgumentParser(description=<span style="color: #98c379;line-height: 26px;"><span leaf="">"Search using DuckDuckGo API"<span leaf="">)  
<span leaf="">    parser.add_argument(<span style="color: #98c379;line-height: 26px;"><span leaf="">"query"<span leaf="">, <span style="color: #e6c07b;line-height: 26px;"><span leaf="">help<span leaf="">=<span style="color: #98c379;line-height: 26px;"><span leaf="">"Search query"<span leaf="">)  
<span leaf="">    parser.add_argument(<span style="color: #98c379;line-height: 26px;"><span leaf="">"--max-results"<span leaf="">, <span style="color: #e6c07b;line-height: 26px;"><span leaf="">type<span leaf="">=int, default=10,  
<span leaf="">                      <span style="color: #e6c07b;line-height: 26px;"><span leaf="">help<span leaf="">=<span style="color: #98c379;line-height: 26px;"><span leaf="">"Maximum number of results (default: 10)"<span leaf="">)  
<span leaf="">      
<span leaf="">    args = parser.parse_args()  
<span leaf="">    search(args.query, args.max_results)  
  
<span style="color: #c678dd;line-height: 26px;"><span leaf="">if<span leaf=""> __name__ == <span style="color: #98c379;line-height: 26px;"><span leaf="">"__main__"<span leaf="">:  
<span leaf="">    main()