Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
73994ad
Add TIKTOKEN_CACHE_DIR environment variable to utils.py
yangbobo2021 Feb 5, 2024
258cc44
feat: Enhance command parsing with origin path
yangbobo2021 Feb 5, 2024
01a9bc1
Update timeout value in OpenAIChat class
yangbobo2021 Feb 5, 2024
6b507da
Fix time mismatch bug in OpenAIPrompt class
yangbobo2021 Feb 5, 2024
e410ac4
feat: Improve error handling for OpenAI APIError
yangbobo2021 Feb 5, 2024
36db91e
feat: Integrate AI-driven workflow engine execution
yangbobo2021 Feb 5, 2024
79986ae
feat: Add storage condition for AI responses
yangbobo2021 Feb 5, 2024
66522b2
fix: Adapt non-storage response setting handling
yangbobo2021 Feb 5, 2024
f911d7c
Add route command to CLI
yangbobo2021 Feb 5, 2024
00029d4
fix lint error
yangbobo2021 Feb 5, 2024
52025ce
fix lint error
yangbobo2021 Feb 5, 2024
1f4783e
Refactor command_runner.py to use thread output***
yangbobo2021 Feb 5, 2024
0d21053
Commented out test_prompt_no_args test case in test_cli_prompt.py
yangbobo2021 Feb 5, 2024
72b8f33
refactor: Simplify CommandRunner execution flow
yangbobo2021 Feb 6, 2024
e9f022d
refactor: Simplify workflow selection logic
yangbobo2021 Feb 6, 2024
011ff4f
refactor: Abstract common logic to util.py
yangbobo2021 Feb 6, 2024
0aa7e3c
fix: Address linting errors in command and util modules
yangbobo2021 Feb 6, 2024
f7df56e
feat: Improve CLI workflow and error handling
yangbobo2021 Feb 6, 2024
3354bcc
refactor: Ensure non-empty env values in CommandRunner
yangbobo2021 Feb 6, 2024
ce8cc9f
feat: Enhance package installation script for dev env alignment
yangbobo2021 Feb 6, 2024
3375c3f
Refactor code for improved readability and maintainability
yangbobo2021 Feb 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion devchat/_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from .prompt import prompt
from .run import run
from .topic import topic
from .route import route

__all__ = [
'log',
'prompt',
'run',
'topic'
'topic',
'route'
]
4 changes: 4 additions & 0 deletions devchat/_cli/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@


class MissContentInPromptException(Exception):
pass
2 changes: 2 additions & 0 deletions devchat/_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from devchat._cli import prompt
from devchat._cli import run
from devchat._cli import topic
from devchat._cli import route

logger = get_logger(__name__)
click.rich_click.USE_MARKDOWN = True
Expand All @@ -24,3 +25,4 @@ def main():
main.add_command(log)
main.add_command(run)
main.add_command(topic)
main.add_command(route)
60 changes: 18 additions & 42 deletions devchat/_cli/prompt.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import json
import sys
from typing import List, Optional
import rich_click as click
from devchat.assistant import Assistant
from devchat.openai.openai_chat import OpenAIChat, OpenAIChatConfig
from devchat.store import Store
from devchat.utils import parse_files
from devchat._cli.utils import handle_errors, init_dir, get_model_config
from devchat._cli.router import llm_prompt


@click.command()
Expand All @@ -24,10 +20,13 @@
help='Path to a JSON file with functions for the prompt.')
@click.option('-n', '--function-name',
help='Specify the function name when the content is the output of a function.')
@click.option('-ns', '--not-store', is_flag=True, default=False, required=False,
help='Do not save the conversation to the store.')
def prompt(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None,
functions: Optional[str] = None, function_name: Optional[str] = None):
functions: Optional[str] = None, function_name: Optional[str] = None,
not_store: Optional[bool] = False):
"""
This command performs interactions with the specified large language model (LLM)
by sending prompts and receiving responses.
Expand Down Expand Up @@ -61,38 +60,15 @@ def prompt(content: Optional[str], parent: Optional[str], reference: Optional[Li
```

"""
repo_chat_dir, user_chat_dir = init_dir()

with handle_errors():
if content is None:
content = click.get_text_stream('stdin').read()

if content == '':
return

instruct_contents = parse_files(instruct)
context_contents = parse_files(context)

model, config = get_model_config(repo_chat_dir, user_chat_dir, model)

parameters_data = config.dict(exclude_unset=True)
if config_str:
config_data = json.loads(config_str)
parameters_data.update(config_data)
openai_config = OpenAIChatConfig(model=model, **parameters_data)

chat = OpenAIChat(openai_config)
store = Store(repo_chat_dir, chat)

assistant = Assistant(chat, store, config.max_input_tokens)

functions_data = None
if functions is not None:
with open(functions, 'r', encoding="utf-8") as f_file:
functions_data = json.load(f_file)
assistant.make_prompt(content, instruct_contents, context_contents, functions_data,
parent=parent, references=reference,
function_name=function_name)

for response in assistant.iterate_response():
click.echo(response, nl=False)
llm_prompt(
content,
parent,
reference,
instruct,
context,
model,
config_str,
functions,
function_name,
not_store)
sys.exit(0)
68 changes: 68 additions & 0 deletions devchat/_cli/route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import sys
from typing import List, Optional
import rich_click as click
from devchat._cli.router import llm_route


