把cursor玩成devin的技巧 -- 知识铺
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("https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&from=appmsg&tp=webp&wxfrom=15&wx_lazy=1") 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&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个工具
- llm_api使用vllm部署的qwen
<span data-cacheurl="" data-remoteid="" style="display: block; background: url("https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&from=appmsg&tp=webp&wxfrom=15&wx_lazy=1") 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&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()
- tools/search_engine 使用duckduckgo
<span data-cacheurl="" data-remoteid="" style="display: block; background: url("https://mmbiz.qpic.cn/mmbiz_svg/4h0Uv4XOMvOQQzHgqlOLvFxMhqPDZI1pfocCsNWBCvja8N33pl3iclpLmxZ2LiaicgHoLPVAKKc0l0LaLH2XBmxpiaaXWZbUXLE7/640?wx_fmt=svg&from=appmsg&tp=webp&wxfrom=15&wx_lazy=1") 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&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()
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/ai/post/20241225/%E6%8A%8Acursor%E7%8E%A9%E6%88%90devin%E7%9A%84%E6%8A%80%E5%B7%A7--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com