前段时间,我看到了一篇博客介绍Cursor的,使得Cursor再次进入了我的视野。最近我有一个构建AI工具的想法,但由于对AI编程相关技术栈的局限,暂时搁置。因此,我重新安装了Cursor进行尝试,发现它已经进化成真正意义上的AI编程助手,于是决定利用Cursor实现之前的构想。
需求来源
相信业务常规运营(BAU)工作是所有团队都在处理的事情。对我而言,这些任务往往是“不想做,但又不得不做”的烦恼。尤其是当你已经总结出执行方案后,每次处理时都感觉自己只是一个无情的BAU处理机器,繁琐而乏味。在这种情况下,我想,是否可以每次只需执行最简单的命令,就能自动执行我期望的动作?而Python脚本正是一个很好的解决方案。因此,我列出了所有需要完成的任务,并通过Python脚本实现了这些功能。
通过这一过程,我成功实现了一键执行期望动作。下次再遇到相同的任务时,只需运行这个脚本,就能省去寻找资源和逐个文件分配权限的时间,效率瞬间从15分钟提升至1分钟。然而,这一解决方案仍然存在角色限制,仅限于技术性角色使用。那么,如何让非技术性角色也能使用这个解决方案呢?
这个问题一直萦绕在我的脑中,直到我想到最近学习的LLM(大型语言模型)编程。灵光一现,我意识到可以让非技术性角色通过自然语言描述,然后由大模型解析这些自然语言并翻译成需要执行的脚本,再进行自动执行。
调研
说干就干,晚上下班后,我开始寻找合适的大模型。我的期望是:
- 能在本地运行(方便调试)
- 我的机器能够支持(有一台常规配置的MacOS M1 16G物理机)
- 参数足够以进行文本到行为的转换
分析与任务规划
有了想法和大模型后,剩下的就是考虑如何实现。经过两周晚上的LLM编程入门课程学习,我了解到了Langchain这个框架,以及如何使用。有了Langchain,实现这一需求变得轻而易举。大致步骤如下:
- 在本地使用Ollama运行Llama3.1 8B模型。
- 构建脚本并放入合适的包中。
- 使用Langchain构建链,并对方法及Prompt进行封装。
- 调用封装好的方法进行自然语言分析,选择并执行需要的脚本。
还有个问题在于,如何让大模型知道有哪些可执行脚本及其作用。针对这一问题,有多种选择,比如:
- 使用提示模板
- 使用分类器
- 使用上下文管理
方法 | 优点 | 缺点 |
---|---|---|
提示模板 | 简单易用:设计和实现相对简单,用户只需提供自然语言输入。 | 局限性:可能无法涵盖所有可能的输入,导致复杂请求无法处理。 |
灵活性高:可以根据不同的需求快速调整模板。 | 维护成本:随着需求变化,模板需要不断更新和维护。 | |
可读性强:用户易于理解提示内容,便于培训和上手。 | ||
分类器 | 自动化程度高:能够自动识别和分类输入,适合处理大量数据。 | 复杂性:需要大量的训练数据和时间来构建和优化分类器。 |
适应性强:可以通过训练模型提高分类精度。 | 不确定性:分类结果可能不准确,导致错误的脚本被调用。 | |
上下文管理 | 状态跟踪:能够在多个交互中保持上下文。 | 实现复杂:需要设计复杂的状态管理机制。 |
智能响应:根据上下文信息提供更精准的响应。 | 资源消耗:维护上下文信息可能消耗更多计算资源和内存。 |
经过权衡优缺点、实现复杂度与当前需求,我决定采用提示模板作为解决方案。
万事俱备,“东风” - Cursor,也来了,直接开整。
实现步骤(Pair with Cursor)
任务1: 给定多个Python脚本,使用Ollama和大模型llama3.1,根据输入的一段话分析出需要执行哪个Python脚本,并执行。
execute_script_with_ollama.py
import subprocess
from langchain_community.llms import Ollama
def execute_script(script_name):
try:
result = subprocess.run(['python', script_name], capture_output=True, text=True)
print(result.stdout)
except Exception as e:
print(f"Error executing script {script_name}: {e}")
def analyze_and_execute(input_text):
llm = Ollama(model="llama3.1")
prompt = f"根据以下输入选择一个Python脚本并执行:\n\n{input_text}\n\n可选脚本:\n1. assign_access_for_dataset.py\n2. generate_random_num_from_1_100.py\n3. sum_1_100.py\n\n请返回要执行的脚本文件名。"
response = llm(prompt)
script_name = response.strip()
if script_name in ['assign_access_for_dataset.py', 'generate_random_num_from_1_100.py', 'sum_1_100.py']:
execute_script(f'src/python_scripts/{script_name}')
else:
print("No matching script found or invalid response.")
if __name__ == "__main__":
user_input = input("请输入一段话: ")
analyze_and_execute(user_input)
任务2: 将Prompt进行封装,使得提示更容易扩展,并放到新的文件中。
prompt_generator.py
class PromptGenerator:
def __init__(self, scripts):
self.scripts = scripts
def generate_prompt(self, input_text):
script_list = "\n".join([f"{i+1}. {script}" for i, script in enumerate(self.scripts)])
prompt = f"根据以下输入选择一个Python脚本并执行:\n\n{input_text}\n\n可选脚本:\n{script_list}\n\n请返回要执行的脚本文件名。"
return prompt
execute_script_with_ollama.py
import subprocess
from langchain_community.llms import Ollama
from prompt_generator import PromptGenerator
def execute_script(script_name):
try:
result = subprocess.run(['python', script_name], capture_output=True, text=True)
print(result.stdout)
except Exception as e:
print(f"Error executing script {script_name}: {e}")
def analyze_and_execute(input_text):
scripts = [
'assign_access_for_dataset.py',
'generate_random_num_from_1_100.py',
'sum_1_100.py'
]
prompt_generator = PromptGenerator(scripts)
llm = Ollama(model="llama3.1")
prompt = prompt_generator.generate_prompt(input_text)
response = llm(prompt)
script_name = response.strip()
if script_name in scripts:
execute_script(f'src/python_scripts/{script_name}')
else:
print("No matching script found or invalid response.")
if __name__ == "__main__":
user_input = input("请输入一段话: ")
analyze_and_execute(user_input)
任务3: 在配置文件中维护脚本列表,以便管理多种类型的PromptGenerator。尝试使用工厂模式。
config.py
#配置文件,用于维护脚本列表
SCRIPTS = [
'assign_access_for_dataset.py',
'generate_random_num_from_1_100.py',
'sum_1_100.py'
]
prompt_generator.py
from config import SCRIPTS
class PromptGenerator:
def __init__(self):
self.scripts = SCRIPTS
def generate_prompt(self, input_text):
script_list = "\n".join([f"{i+1}. {script}" for i, script in enumerate(self.scripts)])
prompt = f"根据以下输入选择一个Python脚本并执行:\n\n{input_text}\n\n可选脚本:\n{script_list}\n\n请返回要执行的脚本文件名。"
return prompt
class AdvancedPromptGenerator(PromptGenerator):
def generate_prompt(self, input_text):
script_list = "\n".join([f"{i+1}. {script}" for i, script in enumerate(self.scripts)])
prompt = f"高级模式:根据以下输入选择一个Python脚本并执行:\n\n{input_text}\n\n可选脚本:\n{script_list}\n\n请返回要执行的脚本文件名。"
return prompt
execute_script_with_ollama.py
import subprocess
from langchain_community.llms import Ollama
from prompt_generator_factory import PromptGeneratorFactory
from config import SCRIPTS
def execute_script(script_name):
try:
result = subprocess.run(['python', script_name], capture_output=True, text=True)
print(result.stdout)
except Exception as e:
print(f"Error executing script {script_name}: {e}")
def analyze_and_execute(input_text, generator_type="default"):
prompt_generator = PromptGeneratorFactory.get_prompt_generator(generator_type)
llm = Ollama(model="llama3.1")
prompt = prompt_generator.generate_prompt(input_text)
response = llm(prompt)
script_name = response.strip()
if script_name in SCRIPTS:
execute_script(f'src/python_scripts/{script_name}')
else:
print("No matching script found or invalid response.")
if __name__ == "__main__":
user_input = input("请输入一段话: ")
generator_type = input("请输入生成器类型(default或advanced): ")
analyze_and_execute(user_input, generator_type)
到此为止,基本上就可以运行了。我还进行了多轮任务,包括让它帮助优化、重构、生成前端页面等,但不一一列举了。有兴趣的人可以下载完整代码库进行体验。
成果展示
我准备了两类脚本任务进行测试:常规类和任务分配类。期望的结果是能够根据输入的自然语言,正确分析应该使用哪个提示生成器,并使用该提示模板构建Prompt,让大模型根据Prompt做出正确的脚本选择,并执行。
- 常规类
- 生成1到100之间的随机数
- 计算1到100的和
- 获取当前日期和时间
- 任务分配类
- 为数据集分配访问权限
- 为Dashboard分配访问权限
场景一:让助手给我执行获取当天日期的脚本
前端执行:
后端输出结果:
场景二:让助手给我执行 Dataset 权限分配的脚本
前端执行:
后端输出结果:
结果符合预期,AC通过。凭借微薄的AI编程基础,使用Cursor成功的开发了AI工具,并成功实现了阶段性目标!🎉
相关环境
- Python 3.10
- Ollama
- Llama 3.1 8B
- Cursor试用版本,2周试用期
- Cursor大模型使用情况:claude-3.5-sonnet与gpt-4o切换使用
- AI编程的预备知识可以通过LLM编程入门课程来学习。即使是零基础的学习者也能理解课程内容,从中掌握一些基本的AI编程概念和框架,但要深入了解仍需自行探索。
Cursor的体验感受
- 易用性:整个过程,没写一行代码,扮演“嘴强王者”,让Cursor成为你的码仔,它能无怨无求的按照要求写出代码。通过自然语言输入,用户可以轻松生成代码或执行复杂任务,这种无缝集成大大降低了技术门槛。
- 高效的代码生成:生成的代码通常处于可用状态,只需进行少量修改即可满足具体需求。
- 智能化的上下文理解:Cursor升级了模型后,支持超长上下文(200k),使得它能够理解项目背景、代码结构及编码风格。这种能力不仅提升了生成代码的相关性和准确性,还使得模型能够根据历史交互进行智能响应,提供更为精准的建议和解决方案。能按照生成的既有风格进行演进。
- 知识储备与效能提升:你的技术知识储备越丰富,那么Cursor帮你提升的效率会越高。比如,如果方法论知识丰富,它能帮忙快速的提升代码的可读性、可维护性和扩展性,在重构方面表现尤为突出。
当然现在的边界情况是简单产品,其实进入到深水区后,依然会遇到很多卡点,不过没关系,迈出的这一步很重要。
最后的最后
附上一些使用Cursor的技巧
- 表述需求时的注意事项:在表述需求时,务必做到明确而谨慎。清晰的需求能够帮助 Cursor 精确理解并执行,而不必要的信息可能会增加实现的复杂性。例如,当我错误地提到使用Ollama3.1时,实际上在编写代码时,Cursor是将Ollama3.1作为模型引用的,但正确的模型应该是Llama3.1。同时,过度简化需求可能会导致 Cursor 自由发挥,产生不必要的偏差。例如,我最开始并没有提到使用大模型来满足需求,只是简单描述了需求。在最初的代码版本中,就并未采用大模型框架来实现。
- README.md 文档的重要性:让 Cursor 从项目一开始就写 README.md 文档,记录清楚产品功能、实现技术栈等,并且让它在完成关键节点后及时更新。
- 中间版本的救命工具:用 Git 做版本管理,在关键成功节点提交时写好说明内容,给自己复原的机会。否则,等一段时间后,你想要回退到某一节点的时候,那个节点就已经找不到了。
- 使用 Composer 和 Chat 功能的建议:使用 Composer 和 Chat 功能时,尽量多 @codebase,否则 Cursor 常有幻觉,不知道项目内容是什么。
- 文档链接的整合:常用的文档链接可以加入 Docs 中,比如你习惯使用的 AI API 调用文档,方便在使用时随时 @。(太好用了)
- 隐私选项:如果用于项目,请记得勾上Privacy Mode,这个选项能够保证你的代码不被第三方或者Cursor官方保存。(明面上是这么说的)
到此,我们的讨论就告一段落了。感谢大家的阅读和关注!