mirror of
https://github.com/VinciGit00/Scrapegraph-ai.git
synced 2026-07-09 21:19:20 +08:00
fix: ollama tokenizer limited to 1024 tokens + ollama structured output + fix browser backend
This commit is contained in:
parent
1a019121bf
commit
ad693b2bb2
@ -15,7 +15,7 @@ graph_config = {
|
|||||||
"temperature": 0,
|
"temperature": 0,
|
||||||
"format": "json", # Ollama needs the format to be specified explicitly
|
"format": "json", # Ollama needs the format to be specified explicitly
|
||||||
# "base_url": "http://localhost:11434", # set ollama URL arbitrarily
|
# "base_url": "http://localhost:11434", # set ollama URL arbitrarily
|
||||||
"model_tokens": 1024,
|
"model_tokens": 4096,
|
||||||
},
|
},
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
"headless": False,
|
"headless": False,
|
||||||
@ -25,7 +25,7 @@ graph_config = {
|
|||||||
# Create the SmartScraperGraph instance and run it
|
# Create the SmartScraperGraph instance and run it
|
||||||
# ************************************************
|
# ************************************************
|
||||||
smart_scraper_graph = SmartScraperGraph(
|
smart_scraper_graph = SmartScraperGraph(
|
||||||
prompt="Find some information about what does the company do, the name and a contact email.",
|
prompt="Find some information about what does the company do and the list of founders.",
|
||||||
source="https://scrapegraphai.com/",
|
source="https://scrapegraphai.com/",
|
||||||
config=graph_config,
|
config=graph_config,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
"""
|
"""
|
||||||
Basic example of scraping pipeline using SmartScraper with schema
|
Basic example of scraping pipeline using SmartScraper with schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from typing import List
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from scrapegraphai.graphs import SmartScraperGraph
|
from scrapegraphai.graphs import SmartScraperGraph
|
||||||
from scrapegraphai.utils import prettify_exec_info
|
from scrapegraphai.utils import prettify_exec_info
|
||||||
|
|
||||||
|
|
||||||
# ************************************************
|
# ************************************************
|
||||||
# Define the configuration for the graph
|
# Define the configuration for the graph
|
||||||
# ************************************************
|
# ************************************************
|
||||||
@ -14,18 +17,15 @@ class Project(BaseModel):
|
|||||||
title: str = Field(description="The title of the project")
|
title: str = Field(description="The title of the project")
|
||||||
description: str = Field(description="The description of the project")
|
description: str = Field(description="The description of the project")
|
||||||
|
|
||||||
|
|
||||||
class Projects(BaseModel):
|
class Projects(BaseModel):
|
||||||
projects: List[Project]
|
projects: list[Project]
|
||||||
|
|
||||||
|
|
||||||
graph_config = {
|
graph_config = {
|
||||||
"llm": {
|
"llm": {"model": "ollama/llama3.2", "temperature": 0, "model_tokens": 4096},
|
||||||
"model": "ollama/llama3.1",
|
|
||||||
"temperature": 0,
|
|
||||||
"format": "json", # Ollama needs the format to be specified explicitly
|
|
||||||
# "base_url": "http://localhost:11434", # set ollama URL arbitrarily
|
|
||||||
},
|
|
||||||
"verbose": True,
|
"verbose": True,
|
||||||
"headless": False
|
"headless": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ************************************************
|
# ************************************************
|
||||||
@ -36,8 +36,15 @@ smart_scraper_graph = SmartScraperGraph(
|
|||||||
prompt="List me all the projects with their description",
|
prompt="List me all the projects with their description",
|
||||||
source="https://perinim.github.io/projects/",
|
source="https://perinim.github.io/projects/",
|
||||||
schema=Projects,
|
schema=Projects,
|
||||||
config=graph_config
|
config=graph_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = smart_scraper_graph.run()
|
result = smart_scraper_graph.run()
|
||||||
print(json.dumps(result, indent=4))
|
print(json.dumps(result, indent=4))
|
||||||
|
|
||||||
|
# ************************************************
|
||||||
|
# Get graph execution info
|
||||||
|
# ************************************************
|
||||||
|
|
||||||
|
graph_exec_info = smart_scraper_graph.get_execution_info()
|
||||||
|
print(prettify_exec_info(graph_exec_info))
|
||||||
|
|||||||
@ -30,8 +30,7 @@ dependencies = [
|
|||||||
"googlesearch-python>=1.2.5",
|
"googlesearch-python>=1.2.5",
|
||||||
"async-timeout>=4.0.3",
|
"async-timeout>=4.0.3",
|
||||||
"simpleeval>=1.0.0",
|
"simpleeval>=1.0.0",
|
||||||
"jsonschema>=4.23.0",
|
"jsonschema>=4.23.0"
|
||||||
"transformers>=4.46.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@ -61,7 +61,6 @@ class ChromiumLoader(BaseLoader):
|
|||||||
|
|
||||||
dynamic_import(backend, message)
|
dynamic_import(backend, message)
|
||||||
|
|
||||||
self.backend = backend
|
|
||||||
self.browser_config = kwargs
|
self.browser_config = kwargs
|
||||||
self.headless = headless
|
self.headless = headless
|
||||||
self.proxy = parse_or_search_proxy(proxy) if proxy else None
|
self.proxy = parse_or_search_proxy(proxy) if proxy else None
|
||||||
@ -69,7 +68,8 @@ class ChromiumLoader(BaseLoader):
|
|||||||
self.load_state = load_state
|
self.load_state = load_state
|
||||||
self.requires_js_support = requires_js_support
|
self.requires_js_support = requires_js_support
|
||||||
self.storage_state = storage_state
|
self.storage_state = storage_state
|
||||||
self.browser_name = browser_name
|
self.backend = kwargs.get("backend", backend)
|
||||||
|
self.browser_name = kwargs.get("browser_name", browser_name)
|
||||||
self.retry_limit = kwargs.get("retry_limit", retry_limit)
|
self.retry_limit = kwargs.get("retry_limit", retry_limit)
|
||||||
self.timeout = kwargs.get("timeout", timeout)
|
self.timeout = kwargs.get("timeout", timeout)
|
||||||
|
|
||||||
|
|||||||
@ -203,8 +203,9 @@ class AbstractGraph(ABC):
|
|||||||
]
|
]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print(
|
print(
|
||||||
f"""Model {llm_params['model_provider']}/{llm_params['model']} not found,
|
f"""Max input tokens for model {llm_params['model_provider']}/{llm_params['model']} not found,
|
||||||
using default token size (8192)"""
|
please specify the model_tokens parameter in the llm section of the graph configuration.
|
||||||
|
Using default token size: 8192"""
|
||||||
)
|
)
|
||||||
self.model_token = 8192
|
self.model_token = 8192
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from langchain_aws import ChatBedrock
|
|||||||
from langchain_community.chat_models import ChatOllama
|
from langchain_community.chat_models import ChatOllama
|
||||||
from langchain_core.output_parsers import JsonOutputParser
|
from langchain_core.output_parsers import JsonOutputParser
|
||||||
from langchain_core.runnables import RunnableParallel
|
from langchain_core.runnables import RunnableParallel
|
||||||
from langchain_openai import AzureChatOpenAI, ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from requests.exceptions import Timeout
|
from requests.exceptions import Timeout
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
@ -59,7 +59,10 @@ class GenerateAnswerNode(BaseNode):
|
|||||||
self.llm_model = node_config["llm_model"]
|
self.llm_model = node_config["llm_model"]
|
||||||
|
|
||||||
if isinstance(node_config["llm_model"], ChatOllama):
|
if isinstance(node_config["llm_model"], ChatOllama):
|
||||||
self.llm_model.format = "json"
|
if node_config.get("schema", None) is None:
|
||||||
|
self.llm_model.format = "json"
|
||||||
|
else:
|
||||||
|
self.llm_model.format = self.node_config["schema"].model_json_schema()
|
||||||
|
|
||||||
self.verbose = node_config.get("verbose", False)
|
self.verbose = node_config.get("verbose", False)
|
||||||
self.force = node_config.get("force", False)
|
self.force = node_config.get("force", False)
|
||||||
@ -123,8 +126,7 @@ class GenerateAnswerNode(BaseNode):
|
|||||||
format_instructions = ""
|
format_instructions = ""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isinstance(self.llm_model, (ChatOpenAI, AzureChatOpenAI))
|
not self.script_creator
|
||||||
and not self.script_creator
|
|
||||||
or self.force
|
or self.force
|
||||||
and not self.script_creator
|
and not self.script_creator
|
||||||
or self.is_md_scraper
|
or self.is_md_scraper
|
||||||
|
|||||||
@ -6,10 +6,11 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from langchain.prompts import PromptTemplate
|
from langchain.prompts import PromptTemplate
|
||||||
from langchain_aws import ChatBedrock
|
from langchain_aws import ChatBedrock
|
||||||
|
from langchain_community.chat_models import ChatOllama
|
||||||
from langchain_core.output_parsers import JsonOutputParser
|
from langchain_core.output_parsers import JsonOutputParser
|
||||||
from langchain_core.runnables import RunnableParallel
|
from langchain_core.runnables import RunnableParallel
|
||||||
from langchain_mistralai import ChatMistralAI
|
from langchain_mistralai import ChatMistralAI
|
||||||
from langchain_openai import AzureChatOpenAI, ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from ..prompts import (
|
from ..prompts import (
|
||||||
@ -55,6 +56,13 @@ class GenerateAnswerNodeKLevel(BaseNode):
|
|||||||
super().__init__(node_name, "node", input, output, 2, node_config)
|
super().__init__(node_name, "node", input, output, 2, node_config)
|
||||||
|
|
||||||
self.llm_model = node_config["llm_model"]
|
self.llm_model = node_config["llm_model"]
|
||||||
|
|
||||||
|
if isinstance(node_config["llm_model"], ChatOllama):
|
||||||
|
if node_config.get("schema", None) is None:
|
||||||
|
self.llm_model.format = "json"
|
||||||
|
else:
|
||||||
|
self.llm_model.format = self.node_config["schema"].model_json_schema()
|
||||||
|
|
||||||
self.embedder_model = node_config.get("embedder_model", None)
|
self.embedder_model = node_config.get("embedder_model", None)
|
||||||
self.verbose = node_config.get("verbose", False)
|
self.verbose = node_config.get("verbose", False)
|
||||||
self.force = node_config.get("force", False)
|
self.force = node_config.get("force", False)
|
||||||
@ -92,8 +100,7 @@ class GenerateAnswerNodeKLevel(BaseNode):
|
|||||||
format_instructions = ""
|
format_instructions = ""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isinstance(self.llm_model, (ChatOpenAI, AzureChatOpenAI))
|
not self.script_creator
|
||||||
and not self.script_creator
|
|
||||||
or self.force
|
or self.force
|
||||||
and not self.script_creator
|
and not self.script_creator
|
||||||
or self.is_md_scraper
|
or self.is_md_scraper
|
||||||
|
|||||||
@ -96,7 +96,6 @@ class ParseNode(BaseNode):
|
|||||||
chunks = split_text_into_chunks(
|
chunks = split_text_into_chunks(
|
||||||
text=docs_transformed.page_content,
|
text=docs_transformed.page_content,
|
||||||
chunk_size=self.chunk_size - 250,
|
chunk_size=self.chunk_size - 250,
|
||||||
model=self.llm_model,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
docs_transformed = docs_transformed[0]
|
docs_transformed = docs_transformed[0]
|
||||||
@ -115,11 +114,10 @@ class ParseNode(BaseNode):
|
|||||||
chunks = split_text_into_chunks(
|
chunks = split_text_into_chunks(
|
||||||
text=docs_transformed.page_content,
|
text=docs_transformed.page_content,
|
||||||
chunk_size=chunk_size,
|
chunk_size=chunk_size,
|
||||||
model=self.llm_model,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
chunks = split_text_into_chunks(
|
chunks = split_text_into_chunks(
|
||||||
text=docs_transformed, chunk_size=chunk_size, model=self.llm_model
|
text=docs_transformed, chunk_size=chunk_size
|
||||||
)
|
)
|
||||||
|
|
||||||
state.update({self.output[0]: chunks})
|
state.update({self.output[0]: chunks})
|
||||||
|
|||||||
@ -4,14 +4,10 @@ split_text_into_chunks module
|
|||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from langchain_core.language_models.chat_models import BaseChatModel
|
|
||||||
|
|
||||||
from .tokenizer import num_tokens_calculus
|
from .tokenizer import num_tokens_calculus
|
||||||
|
|
||||||
|
|
||||||
def split_text_into_chunks(
|
def split_text_into_chunks(text: str, chunk_size: int, use_semchunk=True) -> List[str]:
|
||||||
text: str, chunk_size: int, model: BaseChatModel, use_semchunk=True
|
|
||||||
) -> List[str]:
|
|
||||||
"""
|
"""
|
||||||
Splits the text into chunks based on the number of tokens.
|
Splits the text into chunks based on the number of tokens.
|
||||||
|
|
||||||
@ -27,9 +23,9 @@ def split_text_into_chunks(
|
|||||||
from semchunk import chunk
|
from semchunk import chunk
|
||||||
|
|
||||||
def count_tokens(text):
|
def count_tokens(text):
|
||||||
return num_tokens_calculus(text, model)
|
return num_tokens_calculus(text)
|
||||||
|
|
||||||
chunk_size = min(chunk_size - 500, int(chunk_size * 0.9))
|
chunk_size = min(chunk_size, int(chunk_size * 0.9))
|
||||||
|
|
||||||
chunks = chunk(
|
chunks = chunk(
|
||||||
text=text, chunk_size=chunk_size, token_counter=count_tokens, memoize=False
|
text=text, chunk_size=chunk_size, token_counter=count_tokens, memoize=False
|
||||||
@ -37,7 +33,7 @@ def split_text_into_chunks(
|
|||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
else:
|
else:
|
||||||
tokens = num_tokens_calculus(text, model)
|
tokens = num_tokens_calculus(text)
|
||||||
|
|
||||||
if tokens <= chunk_size:
|
if tokens <= chunk_size:
|
||||||
return [text]
|
return [text]
|
||||||
@ -48,7 +44,7 @@ def split_text_into_chunks(
|
|||||||
|
|
||||||
words = text.split()
|
words = text.split()
|
||||||
for word in words:
|
for word in words:
|
||||||
word_tokens = num_tokens_calculus(word, model)
|
word_tokens = num_tokens_calculus(word)
|
||||||
if current_length + word_tokens > chunk_size:
|
if current_length + word_tokens > chunk_size:
|
||||||
chunks.append(" ".join(current_chunk))
|
chunks.append(" ".join(current_chunk))
|
||||||
current_chunk = [word]
|
current_chunk = [word]
|
||||||
|
|||||||
@ -2,35 +2,15 @@
|
|||||||
Module for counting tokens and splitting text into chunks
|
Module for counting tokens and splitting text into chunks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from langchain_core.language_models.chat_models import BaseChatModel
|
from .tokenizers.tokenizer_openai import num_tokens_openai
|
||||||
from langchain_mistralai import ChatMistralAI
|
|
||||||
from langchain_ollama import ChatOllama
|
|
||||||
from langchain_openai import ChatOpenAI
|
|
||||||
|
|
||||||
|
|
||||||
def num_tokens_calculus(string: str, llm_model: BaseChatModel) -> int:
|
def num_tokens_calculus(string: str) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the number of tokens in a text string.
|
Returns the number of tokens in a text string.
|
||||||
"""
|
"""
|
||||||
if isinstance(llm_model, ChatOpenAI):
|
|
||||||
from .tokenizers.tokenizer_openai import num_tokens_openai
|
|
||||||
|
|
||||||
num_tokens_fn = num_tokens_openai
|
num_tokens_fn = num_tokens_openai
|
||||||
|
|
||||||
elif isinstance(llm_model, ChatMistralAI):
|
num_tokens = num_tokens_fn(string)
|
||||||
from .tokenizers.tokenizer_mistral import num_tokens_mistral
|
|
||||||
|
|
||||||
num_tokens_fn = num_tokens_mistral
|
|
||||||
|
|
||||||
elif isinstance(llm_model, ChatOllama):
|
|
||||||
from .tokenizers.tokenizer_ollama import num_tokens_ollama
|
|
||||||
|
|
||||||
num_tokens_fn = num_tokens_ollama
|
|
||||||
|
|
||||||
else:
|
|
||||||
from .tokenizers.tokenizer_openai import num_tokens_openai
|
|
||||||
|
|
||||||
num_tokens_fn = num_tokens_openai
|
|
||||||
|
|
||||||
num_tokens = num_tokens_fn(string, llm_model)
|
|
||||||
return num_tokens
|
return num_tokens
|
||||||
|
|||||||
@ -3,19 +3,17 @@ Tokenization utilities for OpenAI models
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import tiktoken
|
import tiktoken
|
||||||
from langchain_core.language_models.chat_models import BaseChatModel
|
|
||||||
|
|
||||||
from ..logging import get_logger
|
from ..logging import get_logger
|
||||||
|
|
||||||
|
|
||||||
def num_tokens_openai(text: str, llm_model: BaseChatModel) -> int:
|
def num_tokens_openai(text: str) -> int:
|
||||||
"""
|
"""
|
||||||
Estimate the number of tokens in a given text using OpenAI's tokenization method,
|
Estimate the number of tokens in a given text using OpenAI's tokenization method,
|
||||||
adjusted for different OpenAI models.
|
adjusted for different OpenAI models.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): The text to be tokenized and counted.
|
text (str): The text to be tokenized and counted.
|
||||||
llm_model (BaseChatModel): The specific OpenAI model to adjust tokenization.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: The number of tokens in the text.
|
int: The number of tokens in the text.
|
||||||
@ -25,7 +23,7 @@ def num_tokens_openai(text: str, llm_model: BaseChatModel) -> int:
|
|||||||
|
|
||||||
logger.debug(f"Counting tokens for text of {len(text)} characters")
|
logger.debug(f"Counting tokens for text of {len(text)} characters")
|
||||||
|
|
||||||
encoding = tiktoken.encoding_for_model("gpt-4")
|
encoding = tiktoken.encoding_for_model("gpt-4o")
|
||||||
|
|
||||||
num_tokens = len(encoding.encode(text))
|
num_tokens = len(encoding.encode(text))
|
||||||
return num_tokens
|
return num_tokens
|
||||||
|
|||||||
4
uv.lock
4
uv.lock
@ -3429,7 +3429,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scrapegraphai"
|
name = "scrapegraphai"
|
||||||
version = "1.35.0b2"
|
version = "1.35.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "async-timeout", version = "4.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
{ name = "async-timeout", version = "4.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||||
@ -3452,7 +3452,6 @@ dependencies = [
|
|||||||
{ name = "simpleeval" },
|
{ name = "simpleeval" },
|
||||||
{ name = "tiktoken" },
|
{ name = "tiktoken" },
|
||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
{ name = "transformers" },
|
|
||||||
{ name = "undetected-playwright" },
|
{ name = "undetected-playwright" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3516,7 +3515,6 @@ requires-dist = [
|
|||||||
{ name = "surya-ocr", marker = "extra == 'ocr'", specifier = ">=0.5.0" },
|
{ name = "surya-ocr", marker = "extra == 'ocr'", specifier = ">=0.5.0" },
|
||||||
{ name = "tiktoken", specifier = ">=0.7" },
|
{ name = "tiktoken", specifier = ">=0.7" },
|
||||||
{ name = "tqdm", specifier = ">=4.66.4" },
|
{ name = "tqdm", specifier = ">=4.66.4" },
|
||||||
{ name = "transformers", specifier = ">=4.46.3" },
|
|
||||||
{ name = "undetected-playwright", specifier = ">=0.3.0" },
|
{ name = "undetected-playwright", specifier = ">=0.3.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user