add /chatflow.ask and /chatflow.gen
This commit is contained in:
parent
1c0e5e045f
commit
344fbff719
42
merico/chatflow/ask/command.py
Normal file
42
merico/chatflow/ask/command.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# 在 ask/command.py 中
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from devchat.llm import chat
|
||||||
|
from lib.ide_service import IDEService
|
||||||
|
|
||||||
|
ROOT_WORKFLOW_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
sys.path.append(ROOT_WORKFLOW_DIR)
|
||||||
|
|
||||||
|
from chatflow.util.contexts import CONTEXTS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
PROMPT = f"""
|
||||||
|
{CONTEXTS.replace("{", "{{").replace("}", "}}")}
|
||||||
|
""" + """
|
||||||
|
当前选中代码:{selected_code}
|
||||||
|
当前打开文件路径:{file_path}
|
||||||
|
用户要求或问题:{question}
|
||||||
|
"""
|
||||||
|
@chat(prompt=PROMPT, stream_out=True)
|
||||||
|
def ask(question, selected_code, file_path):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_code():
|
||||||
|
"""Retrieves the selected lines of code from the user's selection."""
|
||||||
|
selected_data = IDEService().get_selected_range().dict()
|
||||||
|
return selected_data
|
||||||
|
|
||||||
|
|
||||||
|
def main(question):
|
||||||
|
selected_text = get_selected_code()
|
||||||
|
file_path = selected_text.get("abspath", "")
|
||||||
|
code_text = selected_text.get("text", "")
|
||||||
|
|
||||||
|
ask(question=question, selected_code=code_text, file_path=file_path)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1])
|
4
merico/chatflow/ask/command.yml
Normal file
4
merico/chatflow/ask/command.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
description: 生成工作流命令,输入命令信息
|
||||||
|
input: required
|
||||||
|
steps:
|
||||||
|
- run: $devchat_python $command_path/command.py "$input"
|
421
merico/chatflow/gen/command.py
Normal file
421
merico/chatflow/gen/command.py
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
"""
|
||||||
|
生成工作流命令实现
|
||||||
|
|
||||||
|
步骤1: 根据用户输入的工作流命令定义信息,生成相关工作流实现步骤描述,展示相关信息,等待用户确认;
|
||||||
|
步骤2: 根据用户确认的工作流实现步骤描述,生成工作流命令实现代码,并保存到指定文件中;
|
||||||
|
"""
|
||||||
|
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from lib.chatmark import Button, Form, TextEditor, Step, Radio, Checkbox
|
||||||
|
from lib.ide_service import IDEService
|
||||||
|
from devchat.llm import chat, chat_json
|
||||||
|
|
||||||
|
ROOT_WORKFLOW_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
sys.path.append(ROOT_WORKFLOW_DIR)
|
||||||
|
|
||||||
|
from chatflow.util.contexts import CONTEXTS
|
||||||
|
|
||||||
|
|
||||||
|
# 工作流命令定义模板
|
||||||
|
COMMAND_YML_TEMPLATE = """description: {description}
|
||||||
|
{input_section}
|
||||||
|
{help_section}{workflow_python_section}
|
||||||
|
steps:
|
||||||
|
- run: ${python_cmd} $command_path/command.py{input_param}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 工作流命令实现模板
|
||||||
|
COMMAND_PY_TEMPLATE = """#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
{imports}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
{main_code}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 从用户输入提取工作流信息的提示
|
||||||
|
EXTRACT_INFO_PROMPT = """
|
||||||
|
请从以下用户输入中提取创建工作流命令所需的关键信息:
|
||||||
|
|
||||||
|
用户输入:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
工作流上下文信息:
|
||||||
|
{contexts}
|
||||||
|
|
||||||
|
请提取以下信息并以JSON格式返回:
|
||||||
|
1. command_name: 工作流命令名称,应以/开头,如"/example"或"/category.command"
|
||||||
|
2. description: 工作流命令的简短描述
|
||||||
|
3. input_required: 工作流是否需要输入参数(true/false)
|
||||||
|
4. custom_env: 是否需要自定义Python环境(true/false)
|
||||||
|
5. env_name: 如果需要自定义环境,环境名称是什么
|
||||||
|
6. dependencies: 如果需要自定义环境,依赖文件名是什么(如requirements.txt)
|
||||||
|
7. purpose: 工作流的主要目的和功能
|
||||||
|
8. implementation_ideas: 实现思路的简要描述
|
||||||
|
|
||||||
|
如果某项信息在用户输入中未明确指定,请根据上下文合理推断。
|
||||||
|
|
||||||
|
返回格式示例:
|
||||||
|
{{
|
||||||
|
"command_name": "/example.command",
|
||||||
|
"description": "这是一个示例命令",
|
||||||
|
"input_required": true,
|
||||||
|
"custom_env": false,
|
||||||
|
"env_name": "",
|
||||||
|
"dependencies": "",
|
||||||
|
"purpose": "这个命令的主要目的是...",
|
||||||
|
"implementation_ideas": "可以通过以下步骤实现..."
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 工作流步骤生成提示
|
||||||
|
WORKFLOW_STEPS_PROMPT = """
|
||||||
|
请为以下工作流命令对应脚本文件生成详细的实现步骤描述:
|
||||||
|
|
||||||
|
工作流命令名称: {command_name}
|
||||||
|
工作流命令描述: {description}
|
||||||
|
输入要求: {input_requirement}
|
||||||
|
工作流目的: {purpose}
|
||||||
|
实现思路: {implementation_ideas}
|
||||||
|
|
||||||
|
工作流上下文信息:
|
||||||
|
{contexts}
|
||||||
|
|
||||||
|
请提供清晰的步骤描述,包括:
|
||||||
|
1. 每个步骤需要完成的具体任务
|
||||||
|
2. 每个步骤可能需要的输入和输出
|
||||||
|
3. 每个步骤可能需要使用的IDE Service或ChatMark组件
|
||||||
|
4. 任何其他实现细节
|
||||||
|
|
||||||
|
返回格式应为markdown块包裹的步骤列表,每个步骤都有详细描述。输出示例如下:
|
||||||
|
```steps
|
||||||
|
步骤1: 获取用户输入的重构任务要求
|
||||||
|
步骤2: 调用IDE Service获取选中代码
|
||||||
|
步骤3: 根据用户重构任务要求,调用大模型生成选中代码的重构代码
|
||||||
|
步骤4: 调用IDE Service,将生成的重构代码通过DIFF VIEW方式展示给用户
|
||||||
|
```
|
||||||
|
|
||||||
|
不要输出工作流命令的其他构建步骤,只需要清洗描述工作流命令对应command.py中对应的步骤实现即可。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 代码实现生成提示
|
||||||
|
CODE_IMPLEMENTATION_PROMPT = """
|
||||||
|
请为以下工作流命令生成Python实现代码:
|
||||||
|
|
||||||
|
工作流命令名称: {command_name}
|
||||||
|
工作流命令描述: {description}
|
||||||
|
输入要求: {input_requirement}
|
||||||
|
自定义环境: {custom_env}
|
||||||
|
工作流实现步骤:
|
||||||
|
{workflow_steps}
|
||||||
|
|
||||||
|
工作流上下文信息:
|
||||||
|
{contexts}
|
||||||
|
|
||||||
|
请生成完整的Python代码实现,包括:
|
||||||
|
1. 必要的导入语句
|
||||||
|
2. 主函数实现
|
||||||
|
3. 按照工作流步骤实现具体功能
|
||||||
|
4. 适当的错误处理
|
||||||
|
5. 必要的注释说明
|
||||||
|
6. 所有markdown代码块都要有明确的语言标识,如python、json、yaml、code等
|
||||||
|
|
||||||
|
代码应该使用IDE Service接口与IDE交互,使用ChatMark组件与用户交互。
|
||||||
|
输出格式应为markdown代码块,语言标识为python。仅输出工作流实现对应的脚本代码块,不需要输出其他逻辑信息。
|
||||||
|
|
||||||
|
只需要输出最终PYTHON代码块,不需要其他信息。例如:
|
||||||
|
```python
|
||||||
|
....
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
@chat_json(prompt=EXTRACT_INFO_PROMPT)
|
||||||
|
def extract_workflow_info(user_input, contexts):
|
||||||
|
"""从用户输入中提取工作流信息"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@chat(prompt=WORKFLOW_STEPS_PROMPT, stream_out=False)
|
||||||
|
def generate_workflow_steps(command_name, description, input_requirement, purpose, implementation_ideas, contexts):
|
||||||
|
"""生成工作流实现步骤描述"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@chat(prompt=CODE_IMPLEMENTATION_PROMPT, stream_out=False)
|
||||||
|
def generate_workflow_code(command_name, description, input_requirement, custom_env, workflow_steps, contexts):
|
||||||
|
"""生成工作流实现代码"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse_command_path(command_name):
|
||||||
|
"""
|
||||||
|
解析命令路径,返回目录结构和最终命令名
|
||||||
|
确保工作流创建在custom目录下的有效namespace中
|
||||||
|
"""
|
||||||
|
parts = command_name.strip('/').split('.')
|
||||||
|
|
||||||
|
# 获取custom目录路径
|
||||||
|
custom_dir = os.path.join(os.path.expanduser('~'), '.chat', 'scripts', 'custom')
|
||||||
|
|
||||||
|
# 获取custom目录下的有效namespace
|
||||||
|
valid_namespaces = []
|
||||||
|
config_path = os.path.join(custom_dir, 'config.yml')
|
||||||
|
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
if config and 'namespaces' in config:
|
||||||
|
valid_namespaces = config['namespaces']
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取custom配置文件失败: {str(e)}")
|
||||||
|
|
||||||
|
# 如果没有找到有效namespace,使用默认namespace
|
||||||
|
if not valid_namespaces:
|
||||||
|
print("警告: 未找到有效的custom namespace,将使用默认namespace 'default'")
|
||||||
|
valid_namespaces = ['default']
|
||||||
|
|
||||||
|
# 确保default namespace存在于config.yml中
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||||
|
with open(config_path, 'w') as f:
|
||||||
|
yaml.dump({'namespaces': ['default']}, f)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"创建默认namespace配置失败: {str(e)}")
|
||||||
|
|
||||||
|
# 使用第一个有效namespace
|
||||||
|
namespace = valid_namespaces[0]
|
||||||
|
|
||||||
|
# 创建最终命令目录路径
|
||||||
|
command_dir = os.path.join(custom_dir, namespace)
|
||||||
|
|
||||||
|
# 创建目录结构
|
||||||
|
for part in parts[:-1]:
|
||||||
|
command_dir = os.path.join(command_dir, part)
|
||||||
|
|
||||||
|
return command_dir, parts[-1]
|
||||||
|
|
||||||
|
def parse_markdown_block(response, block_type="steps"):
|
||||||
|
"""
|
||||||
|
从AI响应中解析指定类型的Markdown代码块内容,支持处理嵌套的代码块。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
response (str): AI生成的响应文本
|
||||||
|
block_type (str): 要解析的代码块类型,默认为"steps"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 解析出的代码块内容
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: 解析失败时抛出异常
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 处理可能存在的思考过程
|
||||||
|
if response.find("</think>") != -1:
|
||||||
|
response = response.split("</think>")[-1]
|
||||||
|
|
||||||
|
# 构建起始标记
|
||||||
|
start_marker = f"```{block_type}"
|
||||||
|
end_marker = "```"
|
||||||
|
|
||||||
|
# 查找起始位置
|
||||||
|
start_pos = response.find(start_marker)
|
||||||
|
if start_pos == -1:
|
||||||
|
# 如果没有找到指定类型的标记,直接返回原文本
|
||||||
|
return response.strip()
|
||||||
|
|
||||||
|
# 从标记后开始的位置
|
||||||
|
content_start = start_pos + len(start_marker)
|
||||||
|
|
||||||
|
# 从content_start开始找到第一个未配对的```
|
||||||
|
pos = content_start
|
||||||
|
open_blocks = 1 # 已经有一个开放的块
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# 找到下一个```
|
||||||
|
next_marker = response.find(end_marker, pos)
|
||||||
|
if next_marker == -1:
|
||||||
|
# 如果没有找到结束标记,返回剩余所有内容
|
||||||
|
break
|
||||||
|
|
||||||
|
# 检查这是开始还是结束标记
|
||||||
|
# 向后看是否跟着语言标识符
|
||||||
|
after_marker = response[next_marker + 3:]
|
||||||
|
# 检查是否是新的代码块开始 - 只要```后面跟着非空白字符,就认为是新代码块开始
|
||||||
|
if after_marker.strip() and not after_marker.startswith("\n"):
|
||||||
|
first_word = after_marker.split()[0]
|
||||||
|
if not any(c in first_word for c in ",.;:!?()[]{}"):
|
||||||
|
open_blocks += 1
|
||||||
|
else:
|
||||||
|
open_blocks -= 1
|
||||||
|
else:
|
||||||
|
open_blocks -= 1
|
||||||
|
|
||||||
|
if open_blocks == 0:
|
||||||
|
# 找到匹配的结束标记
|
||||||
|
return response[content_start:next_marker].strip()
|
||||||
|
|
||||||
|
pos = next_marker + 3
|
||||||
|
|
||||||
|
# 如果没有找到匹配的结束标记,返回从content_start到末尾的内容
|
||||||
|
return response[content_start:].strip()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.info(f"Response: {response}")
|
||||||
|
logging.error(f"Exception in parse_markdown_block: {str(e)}")
|
||||||
|
raise Exception(f"解析{block_type}内容失败: {str(e)}") from e
|
||||||
|
|
||||||
|
def create_workflow_files(command_dir, command_name, description, input_required,
|
||||||
|
code):
|
||||||
|
"""创建工作流命令文件"""
|
||||||
|
# 创建命令目录
|
||||||
|
os.makedirs(command_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 创建command.yml
|
||||||
|
input_section = f"input: {'required' if input_required else 'optional'}"
|
||||||
|
help_section = "help: README.md"
|
||||||
|
input_param = ' "$input"' if input_required else ''
|
||||||
|
|
||||||
|
# 添加自定义环境配置
|
||||||
|
workflow_python_section = ""
|
||||||
|
python_cmd = "devchat_python"
|
||||||
|
|
||||||
|
yml_content = COMMAND_YML_TEMPLATE.format(
|
||||||
|
description=description,
|
||||||
|
input_section=input_section,
|
||||||
|
help_section=help_section,
|
||||||
|
workflow_python_section=workflow_python_section,
|
||||||
|
python_cmd=python_cmd,
|
||||||
|
input_param=input_param
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(os.path.join(command_dir, 'command.yml'), 'w') as f:
|
||||||
|
f.write(yml_content)
|
||||||
|
|
||||||
|
# 创建command.py
|
||||||
|
with open(os.path.join(command_dir, 'command.py'), 'w') as f:
|
||||||
|
f.write(code)
|
||||||
|
|
||||||
|
# 设置执行权限
|
||||||
|
os.chmod(os.path.join(command_dir, 'command.py'), 0o755)
|
||||||
|
|
||||||
|
# 创建README.md
|
||||||
|
readme_content = f"# {command_name}\n\n{description}\n"
|
||||||
|
with open(os.path.join(command_dir, 'README.md'), 'w') as f:
|
||||||
|
f.write(readme_content)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 获取用户输入
|
||||||
|
user_input = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||||
|
|
||||||
|
# 步骤1: 通过AI分析用户输入,提取必要信息
|
||||||
|
with Step("分析用户输入,提取工作流信息..."):
|
||||||
|
workflow_info = extract_workflow_info(user_input=user_input, contexts=CONTEXTS)
|
||||||
|
|
||||||
|
# 步骤3: 生成工作流实现步骤描述
|
||||||
|
with Step("生成工作流实现步骤描述..."):
|
||||||
|
workflow_steps = generate_workflow_steps(
|
||||||
|
command_name=workflow_info.get("command_name", ""),
|
||||||
|
description=workflow_info.get("description", ""),
|
||||||
|
input_requirement="可选",
|
||||||
|
purpose=workflow_info.get("purpose", ""),
|
||||||
|
implementation_ideas=workflow_info.get("implementation_ideas", ""),
|
||||||
|
contexts=CONTEXTS
|
||||||
|
)
|
||||||
|
|
||||||
|
workflow_steps = parse_markdown_block(workflow_steps, block_type="steps")
|
||||||
|
|
||||||
|
# 步骤2: 使用Form组件一次性展示所有信息,让用户编辑
|
||||||
|
print("\n## 工作流信息\n")
|
||||||
|
|
||||||
|
# 创建所有编辑组件
|
||||||
|
command_name_editor = TextEditor(workflow_info.get("command_name", "/example.command"))
|
||||||
|
description_editor = TextEditor(workflow_info.get("description", "工作流命令描述"))
|
||||||
|
input_radio = Radio(["必选 (required)", "可选 (optional)"])
|
||||||
|
purpose_editor = TextEditor(workflow_info.get("purpose", "请描述工作流的主要目的和功能"))
|
||||||
|
# ideas_editor = TextEditor(workflow_info.get("implementation_ideas", "请描述实现思路"))
|
||||||
|
steps_editor = TextEditor(workflow_steps)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用Form组件一次性展示所有编辑组件
|
||||||
|
form = Form([
|
||||||
|
"命令名称:",
|
||||||
|
command_name_editor,
|
||||||
|
"输入要求:",
|
||||||
|
input_radio,
|
||||||
|
"描述:",
|
||||||
|
description_editor,
|
||||||
|
"工作流目的:",
|
||||||
|
purpose_editor,
|
||||||
|
"实现步骤:",
|
||||||
|
steps_editor
|
||||||
|
])
|
||||||
|
|
||||||
|
form.render()
|
||||||
|
|
||||||
|
# 获取用户编辑后的值
|
||||||
|
command_name = command_name_editor.new_text
|
||||||
|
description = description_editor.new_text
|
||||||
|
input_required = input_radio.selection == 0
|
||||||
|
purpose = purpose_editor.new_text
|
||||||
|
# implementation_ideas = ideas_editor.new_text
|
||||||
|
workflow_steps = steps_editor.new_text
|
||||||
|
|
||||||
|
|
||||||
|
# 步骤4: 生成工作流实现代码
|
||||||
|
with Step("生成工作流实现代码..."):
|
||||||
|
code = generate_workflow_code(
|
||||||
|
command_name=command_name,
|
||||||
|
description=description,
|
||||||
|
input_requirement="必选" if input_required else "可选",
|
||||||
|
custom_env=False,
|
||||||
|
workflow_steps=workflow_steps,
|
||||||
|
contexts=CONTEXTS
|
||||||
|
)
|
||||||
|
|
||||||
|
code = code.strip()
|
||||||
|
start_index = code.find("```python")
|
||||||
|
end_index = code.rfind("```")
|
||||||
|
code = code[start_index + len("```python"):end_index].strip()
|
||||||
|
# code = parse_markdown_block(code, block_type="python")
|
||||||
|
|
||||||
|
# 解析命令路径
|
||||||
|
command_dir, final_command = parse_command_path(command_name)
|
||||||
|
full_command_dir = os.path.join(command_dir, final_command)
|
||||||
|
|
||||||
|
# 步骤5: 创建工作流文件
|
||||||
|
with Step(f"创建工作流文件到 {full_command_dir}"):
|
||||||
|
create_workflow_files(
|
||||||
|
full_command_dir,
|
||||||
|
command_name,
|
||||||
|
description,
|
||||||
|
input_required,
|
||||||
|
code
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新斜杠命令
|
||||||
|
with Step("更新斜杠命令..."):
|
||||||
|
IDEService().update_slash_commands()
|
||||||
|
|
||||||
|
# 在新窗口中打开工作流目录
|
||||||
|
try:
|
||||||
|
subprocess.run(["code", full_command_dir], check=True)
|
||||||
|
except subprocess.SubprocessError:
|
||||||
|
print(f"无法自动打开编辑器,请手动打开工作流目录: {full_command_dir}")
|
||||||
|
|
||||||
|
print(f"\n✅ 工作流命令 {command_name} 已成功创建!")
|
||||||
|
print(f"命令文件位置: {full_command_dir}")
|
||||||
|
print("你现在可以在DevChat中使用这个命令了。")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
4
merico/chatflow/gen/command.yml
Normal file
4
merico/chatflow/gen/command.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
description: 生成工作流命令,输入命令信息
|
||||||
|
input: required
|
||||||
|
steps:
|
||||||
|
- run: $devchat_python $command_path/command.py "$input"
|
64
merico/chatflow/util/base_functions_guide.md
Normal file
64
merico/chatflow/util/base_functions_guide.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
工作流开发中封装了部分基础函数,方便开发者在工作流中使用。
|
||||||
|
|
||||||
|
**大模型调用**
|
||||||
|
针对大模型调用,封装了几个装饰器函数,分别用于调用大模型生成文本和生成json格式的文本。
|
||||||
|
- chat装饰器
|
||||||
|
用于调用大模型生成文本,并将生成的文本返回给调用者。具体使用示例如下:
|
||||||
|
```python
|
||||||
|
from devchat.llm import chat
|
||||||
|
|
||||||
|
PROMPT = """
|
||||||
|
对以下代码段进行解释:
|
||||||
|
{code}
|
||||||
|
"""
|
||||||
|
@chat(prompt=PROMPT, stream_out=True)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def explain(code):
|
||||||
|
"""
|
||||||
|
call ai to explain selected code
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
ai_explanation = explain(code="def foo(): pass")
|
||||||
|
```
|
||||||
|
调用explain函数时,需要使用参数名称=参数值的方式传递参数,参数名称必须与PROMPT中使用的参数名称一致。
|
||||||
|
|
||||||
|
- chat_json装饰器
|
||||||
|
用于调用大模型生成json对象。具体使用示例如下:
|
||||||
|
```python
|
||||||
|
from devchat.llm import chat_json
|
||||||
|
PROMPT = (
|
||||||
|
"Give me 5 different git branch names, "
|
||||||
|
"mainly hoping to express: {task}, "
|
||||||
|
"Good branch name should looks like: <type>/<main content>,"
|
||||||
|
"the final result is output in JSON format, "
|
||||||
|
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@chat_json(prompt=PROMPT)
|
||||||
|
def generate_branch_name(task):
|
||||||
|
pass
|
||||||
|
|
||||||
|
task = "fix bug"
|
||||||
|
branch_names = generate_branch_name(task=task)
|
||||||
|
print(branch_names["names"])
|
||||||
|
```
|
||||||
|
调用generate_branch_name函数时,需要使用参数名称=参数值的方式传递参数,参数名称必须与PROMPT中使用的参数名称一致。
|
||||||
|
使用chat, chat_json的注意:
|
||||||
|
1. 使用chat_json装饰器时,返回的结果是一个字典对象,在PROMPT中描述这个字典对象的结构时需要使用{{}}来表示{}。因为后续操作中会使用类似f-string的方式替代PROMPT中的参数,所以在PROMPT中使用{}时需要使用{{}}来表示{}。
|
||||||
|
2. 使用这两个装饰器时,PROMPT中不要使用markdown的代码块语法。
|
||||||
|
|
||||||
|
**工作流调用**
|
||||||
|
目的是对已有工作流进行复用,在A工作流中调用B工作流。
|
||||||
|
- workflow_call函数
|
||||||
|
调用指定的工作流,并将指定的参数传递给被调用的工作流。具体使用示例如下:
|
||||||
|
```python
|
||||||
|
from lib.workflow import workflow_call
|
||||||
|
|
||||||
|
ret_code = workflow_call("/helloworld some name")
|
||||||
|
if ret_code == 0:
|
||||||
|
print("workflow call success")
|
||||||
|
else:
|
||||||
|
print("workflow call failed")
|
||||||
|
```
|
93
merico/chatflow/util/contexts.py
Normal file
93
merico/chatflow/util/contexts.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
为workflow命令提供上下文信息
|
||||||
|
|
||||||
|
编写工作流实现代码需要的上下文:
|
||||||
|
1. IDE Service接口定义
|
||||||
|
2. ChatMark接口定义;
|
||||||
|
3. 工作流命令列表;
|
||||||
|
4. 工作流命令组织、实现规范;
|
||||||
|
5. 基础可用的函数信息;
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def load_file_in_user_scripts(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
从用户脚本目录中加载文件内容
|
||||||
|
"""
|
||||||
|
user_path = os.path.expanduser("~/.chat/scripts")
|
||||||
|
file_path = os.path.join(user_path, filename)
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def load_local_file(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
从当前脚本所在目录的相对目录加载文件内容
|
||||||
|
"""
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
file_path = os.path.join(script_dir, filename)
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def load_existing_workflow_defines() -> str:
|
||||||
|
""" 从user scripts目录遍历找到所有command.yml文件,并加载其内容 """
|
||||||
|
merico_path = os.path.expanduser("~/.chat/scripts/merico")
|
||||||
|
community_path = os.path.expanduser("~/.chat/scripts/community")
|
||||||
|
custom_path = os.path.expanduser("~/.chat/scripts/custom")
|
||||||
|
|
||||||
|
root_paths = [merico_path]
|
||||||
|
# 遍历community_path、custom_path下直接子目录,将其添加到root_paths作为下一步的根目录
|
||||||
|
for path in [community_path, custom_path]:
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
if root == path:
|
||||||
|
root_paths.extend([os.path.join(root, d) for d in dirs])
|
||||||
|
break
|
||||||
|
|
||||||
|
wrkflow_defines = []
|
||||||
|
# 遍历所有根目录,对每个根目录进行递归遍历,找到所有command.yml文件,并加载其内容
|
||||||
|
# 将目录名称与command.yml内容拼接成一个字符串,添加到wrkflow_defines列表中
|
||||||
|
# 例如:~/.chat/scripts/merico/github/commit/command.yml被找到,那么拼接的字符串为:
|
||||||
|
# 工作流命令/github.commit的定义:\n<command.yml内容>\n\n
|
||||||
|
for root_path in root_paths:
|
||||||
|
for root, dirs, files in os.walk(root_path):
|
||||||
|
if "command.yml" in files:
|
||||||
|
with open(os.path.join(root, "command.yml"), "r") as f:
|
||||||
|
wrkflow_defines.append(f"工作流命令/{root[len(root_path)+1:].replace(os.sep, '.')}的定义:\n{f.read()}\n\n")
|
||||||
|
return "\n".join(wrkflow_defines)
|
||||||
|
|
||||||
|
|
||||||
|
CONTEXTS = f"""
|
||||||
|
工作流开发需要的上下文信息:
|
||||||
|
|
||||||
|
|
||||||
|
# IDE Service接口定义及使用示例
|
||||||
|
IDE Service用于在工作流命令中访问与IDE相关的数据,以及调用IDE提供的功能。
|
||||||
|
## IDEService接口定义
|
||||||
|
接口定义:
|
||||||
|
{load_file_in_user_scripts("lib/ide_service/service.py")}
|
||||||
|
涉及类型定义:
|
||||||
|
{load_file_in_user_scripts("lib/ide_service/types.py")}
|
||||||
|
|
||||||
|
## IDEService接口示例
|
||||||
|
{load_local_file("ide_service_demo.py")}
|
||||||
|
|
||||||
|
|
||||||
|
# ChatMark接口使用示例
|
||||||
|
ChatMark用于在工作流命令中与用户交互,展示信息,获取用户输入。
|
||||||
|
{load_file_in_user_scripts("lib/chatmark/chatmark_example/main.py")}
|
||||||
|
ChatMark Form组件类似于HTML表单,用于组合多个组件,获取相关设置结果。
|
||||||
|
|
||||||
|
|
||||||
|
# 工作流命令规范
|
||||||
|
{load_local_file("workflow_guide.md")}
|
||||||
|
|
||||||
|
|
||||||
|
# 已有工作流命令定义
|
||||||
|
{load_existing_workflow_defines()}
|
||||||
|
|
||||||
|
|
||||||
|
# 工作流内部函数定义
|
||||||
|
{load_local_file("base_functions_guide.md")}
|
||||||
|
|
||||||
|
"""
|
5
merico/chatflow/util/ide_service_demo.py
Normal file
5
merico/chatflow/util/ide_service_demo.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from lib.ide_service import IDEService
|
||||||
|
|
||||||
|
IDEService().ide_logging(
|
||||||
|
"debug", "Hello IDE Service!"
|
||||||
|
)
|
52
merico/chatflow/util/workflow_guide.md
Normal file
52
merico/chatflow/util/workflow_guide.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
一个工作流对应一个目录,目录可以进行嵌套,每个工作流在UI视图中对应了一个斜杠命令,例如/github.commit。
|
||||||
|
|
||||||
|
github工作流目录结构简化后示意如下:
|
||||||
|
github
|
||||||
|
|-- commit
|
||||||
|
|-- command.yml
|
||||||
|
|-- command.py
|
||||||
|
|
||||||
|
|-- README.md
|
||||||
|
|-- new_pr
|
||||||
|
|-- command.yml
|
||||||
|
|-- command.py
|
||||||
|
......
|
||||||
|
|
||||||
|
"command.yml"文件定义了工作流命令的元信息,例如命令的名称、描述、参数等。
|
||||||
|
"command.py"文件定义了工作流命令的实现逻辑。
|
||||||
|
|
||||||
|
拿一个hello world工作流命令为例,目录结构如下:
|
||||||
|
helloworld
|
||||||
|
|-- command.yml
|
||||||
|
|-- command.py
|
||||||
|
|-- README.md
|
||||||
|
|
||||||
|
"command.yml"文件内容如下:
|
||||||
|
```yaml
|
||||||
|
description: Hello Workflow, use as /helloworld <name>
|
||||||
|
input: required
|
||||||
|
steps:
|
||||||
|
- run: $devchat_python $command_path/command.py "$input"
|
||||||
|
````
|
||||||
|
"command.py"文件内容如下:
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
print(sys.argv[1])
|
||||||
|
```
|
||||||
|
|
||||||
|
使用一个工作流时,在DevChat AI智能问答视图中输入斜杠命令,例如/helloworld <name>,即可执行该工作流命令。/helloworld表示对应系统可用的工作流。<name>表示工作流的输入,是可选的,如果工作流定义中input为required,则必须输入,否则可以不输入。
|
||||||
|
|
||||||
|
工作流命令有重载优先级,merico目录下工作流 < community目录下工作流 < custom目录下工作流。
|
||||||
|
例如,如果merico与community目录下都有github工作流,那么在DevChat AI智能问答视图中输入/github命令时,会优先执行community目录下的github工作流命令。
|
||||||
|
|
||||||
|
在custom目录下,通过config.yml文件定义custom目录下的工作流目录。例如:
|
||||||
|
namespaces:
|
||||||
|
- bobo
|
||||||
|
那么custom/bobo就是一个工作流存储目录,可以在该目录下定义工作流命令。
|
||||||
|
例如:
|
||||||
|
custom/bobo
|
||||||
|
| ---- hello
|
||||||
|
|------ command.yml
|
||||||
|
|------ command.py
|
||||||
|
那么custom/bobo/hello对应的工作流命令就是/hello.
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user