@click.command()
@click.argument('content', required=False)
@click.option('-p', '--parent', help='Input the parent prompt hash to continue the conversation.')
@click.option('-r', '--reference', multiple=True,
help='Input one or more specific previous prompts to include in the current prompt.')
@click.option('-i', '--instruct', multiple=True,
help='Add one or more files to the prompt as instructions.')
@click.option('-c', '--context', multiple=True,
help='Add one or more files to the prompt as a context.')
@click.option('-m', '--model', help='Specify the model to use for the prompt.')
@click.option('--config', 'config_str',
help='Specify a JSON string to overwrite the default configuration for this prompt.')
@click.option('-a', '--auto', is_flag=True, default=False, required=False,
help='Answer question by function-calling.')
def route(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None,
auto: Optional[bool] = False):
"""
This command performs interactions with the specified large language model (LLM)
by sending prompts and receiving responses.

Examples
--------

To send a multi-line message to the LLM, use the here-doc syntax:

```bash
devchat prompt << 'EOF'
What is the capital of France?
Can you tell me more about its history?
EOF
```

Note the quotes around EOF in the first line, to prevent the shell from expanding variables.

Configuration
-------------

DevChat CLI reads configuration from `~/.chat/config.yml`
(if `~/.chat` is not accessible, it will try `.chat` in your current Git or SVN root directory).
You can edit the file to modify default configuration.

To use OpenAI's APIs, you have to set an API key by the environment variable `OPENAI_API_KEY`.
Run the following command line with your API key:

```bash
export OPENAI_API_KEY="sk-..."
```

"""
llm_route(
content,
parent,
reference,
instruct,
context,
model,
config_str,
auto
)
sys.exit(0)
140 changes: 140 additions & 0 deletions devchat/_cli/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import json
import sys
from typing import List, Optional
import rich_click as click
from devchat.engine import run_command, load_workflow_instruction
from devchat.assistant import Assistant
from devchat.openai.openai_chat import OpenAIChat, OpenAIChatConfig
from devchat.store import Store
from devchat.utils import parse_files
from devchat._cli.utils import handle_errors, init_dir, get_model_config
from devchat._cli.errors import MissContentInPromptException


def _get_model_and_config(
model: Optional[str],
config_str: Optional[str]):
repo_chat_dir, user_chat_dir = init_dir()
model, config = get_model_config(repo_chat_dir, user_chat_dir, model)

parameters_data = config.dict(exclude_unset=True)
if config_str:
config_data = json.loads(config_str)
parameters_data.update(config_data)
return model, parameters_data

def _load_tool_functions(functions: Optional[str]):
try:
if functions:
with open(functions, 'r', encoding="utf-8") as f_file:
return json.load(f_file)
return None
except Exception:
return None

def _load_instruction_contents(content: str, instruct: Optional[List[str]]):
instruct_contents = parse_files(instruct)
command_instructions = load_workflow_instruction(content)
if command_instructions is not None:
instruct_contents.extend(command_instructions)

return instruct_contents


def before_prompt(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None,
functions: Optional[str] = None, function_name: Optional[str] = None,
not_store: Optional[bool] = False):
repo_chat_dir, _1 = init_dir()

if content is None:
content = click.get_text_stream('stdin').read()

if content == '':
raise MissContentInPromptException()

instruct_contents = _load_instruction_contents(content, instruct)
context_contents = parse_files(context)
tool_functions = _load_tool_functions(functions)

model, parameters_data = _get_model_and_config(model, config_str)
max_input_tokens = parameters_data.get("max_input_tokens", 4000)

openai_config = OpenAIChatConfig(model=model, **parameters_data)
chat = OpenAIChat(openai_config)
chat_store = Store(repo_chat_dir, chat)

assistant = Assistant(chat, chat_store, max_input_tokens, not not_store)
assistant.make_prompt(
request = content,
instruct_contents = instruct_contents,
context_contents = context_contents,
functions = tool_functions,
parent=parent,
references=reference,
function_name=function_name
)

return model, assistant, content

def llm_prompt(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None,
functions: Optional[str] = None, function_name: Optional[str] = None,
not_store: Optional[bool] = False):
with handle_errors():
_1, assistant, _3, = before_prompt(
content, parent, reference, instruct, context,
model, config_str, functions, function_name, not_store
)

click.echo(assistant.prompt.formatted_header())
for response in assistant.iterate_response():
click.echo(response, nl=False)


def llm_commmand(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None):
with handle_errors():
model, assistant, content = before_prompt(
content, parent, reference, instruct, context, model, config_str, None, None, True
)

click.echo(assistant.prompt.formatted_header())
command_result = run_command(
model_name = model,
history_messages = assistant.prompt.messages,
input_text = content,
parent_hash = parent,
auto_fun = False)
if command_result is not None:
sys.exit(0)

click.echo("run command fail.")
click.echo(command_result)
sys.exit(-1)


def llm_route(content: Optional[str], parent: Optional[str], reference: Optional[List[str]],
instruct: Optional[List[str]], context: Optional[List[str]],
model: Optional[str], config_str: Optional[str] = None,
auto: Optional[bool] = False):
with handle_errors():
model, assistant, content = before_prompt(
content, parent, reference, instruct, context, model, config_str, None, None, True
)

click.echo(assistant.prompt.formatted_header())
command_result = run_command(
model_name = model,
history_messages = assistant.prompt.messages,
input_text = content,
parent_hash = parent,
auto_fun = auto)
if command_result is not None:
sys.exit(command_result[0])

for response in assistant.iterate_response():
click.echo(response, nl=False)
Loading