fix: ollama tokenizer limited to 1024 tokens + ollama structured output + fix browser backend

This commit is contained in:
PeriniM 2025-01-12 12:45:49 +01:00
parent 1a019121bf
commit ad693b2bb2
12 changed files with 55 additions and 69 deletions

View File

@ -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,
) )

View File

@ -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))

View File

@ -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"

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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" },
] ]