返回提交历史
Added
SKILL.md
+48
-0
Modified
etc/unittest/__main__.py
+3
-3
Modified
etc/unittest/client.py
+8
-4
Modified
etc/unittest/mocks.py
+8
-0
Modified
g4f/__init__.py
+0
-1
Modified
g4f/providers/base_provider.py
+9
-3
Modified
g4f/providers/retry_provider.py
+3
-6
Modified
g4f/providers/tool_support.py
+4
-4
Modified
g4f/tools/run_tools.py
+3
-5
XFEstudio/gpt4free
Refactor provider method handling for improved clarity and consistency; update mocks and add SKILL.md for usage guidance
cc0ad7a9
代码差异
9 个文件
+86
-26
@@ -0,0 +1,48 @@
1
# SKILL.md
2
3
## Using gpt4free as an LLM Server for Bots (Clawbot/OpenClaw)
4
5
### Overview
6
This skill covers running gpt4free as a local LLM server with an OpenAI-compatible REST API, custom model routing (config.yaml), and integration with bots like Clawbot or OpenClaw.
7
8
### Best Practices
9
- Start the API server with: `python -m g4f --port 8080` (or use `g4f api --debug --port 8080`)
10
- Use the `/v1` endpoint for OpenAI-compatible requests (e.g., POST to `http://localhost:8080/v1/chat/completions`)
11
- Define custom model routes in `config.yaml` to aggregate/fallback across providers
12
- Place `config.yaml` in your cookies directory (e.g., `~/.g4f/cookies/config.yaml`)
13
- For Clawbot/OpenClaw, patch their config to point to your gpt4free server (see `patch-openclaw.py`)
14
- Test with: `g4f client "Hello" --model openclaw` or Python client
15
16
### Common Pitfalls
17
- Not starting the server before connecting bots
18
- Incorrect config.yaml path or syntax errors
19
- Missing required Python dependencies (install with `pip install -r requirements.txt`)
20
- Not exposing the correct port (default 8080)
21
- Forgetting to patch bot configs to use your local endpoint
22
23
### Workflow Steps
24
1. Install and set up gpt4free (see README)
25
2. Start the API server: `python -m g4f --port 8080`
26
3. (Optional) Create or edit `config.yaml` for custom model routing:
27
```yaml
28
models:
29
- name: "openclaw"
30
providers:
31
- provider: "GeminiCLI"
32
model: "gemini-3-flash-preview"
33
condition: "quota.models.gemini-3-flash-preview.remainingFraction > 0 and error_count < 3"
34
- provider: "Antigravity"
35
model: "gemini-3-flash"
36
- provider: "PollinationsAI"
37
model: "openai"
38
```
39
4. Patch your bot config (e.g., OpenClaw) to use `http://localhost:8080/v1` as the base URL (see `scripts/patch-openclaw.py`)
40
5. Start your bot and verify it connects to gpt4free
41
6. Monitor logs and test with the Python client or CLI
42
43
### References
44
- [README.md](../README.md)
45
- [docs/config-yaml-routing.md](../docs/config-yaml-routing.md)
46
- [scripts/patch-openclaw.py](../scripts/patch-openclaw.py)
47
- [scripts/setup-openclaw.sh](../scripts/setup-openclaw.sh)
48
- [g4f/client/__init__.py](../g4f/client/__init__.py)
@@ -15,8 +15,8 @@ from .retry_provider import *
15
15
from .thinking import *
16
16
from .web_search import *
17
17
from .models import *
18
from .mcp import *
19
from .tool_support_provider import *
20
from .config_provider import *
18
#from .mcp import *
19
#from .tool_support_provider import *
20
#from .config_provider import *
21
21
22
22
unittest.main()
@@ -5,6 +5,7 @@ import unittest
5
5
from g4f.errors import ModelNotFoundError
6
6
from g4f.client import Client, AsyncClient, ChatCompletion, ChatCompletionChunk
7
7
from g4f.client.service import get_model_and_provider
8
from g4f.providers.types import BaseProvider
8
9
from g4f.Provider.Copilot import Copilot
9
10
from g4f.models import gpt_4o
10
11
from .mocks import AsyncGeneratorProviderMock, ModelProviderMock, YieldProviderMock
@@ -117,25 +118,28 @@ class TestPassModel(unittest.TestCase):
117
118
def test_best_provider(self):
118
119
not_default_model = "gpt-4o"
119
120
model, provider = get_model_and_provider(not_default_model, None, False)
120
self.assertTrue(hasattr(provider, "create_completion"))
121
self.assertIsInstance(model, str)
122
self.assertIsInstance(provider, (type, BaseProvider))
121
123
self.assertEqual(model, not_default_model)
122
124
123
125
def test_default_model(self):
124
126
default_model = ""
125
127
model, provider = get_model_and_provider(default_model, None, False)
126
self.assertTrue(hasattr(provider, "create_completion"))
128
self.assertIsInstance(model, str)
129
self.assertIsInstance(provider, (type, BaseProvider))
127
130
self.assertEqual(model, default_model)
128
131
129
132
def test_provider_as_model(self):
130
133
provider_as_model = Copilot.__name__
131
134
model, provider = get_model_and_provider(provider_as_model, None, False)
132
self.assertTrue(hasattr(provider, "create_completion"))
133
135
self.assertIsInstance(model, str)
136
self.assertIsInstance(provider, (type, BaseProvider))
134
137
self.assertEqual(model, Copilot.default_model)
135
138
136
139
def test_get_model(self):
137
140
model, provider = get_model_and_provider(gpt_4o.name, None, False)
138
self.assertTrue(hasattr(provider, "create_completion"))
141
self.assertIsInstance(model, str)
142
self.assertIsInstance(provider, (type, BaseProvider))
139
143
self.assertEqual(model, gpt_4o.name)
140
144
141
145
if __name__ == '__main__':
@@ -4,6 +4,7 @@ from g4f.errors import MissingAuthError
4
4
5
5
class ProviderMock(AbstractProvider):
6
6
working = True
7
use_stream_timeout = False
7
8
8
9
@classmethod
9
10
def create_completion(
@@ -13,6 +14,7 @@ class ProviderMock(AbstractProvider):
13
14
14
15
class AsyncProviderMock(AsyncProvider):
15
16
working = True
17
use_stream_timeout = False
16
18
17
19
@classmethod
18
20
async def create_async(
@@ -22,6 +24,7 @@ class AsyncProviderMock(AsyncProvider):
22
24
23
25
class AsyncGeneratorProviderMock(AsyncGeneratorProvider):
24
26
working = True
27
use_stream_timeout = False
25
28
26
29
@classmethod
27
30
async def create_async_generator(
@@ -29,8 +32,10 @@ class AsyncGeneratorProviderMock(AsyncGeneratorProvider):
29
32
):
30
33
yield "Mock"
31
34
35
32
36
class ModelProviderMock(AbstractProvider):
33
37
working = True
38
use_stream_timeout = False # Added to fix unittest error
34
39
35
40
@classmethod
36
41
def create_completion(
@@ -40,6 +45,7 @@ class ModelProviderMock(AbstractProvider):
40
45
41
46
class YieldProviderMock(AsyncGeneratorProvider):
42
47
working = True
48
use_stream_timeout = False
43
49
44
50
@classmethod
45
51
async def create_async_generator(
@@ -50,6 +56,7 @@ class YieldProviderMock(AsyncGeneratorProvider):
50
56
51
57
class YieldImageResponseProviderMock(AsyncGeneratorProvider):
52
58
working = True
59
use_stream_timeout = False
53
60
54
61
@classmethod
55
62
async def create_async_generator(
@@ -58,6 +65,7 @@ class YieldImageResponseProviderMock(AsyncGeneratorProvider):
58
65
yield ImageResponse(prompt, "")
59
66
60
67
class MissingAuthProviderMock(AbstractProvider):
68
use_stream_timeout = False
61
69
working = True
62
70
63
71
@classmethod
@@ -73,7 +73,6 @@ class ChatCompletion:
73
73
)
74
74
method = get_provider_method(provider)
75
75
result = method(model, messages, stream=stream, **kwargs)
76
result = to_sync_generator(result)
77
76
return result if stream or ignore_stream else concat_chunks(result)
78
77
79
78
@staticmethod
@@ -89,7 +89,9 @@ def get_async_provider_method(provider: type) -> Optional[callable]:
89
89
if hasattr(provider, "create_async_generator"):
90
90
return provider.create_async_generator
91
91
if hasattr(provider, "create_async"):
92
return provider.create_async
92
async def wrapper(*args, **kwargs):
93
yield await provider.create_async(*args, **kwargs)
94
return wrapper
93
95
if hasattr(provider, "create_completion"):
94
96
async def wrapper(*args, **kwargs):
95
97
for chunk in provider.create_completion(*args, **kwargs):
@@ -102,9 +104,13 @@ def get_provider_method(provider: type) -> Optional[callable]:
102
104
if hasattr(provider, "create_completion"):
103
105
return provider.create_completion
104
106
if hasattr(provider, "create_async_generator"):
105
return provider.create_async_generator
107
def wrapper(*args, **kwargs):
108
return to_sync_generator(provider.create_async_generator(*args, **kwargs), stream=provider.supports_stream)
109
return wrapper
106
110
if hasattr(provider, "create_async"):
107
return provider.create_async
111
def wrapper(*args, **kwargs):
112
yield asyncio.run(provider.create_async(*args, **kwargs))
113
return wrapper
108
114
raise NotImplementedError(f"{provider.__name__} does not implement a create method")
109
115
110
116
class AbstractProvider(BaseProvider):
@@ -109,7 +109,7 @@ class RotatedProvider(BaseRetryProvider):
109
109
method = get_async_provider_method(provider)
110
110
response = method(model=alias, messages=messages, **extra_body)
111
111
started = False
112
async for chunk in to_async_iterator(response):
112
async for chunk in response:
113
113
if isinstance(chunk, JsonConversation):
114
114
if conversation is None: conversation = JsonConversation()
115
115
setattr(conversation, provider.__name__, chunk.get_dict())
@@ -168,7 +168,7 @@ class IterListProvider(BaseRetryProvider):
168
168
try:
169
169
method = get_async_provider_method(provider)
170
170
response = method(model=alias, messages=messages, **extra_body)
171
async for chunk in to_async_iterator(response):
171
async for chunk in response:
172
172
if isinstance(chunk, JsonConversation):
173
173
if conversation is None:
174
174
conversation = JsonConversation()
@@ -231,7 +231,7 @@ class RetryProvider(IterListProvider):
231
231
debug.log(f"Using {provider.__name__} provider (attempt {attempt + 1})")
232
232
method = get_async_provider_method(provider)
233
233
response = method(model=model, messages=messages, **kwargs)
234
async for chunk in to_async_iterator(response):
234
async for chunk in response:
235
235
yield chunk
236
236
if is_content(chunk):
237
237
started = True
@@ -255,9 +255,6 @@ def raise_exceptions(exceptions: dict) -> None:
255
255
RetryNoProviderError: If no provider is found.
256
256
"""
257
257
if exceptions:
258
for provider_name, e in exceptions.items():
259
if isinstance(e, (MissingAuthError, NoValidHarFileError)):
260
raise e
261
258
if len(exceptions) == 1:
262
259
raise list(exceptions.values())[0]
263
260
raise RetryProviderError("RetryProvider failed:\n" + "\n".join([
@@ -75,14 +75,14 @@ class ToolSupportProvider(AsyncGeneratorProvider):
75
75
chunks = []
76
76
has_usage = False
77
77
method = get_async_provider_method(provider)
78
async for chunk in to_async_iterator(method(
79
model,
80
messages,
78
async for chunk in method(
79
model=model,
80
messages=messages,
81
81
stream=stream,
82
82
media=media,
83
83
response_format=response_format,
84
84
**kwargs,
85
)):
85
):
86
86
if isinstance(chunk, str):
87
87
chunks.append(chunk)
88
88
elif isinstance(chunk, Usage):
@@ -298,9 +298,7 @@ async def async_iter_run_tools(
298
298
299
299
# Generate response
300
300
method = get_async_provider_method(provider)
301
response = to_async_iterator(
302
method(model=model, messages=messages, **kwargs)
303
)
301
response = method(model=model, messages=messages, **kwargs)
304
302
timeout = kwargs.get("stream_timeout") if provider.use_stream_timeout else kwargs.get("timeout")
305
303
response = wait_for(response, timeout=timeout) if stream else response
306
304
@@ -476,9 +474,9 @@ def iter_run_tools(
476
474
completion_tokens = 0
477
475
usage = None
478
476
method = get_provider_method(provider)
479
for chunk in to_sync_generator(method(
477
for chunk in method(
480
478
model=model, messages=messages, provider=provider, **kwargs
481
)):
479
):
482
480
if isinstance(chunk, FinishReason):
483
481
if sources is not None:
484
482
yield sources