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

XFEstudio/gpt4free

Add ToolSupportProvider

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

代码差异

11 个文件 +172 -21
Modified docs/pydantic_ai.md +67 -0
@@ -129,6 +129,73 @@ This example demonstrates the use of a custom Pydantic model (`MyModel`) to capt
129 129
130 130 ---
131 131
132 ### Support for Models/Providers without Tool Call Suport
133
134 For models/providers that do not fully support tool calls or lack a direct API for structured output, the `ToolSupportProvider` can be used to bridge the gap. This provider ensures that the agent properly formats the response, even when the model itself doesn't have built-in support for structured outputs. It does so by leveraging a tool list and creating a response format when only one tool is used.
135
136 ### Example for Models/Providers without Tool Support (Single Tool Usage)
137
138 ```python
139 from pydantic import BaseModel
140 from pydantic_ai import Agent
141 from pydantic_ai.models import ModelSettings
142 from g4f.integration.pydantic_ai import AIModel
143 from g4f.providers.tool_support import ToolSupportProvider
144
145 from g4f import debug
146 debug.logging = True
147
148 # Define a custom model for structured output (e.g., city and country)
149 class MyModel(BaseModel):
150 city: str
151 country: str
152
153 # Create the agent for a model with tool support (using one tool)
154 agent = Agent(AIModel(
155 "PollinationsAI:gpt-4o", # Specify the provider and model
156 ToolSupportProvider # Use ToolSupportProvider to handle tool-based response formatting
157 ), result_type=MyModel, model_settings=ModelSettings(temperature=0))
158
159 if __name__ == '__main__':
160 # Run the agent with a query to extract information (e.g., city and country)
161 result = agent.run_sync('European city with the bear.')
162 print(result.data) # Structured output of city and country
163 print(result.usage()) # Usage statistics
164 ```
165
166 ### Explanation:
167
168 - **`ToolSupportProvider` as a Bridge:** The `ToolSupportProvider` acts as a bridge between the agent and the model, ensuring that the response is formatted into a structured output, even if the model doesn't have an API that directly supports such formatting.
169
170 - For instance, if the model generates raw text or unstructured data, the `ToolSupportProvider` will convert this into the expected format (like `MyModel`), allowing the agent to process it as structured data.
171
172 - **Model Initialization:** We initialize the agent with the `PollinationsAI:gpt-4o` model, which may not have a built-in API for returning structured outputs. Instead, it relies on the `ToolSupportProvider` to format the output.
173
174 - **Custom Result Model:** We define a custom Pydantic model (`MyModel`) to capture the expected output in a structured way (e.g., `city` and `country` fields). This helps ensure that even when the model doesn't support structured data, the agent can interpret and format it.
175
176 - **Debug Logging:** The `g4f.debug.logging` is enabled to provide detailed logs for troubleshooting and monitoring the agent's execution.
177
178 ### Example Output:
179
180 ```bash
181 city='Berlin'
182 country='Germany'
183 usage={'prompt_tokens': 15, 'completion_tokens': 50}
184 ```
185
186 ### Key Points:
187
188 - **`ToolSupportProvider` Role:** The `ToolSupportProvider` ensures that the agent formats the raw or unstructured response from the model into a structured format, even if the model itself lacks built-in support for structured data.
189
190 - **Single Tool Usage:** The `ToolSupportProvider` is particularly useful when only one tool is used by the model, and it needs to format or transform the model's output into a structured response without additional tools.
191
192 ### Notes:
193
194 - This approach is ideal for models that return unstructured text or data that needs to be transformed into a structured format (e.g., Pydantic models).
195 - The `ToolSupportProvider` bridges the gap between the model's output and the expected structured format, enabling seamless integration into workflows that require structured responses.
196
197 ---
198
132 199 ## LangChain Integration Example
133 200
134 201 For users working with LangChain, here is an example demonstrating how to integrate G4F models into a LangChain environment:
Modified g4f/Provider/PollinationsAI.py +8 -6
@@ -1,6 +1,5 @@
1 1 from __future__ import annotations
2 2
3 import json
4 3 import random
5 4 import requests
6 5 from urllib.parse import quote_plus
@@ -15,6 +14,7 @@ from ..errors import ModelNotFoundError
15 14 from ..requests.raise_for_status import raise_for_status
16 15 from ..requests.aiohttp import get_connector
17 16 from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage
17 from .. import debug
18 18
19 19 DEFAULT_HEADERS = {
20 20 'Accept': '*/*',
@@ -74,9 +74,11 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
74 74 try:
75 75 # Update of image models
76 76 image_response = requests.get("https://image.pollinations.ai/models")
77 image_response.raise_for_status()
78 new_image_models = image_response.json()
79
77 if image_response.ok:
78 new_image_models = image_response.json()
79 else:
80 new_image_models = []
81
80 82 # Combine models without duplicates
81 83 all_image_models = (
82 84 cls.image_models + # Already contains the default
@@ -112,8 +114,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
112 114 cls.text_models = [cls.default_model]
113 115 if not cls.image_models:
114 116 cls.image_models = [cls.default_image_model]
115 raise RuntimeError(f"Failed to fetch models: {e}") from e
116
117 debug.error(f"Failed to fetch models: {e}")
118
117 119 return cls.text_models + cls.image_models
118 120
119 121 @classmethod
Modified g4f/Provider/hf/HuggingFaceAPI.py +2 -2
@@ -61,10 +61,10 @@ class HuggingFaceAPI(OpenaiTemplate):
61 61 images: ImagesType = None,
62 62 **kwargs
63 63 ):
64 if model in cls.model_aliases:
65 model = cls.model_aliases[model]
66 64 if model == llama_models["name"]:
67 65 model = llama_models["text"] if images is None else llama_models["vision"]
66 if model in cls.model_aliases:
67 model = cls.model_aliases[model]
68 68 api_base = f"https://api-inference.huggingface.co/models/{model}/v1"
69 69 pipeline_tag = await cls.get_pipline_tag(model, api_key)
70 70 if pipeline_tag not in ("text-generation", "image-text-to-text"):
Modified g4f/Provider/hf/models.py +1 -0
@@ -20,6 +20,7 @@ fallback_models = text_models + image_models
20 20 model_aliases = {
21 21 ### Chat ###
22 22 "qwen-2.5-72b": "Qwen/Qwen2.5-Coder-32B-Instruct",
23 "llama-3": "meta-llama/Llama-3.3-70B-Instruct",
23 24 "llama-3.3-70b": "meta-llama/Llama-3.3-70B-Instruct",
24 25 "command-r-plus": "CohereForAI/c4ai-command-r-plus-08-2024",
25 26 "deepseek-r1": "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
Modified g4f/Provider/template/OpenaiTemplate.py +0 -1
@@ -145,7 +145,6 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
145 145 elif content_type.startswith("text/event-stream"):
146 146 await raise_for_status(response)
147 147 first = True
148 is_thinking = 0
149 148 async for line in response.iter_lines():
150 149 if line.startswith(b"data: "):
151 150 chunk = line[6:]
Modified g4f/client/__init__.py +6 -4
@@ -275,6 +275,7 @@ class Completions:
275 275
276 276 def create(
277 277 self,
278 *,
278 279 messages: Messages,
279 280 model: str,
280 281 provider: Optional[ProviderType] = None,
@@ -306,8 +307,8 @@ class Completions:
306 307
307 308 response = iter_run_tools(
308 309 provider.get_create_function(),
309 model,
310 messages,
310 model=model,
311 messages=messages,
311 312 stream=stream,
312 313 **filter_none(
313 314 proxy=self.client.proxy if proxy is None else proxy,
@@ -561,6 +562,7 @@ class AsyncCompletions:
561 562
562 563 def create(
563 564 self,
565 *,
564 566 messages: Messages,
565 567 model: str,
566 568 provider: Optional[ProviderType] = None,
@@ -592,8 +594,8 @@ class AsyncCompletions:
592 594
593 595 response = async_iter_run_tools(
594 596 provider,
595 model,
596 messages,
597 model=model,
598 messages=messages,
597 599 stream=stream,
598 600 **filter_none(
599 601 proxy=self.client.proxy if proxy is None else proxy,
Modified g4f/debug.py +4 -4
@@ -1,10 +1,7 @@
1 1 import sys
2 from .providers.types import ProviderType
3 2
4 3 logging: bool = False
5 4 version_check: bool = True
6 last_provider: ProviderType = None
7 last_model: str = None
8 5 version: str = None
9 6 log_handler: callable = print
10 7 logs: list = []
@@ -14,4 +11,7 @@ def log(text, file = None):
14 11 log_handler(text, file=file)
15 12
16 13 def error(error, name: str = None):
17 log(error if isinstance(error, str) else f"{type(error).__name__ if name is None else name}: {error}", file=sys.stderr)
14 log(
15 error if isinstance(error, str) else f"{type(error).__name__ if name is None else name}: {error}",
16 file=sys.stderr
17 )
Modified g4f/gui/client/demo.html +7 -3
@@ -201,7 +201,7 @@
201 201 </head>
202 202 <body>
203 203 <iframe id="background"></iframe>
204 <img id="image-feed" alt="Image Feed">
204 <img id="image-feed" class="hidden" alt="Image Feed">
205 205
206 206 <!-- Gradient Background Circle -->
207 207 <div class="gradient"></div>
@@ -336,14 +336,15 @@
336 336 const images = []
337 337 eventSource.onmessage = (event) => {
338 338 const data = JSON.parse(event.data);
339 if (data.nsfw || !data.nologo || data.width < 1024 || !data.imageURL) {
339 if (data.nsfw || !data.nologo || data.width < 1024 || !data.imageURL || data.isChild) {
340 340 return;
341 341 }
342 342 const lower = data.prompt.toLowerCase();
343 const tags = ["logo", "infographic", "warts","prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
343 const tags = ["nsfw", "timeline", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", " text ", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
344 344 for (i in tags) {
345 345 if (lower.indexOf(tags[i]) != -1) {
346 346 console.log("Skipping image with tag: " + tags[i]);
347 console.debug("Skipping image:", data.imageURL);
347 348 return;
348 349 }
349 350 }
@@ -354,7 +355,10 @@
354 355 }
355 356 setInterval(() => {
356 357 if (images.length > 0) {
358 imageFeed.classList.remove("hidden");
357 359 imageFeed.src = images.shift();
360 } else if(imageFeed) {
361 imageFeed.remove();
358 362 }
359 363 }, 7000);
360 364 })();
Added g4f/integration/__init__.py +0 -0
此文件没有可显示的逐行差异。
Added g4f/providers/tool_support.py +77 -0
@@ -0,0 +1,77 @@
1 from __future__ import annotations
2
3 import json
4
5 from ..typing import AsyncResult, Messages, ImagesType
6 from ..providers.asyncio import to_async_iterator
7 from ..client.service import get_model_and_provider
8 from ..client.helper import filter_json
9 from .base_provider import AsyncGeneratorProvider
10 from .response import ToolCalls, FinishReason
11
12 class ToolSupportProvider(AsyncGeneratorProvider):
13 working = True
14
15 @classmethod
16 async def create_async_generator(
17 cls,
18 model: str,
19 messages: Messages,
20 stream: bool = True,
21 images: ImagesType = None,
22 tools: list[str] = None,
23 response_format: dict = None,
24 **kwargs
25 ) -> AsyncResult:
26 provider = None
27 if ":" in model:
28 provider, model = model.split(":", 1)
29 model, provider = get_model_and_provider(
30 model, provider,
31 stream, logging=False,
32 has_images=images is not None
33 )
34 if response_format is None:
35 response_format = {"type": "json"}
36
37 if tools is not None:
38 if len(tools) > 1:
39 raise ValueError("Only one tool is supported.")
40 tools = tools.pop()
41 lines = ["Respone in JSON format."]
42 properties = tools["function"]["parameters"]["properties"]
43 properties = {key: value["type"] for key, value in properties.items()}
44 lines.append(f"Response format: {json.dumps(properties, indent=2)}")
45 messages = [{"role": "user", "content": "\n".join(lines)}] + messages
46
47 finish = None
48 chunks = []
49 async for chunk in provider.get_async_create_function()(
50 model,
51 messages,
52 stream=stream,
53 images=images,
54 response_format=response_format,
55 **kwargs
56 ):
57 if isinstance(chunk, FinishReason):
58 finish = chunk
59 break
60 elif isinstance(chunk, str):
61 chunks.append(chunk)
62 else:
63 yield chunk
64
65 chunks = "".join(chunks)
66 if tools is not None:
67 yield ToolCalls([{
68 "id": "",
69 "type": "function",
70 "function": {
71 "name": tools["function"]["name"],
72 "arguments": filter_json(chunks)
73 }
74 }])
75 yield chunks
76 if finish is not None:
77 yield finish
Deleted g4f/tools/pydantic_ai.py +0 -1
@@ -1 +0,0 @@
1 from ..integration.pydantic_ai import AIModel, patch_infer_model