XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/gpt4free

Add g4f cli client

eff8624e
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

4 个文件 +272 -25
Modified g4f/__main__.py +1 -1
@@ -1,6 +1,6 @@
1 1 from __future__ import annotations
2 2
3 from .cli import get_api_parser, run_api_args
3 from .cli.__main__ import get_api_parser, run_api_args
4 4
5 5 parser = get_api_parser()
6 6 args = parser.parse_args()
Renamed g4f/cli/__main__.py +4 -0
@@ -2,6 +2,7 @@ from __future__ import annotations
2 2
3 3 import argparse
4 4 from argparse import ArgumentParser
5 from .client import get_parser, run_client_args
5 6
6 7 from g4f import Provider
7 8 from g4f.gui.run import gui_parser, run_gui_args
@@ -41,12 +42,15 @@ def main():
41 42 subparsers = parser.add_subparsers(dest="mode", help="Mode to run the g4f in.")
42 43 subparsers.add_parser("api", parents=[get_api_parser()], add_help=False)
43 44 subparsers.add_parser("gui", parents=[gui_parser()], add_help=False)
45 subparsers.add_parser("client", parents=[get_parser()], add_help=False)
44 46
45 47 args = parser.parse_args()
46 48 if args.mode == "api":
47 49 run_api_args(args)
48 50 elif args.mode == "gui":
49 51 run_gui_args(args)
52 elif args.mode == "client":
53 run_client_args(args)
50 54 else:
51 55 parser.print_help()
52 56 exit(1)
Added g4f/cli/client.py +222 -0
@@ -0,0 +1,222 @@
1 #!/usr/bin/env python3
2
3 import os
4 import sys
5 import asyncio
6 import json
7 import argparse
8 from pathlib import Path
9 from typing import Optional, List, Dict
10 from g4f.client import AsyncClient
11 from g4f.providers.response import JsonConversation
12 from g4f.cookies import set_cookies_dir, read_cookie_files
13 from g4f.Provider import ProviderUtils
14 from g4f import debug
15
16 # Platform-appropriate directories
17 def get_config_dir() -> Path:
18 """Get platform-appropriate config directory."""
19 if sys.platform == "win32":
20 return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
21 elif sys.platform == "darwin":
22 return Path.home() / "Library" / "Application Support"
23 else: # Linux and other UNIX-like
24 return Path.home() / ".config"
25
26 CONFIG_DIR = get_config_dir() / "g4f-cli"
27 COOKIES_DIR = CONFIG_DIR / "cookies"
28 CONVERSATION_FILE = CONFIG_DIR / "conversation.json"
29
30 class ConversationManager:
31 """Manages conversation history and state."""
32
33 def __init__(self, file_path: Path, model: Optional[str] = None, provider: Optional[str] = None) -> None:
34 self.file_path = file_path
35 self.model: Optional[str] = model
36 self.provider: Optional[str] = provider
37 self.conversation = None
38 self.history: List[Dict[str, str]] = []
39 self._load()
40
41 def _load(self) -> None:
42 """Load conversation from file."""
43 if not self.file_path.exists():
44 return
45
46 try:
47 with open(self.file_path, 'r', encoding='utf-8') as f:
48 data = json.load(f)
49 self.model = data.get("model")
50 self.provider = data.get("provider") if self.provider is None else self.provider
51 self.data = data.get("data", {})
52 if self.provider and data.get(self.provider):
53 self.conversation = JsonConversation(**data.get(self.provider))
54 self.history = data.get("history", [])
55 except (json.JSONDecodeError, KeyError) as e:
56 print(f"Error loading conversation: {e}", file=sys.stderr)
57 except Exception as e:
58 print(f"Unexpected error loading conversation: {e}", file=sys.stderr)
59
60 def save(self) -> None:
61 """Save conversation to file."""
62 try:
63 with open(self.file_path, 'w', encoding='utf-8') as f:
64 if self.conversation and self.provider:
65 self.data[self.provider] = self.conversation.get_dict()
66 else:
67 self.data = self.conversation.get_dict() if self.conversation else {}
68 json.dump({
69 "model": self.model,
70 "provider": self.provider,
71 "data": self.data,
72 "history": self.history
73 }, f, indent=2, ensure_ascii=False)
74 except Exception as e:
75 print(f"Error saving conversation: {e}", file=sys.stderr)
76
77 def add_message(self, role: str, content: str) -> None:
78 """Add a message to the conversation."""
79 self.history.append({"role": role, "content": content})
80
81 def get_messages(self) -> List[Dict[str, str]]:
82 """Get all messages in the conversation."""
83 return self.history
84
85 async def stream_response(
86 client: AsyncClient,
87 input_text: str,
88 conversation: ConversationManager
89 ) -> None:
90 """Stream the response from the API and update conversation."""
91 input_text = input_text.strip()
92 if not input_text:
93 raise ValueError("Input text cannot be empty")
94
95 # Add user message to conversation
96 conversation.add_message("user", input_text)
97
98 create_args = {
99 "messages": conversation.get_messages(),
100 "stream": True
101 }
102
103 if conversation.model:
104 create_args["model"] = conversation.model
105
106 response_content = []
107 last_chunk = None
108 async for chunk in client.chat.completions.create(**create_args):
109 last_chunk = chunk
110 token = chunk.choices[0].delta.content
111 if not token:
112 continue
113 token = str(token)
114 response_content.append(token)
115 try:
116 for byte in token.encode('utf-8'):
117 sys.stdout.buffer.write(bytes([byte]))
118 sys.stdout.buffer.flush()
119 except (IOError, BrokenPipeError) as e:
120 print(f"\nError writing to stdout: {e}", file=sys.stderr)
121 break
122
123 conversation.conversation = getattr(last_chunk, 'conversation', None)
124
125 if response_content:
126 # Add assistant message to conversation
127 full_response = "".join(response_content).strip()
128 conversation.add_message("assistant", full_response)
129 else:
130 raise RuntimeError("No response received from the API")
131
132 def get_parser():
133 """Parse command line arguments."""
134 parser = argparse.ArgumentParser(
135 description="GPT CLI client with conversation history",
136 formatter_class=argparse.ArgumentDefaultsHelpFormatter
137 )
138 parser.add_argument("--debug", "-d", action="store_true", help="Enable verbose logging.")
139 parser.add_argument(
140 '-p', '--provider',
141 default=None,
142 help=f"Provider to use. Available: {', '.join([key for key, provider in ProviderUtils.convert.items() if provider.working])}."
143 )
144 parser.add_argument(
145 '-m', '--model',
146 help="Model to use (provider-specific)"
147 )
148 parser.add_argument(
149 '-c', '--cookies-dir',
150 type=Path,
151 default=COOKIES_DIR,
152 help="Directory containing cookies for authenticated providers"
153 )
154 parser.add_argument(
155 '--conversation-file',
156 type=Path,
157 default=CONVERSATION_FILE,
158 help="File to store/load conversation state"
159 )
160 parser.add_argument(
161 '--clear-history',
162 action='store_true',
163 help="Clear conversation history before starting"
164 )
165 parser.add_argument(
166 'input',
167 nargs='?',
168 help="Input text (or read from stdin)"
169 )
170
171 return parser
172
173 async def run_args(input_text: str, args):
174 try:
175 # Ensure directories exist
176 CONFIG_DIR.mkdir(parents=True, exist_ok=True)
177 COOKIES_DIR.mkdir(parents=True, exist_ok=True)
178
179 if args.debug:
180 debug.logging = True
181
182 # Initialize conversation manager
183 conversation = ConversationManager(args.conversation_file, args.model, args.provider)
184 if args.clear_history:
185 conversation.history = []
186 conversation.conversation = None
187
188 # Set cookies directory if specified
189 set_cookies_dir(str(args.cookies_dir))
190 read_cookie_files()
191
192 # Initialize client with selected provider
193 client = AsyncClient(provider=conversation.provider)
194
195 # Stream response and update conversation
196 await stream_response(client, input_text, conversation)
197
198 # Save conversation state
199 conversation.save()
200
201 print() # Ensure final newline
202
203 except Exception as e:
204 print(f"\nError: {e}", file=sys.stderr)
205 sys.exit(1)
206
207 def run_client_args(args):
208 input_text = args.input
209 if not input_text:
210 input_text = sys.stdin.read().strip()
211 if not input_text:
212 print("No input provided. Use -h for help.", file=sys.stderr)
213 sys.exit(1)
214 if os.path.isfile(input_text):
215 with open(input_text, 'r', encoding='utf-8') as f:
216 input_text = f.read().strip()
217 # Run the client with provided arguments
218 asyncio.run(run_args(input_text, args))
219
220 if __name__ == "__main__":
221 # Run the client with command line arguments
222 run_client_args(get_parser().parse_args())
Modified setup.py +45 -24
@@ -104,7 +104,7 @@ setup(
104 104 long_description=long_description,
105 105 packages=find_packages(),
106 106 package_data={
107 'g4f': ['g4f/inference/*', 'g4f/gui/client/*', 'g4f/gui/server/*', 'g4f/Provider/npm/*', 'g4f/local/models/*']
107 'g4f': []
108 108 },
109 109 include_package_data=True,
110 110 install_requires=INSTALL_REQUIRE,
@@ -118,29 +118,50 @@ setup(
118 118 'Bug Tracker': 'https://github.com/xtekky/gpt4free/issues', # Link to issue tracker
119 119 },
120 120 keywords=[
121 'python',
122 'chatbot',
123 'reverse-engineering',
124 'openai',
125 'chatbots',
126 'gpt',
127 'language-model',
128 'gpt-3',
129 'gpt3',
130 'openai-api',
131 'gpt-4',
132 'gpt4',
133 'chatgpt',
134 'chatgpt-api',
135 'openai-chatgpt',
136 'chatgpt-free',
137 'chatgpt-4',
138 'chatgpt4',
139 'chatgpt4-api',
140 'free',
141 'free-gpt',
142 'gpt4free',
143 'g4f',
121 "gpt4free",
122 "gpt4free.js",
123 "g4f",
124 "g4f.dev",
125 "javascript",
126 "npm",
127 "browser",
128 "gpt",
129 "chatgpt",
130 "deepseek",
131 "openai",
132 "ai",
133 "client",
134 "sdk",
135 "free",
136 "ai",
137 "gpt-4",
138 "gpt-4o",
139 "chat",
140 "api",
141 "browser",
142 "ai",
143 "ai",
144 "js",
145 "client",
146 "text",
147 "generation",
148 "image",
149 "generation",
150 "in-browser",
151 "ai",
152 "frontend",
153 "ai",
154 "openai",
155 "alternative",
156 "javascript",
157 "ai",
158 "library",
159 "nodejs",
160 "prompt",
161 "engineering",
162 "chatbot",
163 "ai",
164 "integration"
144 165 ],
145 166 classifiers=[
146 167 'Development Status :: 2 - Pre-Alpha',