diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index d0050c6e..7509f00d 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -23,7 +23,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r dev-requirements.txt - pip install -r requirements.txt + pip freeze - name: Run testing env: CI: true diff --git a/Makefile b/Makefile index edd29d1b..81e26693 100644 --- a/Makefile +++ b/Makefile @@ -4,17 +4,20 @@ GIT_ROOT ?= $(shell git rev-parse --show-toplevel) format: black . + isort . dev-lint: mypy . black . ruff check . --fix + isort . lint: mypy . black . --check ruff check . - + pylint council/. --max-line-length 120 --disable=R,C,I,W1203,W0107 --fail-under=9 + isort . --check-only test: pytest tests diff --git a/council/agent_tests/agent_tests.py b/council/agent_tests/agent_tests.py index ed93cbf0..dd84bc58 100644 --- a/council/agent_tests/agent_tests.py +++ b/council/agent_tests/agent_tests.py @@ -2,16 +2,12 @@ import time from enum import Enum -from typing import List, Dict, Any, Sequence, Optional -import progressbar # type: ignore +from typing import Any, Dict, List, Optional, Sequence +import progressbar # type: ignore from council.agents import Agent +from council.contexts import AgentContext, Budget, ScorerContext from council.scorers import ScorerBase, ScorerException -from council.contexts import ( - AgentContext, - Budget, - ScorerContext, -) class AgentTestCaseOutcome(str, Enum): @@ -22,7 +18,7 @@ class AgentTestCaseOutcome(str, Enum): class ScorerResult: - def __init__(self, scorer: ScorerBase, score: float): + def __init__(self, scorer: ScorerBase, score: float) -> None: self._scorer = scorer self._score = score @@ -41,22 +37,14 @@ def to_dict(self) -> Dict[str, Any]: class AgentTestCaseResult: - _actual: str - _execution_time: float - _outcome: AgentTestCaseOutcome - _prompt: str - _scorers: List[ScorerBase] - _scorer_results: List[ScorerResult] - _error: str - _error_message: str - def __init__(self, prompt: str, scorers: List[ScorerBase]): + def __init__(self, prompt: str, scorers: List[ScorerBase]) -> None: self._actual = "" - self._execution_time = 0 + self._execution_time = 0.0 self._outcome = AgentTestCaseOutcome.Unknown self._prompt = prompt - self._scorers = scorers - self._scorer_results = [] + self._scorers: List[ScorerBase] = scorers + self._scorer_results: List[ScorerResult] = [] self._error = "" self._error_message = "" @@ -84,18 +72,20 @@ def error(self) -> str: def error_message(self) -> str: return self._error_message - def set_success(self, actual: str, execution_time: float, scores: List[float]): + def set_success(self, actual: str, execution_time: float, scores: List[float]) -> None: self.set_result(actual, execution_time, scores, AgentTestCaseOutcome.Success) - def set_error(self, error: Exception, execution_time: float): + def set_error(self, error: Exception, execution_time: float) -> None: self.set_result("", execution_time, [], AgentTestCaseOutcome.Error) self._error = error.__class__.__name__ self._error_message = str(error) - def set_inconclusive(self, execution_time: float): + def set_inconclusive(self, execution_time: float) -> None: self.set_result("", execution_time, [], AgentTestCaseOutcome.Inconclusive) - def set_result(self, actual: str, execution_time: float, scores: List[float], outcome: AgentTestCaseOutcome): + def set_result( + self, actual: str, execution_time: float, scores: List[float], outcome: AgentTestCaseOutcome + ) -> None: self._actual = actual self._execution_time = execution_time self._outcome = outcome @@ -120,7 +110,7 @@ class AgentTestCase: _prompt: str _scorers: List[ScorerBase] - def __init__(self, prompt: str, scorers: List[ScorerBase]): + def __init__(self, prompt: str, scorers: List[ScorerBase]) -> None: self._prompt = prompt self._scorers = scorers @@ -161,8 +151,8 @@ def run(self, agent: Agent) -> AgentTestCaseResult: class AgentTestSuiteResult: _results: List[AgentTestCaseResult] - def __init__(self): - self._results = [] + def __init__(self) -> None: + self._results: List[AgentTestCaseResult] = [] @property def results(self) -> Sequence[AgentTestCaseResult]: diff --git a/council/agents/agent.py b/council/agents/agent.py index 6415fd36..a5a866bf 100644 --- a/council/agents/agent.py +++ b/council/agents/agent.py @@ -11,6 +11,7 @@ from council.filters import BasicFilter, FilterBase from council.runners import new_runner_executor from council.skills import SkillBase + from .agent_result import AgentResult @@ -124,14 +125,12 @@ def execute_plan(self, iteration_context: AgentContext, plan: Sequence[Execution try: for group in self._group_units(plan): fs = [executor.submit(self._execute_unit, iteration_context, unit) for unit in group] - dones, not_dones = futures.wait( - fs, iteration_context.budget.remaining_duration, futures.FIRST_EXCEPTION - ) - + dones, _ = futures.wait(fs, iteration_context.budget.remaining_duration, futures.FIRST_EXCEPTION) # rethrow exception if any [d.result(0) for d in dones] finally: - [f.cancel() for f in fs] + for f in fs: + f.cancel() @staticmethod def _group_units(plan: Sequence[ExecutionUnit]) -> List[List[ExecutionUnit]]: @@ -154,7 +153,7 @@ def _group_units(plan: Sequence[ExecutionUnit]) -> List[List[ExecutionUnit]]: return result @staticmethod - def _execute_unit(iteration_context: AgentContext, unit: ExecutionUnit): + def _execute_unit(iteration_context: AgentContext, unit: ExecutionUnit) -> None: with iteration_context.new_agent_context_for_execution_unit(unit.name) as context: chain = unit.chain context.logger.info(f'message="chain execution started" chain="{chain.name}" execution_unit="{unit.name}"') diff --git a/council/agents/agent_chain.py b/council/agents/agent_chain.py index 05d63576..751e575d 100644 --- a/council/agents/agent_chain.py +++ b/council/agents/agent_chain.py @@ -1,8 +1,9 @@ -from typing import Any, Optional +from typing import Optional from council.chains import ChainBase from council.contexts import AgentContext, ChainContext, ChatMessage, Monitored from council.runners import RunnerExecutor + from .agent import Agent @@ -13,7 +14,7 @@ class AgentChain(ChainBase): _agent: Monitored[Agent] - def __init__(self, name: str, description: str, agent: Agent): + def __init__(self, name: str, description: str, agent: Agent) -> None: """ Initialize a new instance. @@ -29,11 +30,7 @@ def __init__(self, name: str, description: str, agent: Agent): def agent(self) -> Agent: return self._agent.inner - def _execute( - self, - context: ChainContext, - _executor: Optional[RunnerExecutor] = None, - ) -> Any: + def _execute(self, context: ChainContext, _executor: Optional[RunnerExecutor] = None) -> None: result = self.agent.execute(AgentContext.from_chat_history(context.chat_history)) maybe_message = result.try_best_message if maybe_message.is_some(): diff --git a/council/agents/agent_result.py b/council/agents/agent_result.py index f7ade51e..f06c95c8 100644 --- a/council/agents/agent_result.py +++ b/council/agents/agent_result.py @@ -1,7 +1,7 @@ from collections.abc import Sequence from typing import List, Optional -from council.contexts import ScoredChatMessage, ChatMessage +from council.contexts import ChatMessage, ScoredChatMessage from council.utils import Option @@ -12,7 +12,7 @@ class AgentResult: _messages: List[ScoredChatMessage] - def __init__(self, messages: Optional[List[ScoredChatMessage]] = None): + def __init__(self, messages: Optional[List[ScoredChatMessage]] = None) -> None: """ Initialize a new instance. diff --git a/council/chains/chain_base.py b/council/chains/chain_base.py index 389a50fe..b14c40d2 100644 --- a/council/chains/chain_base.py +++ b/council/chains/chain_base.py @@ -14,7 +14,7 @@ class ChainBase(Monitorable, abc.ABC): _description: str _instructions: bool - def __init__(self, name: str, description: str, support_instructions: bool = False): + def __init__(self, name: str, description: str, support_instructions: bool = False) -> None: super().__init__("chain") self._name = name self._description = description @@ -57,15 +57,11 @@ def execute(self, context: ChainContext, executor: Optional[RunnerExecutor] = No self._execute(context, executor) @abc.abstractmethod - def _execute( - self, - context: ChainContext, - executor: Optional[RunnerExecutor] = None, - ) -> None: + def _execute(self, context: ChainContext, executor: Optional[RunnerExecutor] = None) -> None: pass - def __repr__(self): + def __repr__(self) -> str: return f"Chain({self.name}, {self.description})" - def __str__(self): + def __str__(self) -> str: return f"Chain {self.name}, description: {self.description}" diff --git a/council/contexts/_agent_context_store.py b/council/contexts/_agent_context_store.py index c54f9f15..16011b73 100644 --- a/council/contexts/_agent_context_store.py +++ b/council/contexts/_agent_context_store.py @@ -14,7 +14,7 @@ class AgentContextStore: Actual data storage used during the execution of an :class:`council.agents.Agent` """ - def __init__(self, chat_history: ChatHistory): + def __init__(self, chat_history: ChatHistory) -> None: self._cancellation_token = CancellationToken() self._chat_history = chat_history self._iterations: List[AgentIterationContextStore] = [] diff --git a/council/contexts/_agent_iteration_context_store.py b/council/contexts/_agent_iteration_context_store.py index 6524f2bf..c116d401 100644 --- a/council/contexts/_agent_iteration_context_store.py +++ b/council/contexts/_agent_iteration_context_store.py @@ -12,12 +12,9 @@ class AgentIterationContextStore: Data storage for the execution of iteration """ - _chains: Dict[str, MonitoredMessageList] - _evaluator: List[ScoredChatMessage] - - def __init__(self): - self._chains = {} - self._evaluator = [] + def __init__(self) -> None: + self._chains: Dict[str, MonitoredMessageList] = {} + self._evaluator: List[ScoredChatMessage] = [] @property def chains(self) -> Mapping[str, MessageCollection]: diff --git a/council/contexts/_budget.py b/council/contexts/_budget.py index 45592c32..2131b829 100644 --- a/council/contexts/_budget.py +++ b/council/contexts/_budget.py @@ -51,7 +51,7 @@ def unit(self) -> str: def kind(self) -> str: return self._kind - def __str__(self): + def __str__(self) -> str: return f"{self._kind} consumption: {self._value} {self.unit}" def add(self, value: float) -> Consumption: @@ -156,7 +156,7 @@ def add_consumption(self, value: float, unit: str, kind: str): """ self._add_consumption(Consumption(value=value, unit=unit, kind=kind)) - def _add_consumption(self, consumption: Consumption): + def _add_consumption(self, consumption: Consumption) -> None: for limit in self._remaining: if limit.unit == consumption.unit and limit.kind == consumption.kind: limit.subtract_value(consumption.value) @@ -168,7 +168,7 @@ def add_consumptions(self, consumptions: Iterable[Consumption]) -> None: for consumption in consumptions: self._add_consumption(consumption) - def __repr__(self): + def __repr__(self) -> str: return f"Budget({self._duration})" @staticmethod @@ -188,7 +188,7 @@ class InfiniteBudget(Budget): Helper class representing a budget with no duration and no limits """ - def __init__(self): + def __init__(self) -> None: super().__init__(10000) def is_expired(self) -> bool: diff --git a/council/contexts/_cancellation_token.py b/council/contexts/_cancellation_token.py index 0a23c4c3..dc8f0751 100644 --- a/council/contexts/_cancellation_token.py +++ b/council/contexts/_cancellation_token.py @@ -6,7 +6,7 @@ class CancellationToken: A cancellation token which is initially not set. """ - def __init__(self): + def __init__(self) -> None: self._cancelled = False self._lock = Lock() diff --git a/council/contexts/_chain_context.py b/council/contexts/_chain_context.py index fe10ca0d..fd07335b 100644 --- a/council/contexts/_chain_context.py +++ b/council/contexts/_chain_context.py @@ -30,7 +30,7 @@ def __init__( name: str, budget: Budget, messages: Optional[Iterable[ChatMessage]] = None, - ): + ) -> None: super().__init__(store, execution_context, budget) self._name = name self._current_messages = MessageList() diff --git a/council/contexts/_chat_history.py b/council/contexts/_chat_history.py index bdcb883d..4c50d623 100644 --- a/council/contexts/_chat_history.py +++ b/council/contexts/_chat_history.py @@ -1,4 +1,5 @@ from __future__ import annotations + from ._message_list import MessageList diff --git a/council/contexts/_chat_message.py b/council/contexts/_chat_message.py index 7f8547e9..17232458 100644 --- a/council/contexts/_chat_message.py +++ b/council/contexts/_chat_message.py @@ -29,7 +29,7 @@ class ChatMessageKind(str, Enum): Represents a chat message generated by a specific skill or functionality within the chat system. """ - def __str__(self): + def __str__(self) -> str: return f"{self.value}" @@ -214,10 +214,10 @@ def is_from_source(self, source: str) -> bool: """ return self._source == source - def __str__(self): + def __str__(self) -> str: return f"{self.kind}: {self.message}" - def to_string(self, max_length: int = 50): + def to_string(self, max_length: int = 50) -> str: message = self.message[:max_length] + "..." if len(self.message) > max_length else self.message return f"Message of kind {self.kind}: {message}" @@ -239,27 +239,24 @@ class ScoredChatMessage: score: a score reflecting the quality of the message """ - message: ChatMessage - score: float - - def __init__(self, message: ChatMessage, score: float): + def __init__(self, message: ChatMessage, score: float) -> None: self.message = message self.score = score - def __gt__(self, other: "ScoredChatMessage"): + def __gt__(self, other: ScoredChatMessage) -> bool: return self.score > other.score - def __lt__(self, other: "ScoredChatMessage"): + def __lt__(self, other: ScoredChatMessage) -> bool: return self.score < other.score - def __ge__(self, other: "ScoredChatMessage"): + def __ge__(self, other: ScoredChatMessage) -> bool: return self.score >= other.score - def __le__(self, other: "ScoredChatMessage"): + def __le__(self, other: ScoredChatMessage) -> bool: return self.score <= other.score - def __str__(self): + def __str__(self) -> str: return f"{self.score}" - def __repr__(self): + def __repr__(self) -> str: return f"ScoredChatMessage({self.message}, {self.score})" diff --git a/council/contexts/_composite_message_collection.py b/council/contexts/_composite_message_collection.py index 282571d7..f4d7eed0 100644 --- a/council/contexts/_composite_message_collection.py +++ b/council/contexts/_composite_message_collection.py @@ -11,7 +11,7 @@ class CompositeMessageCollection(MessageCollection): _collections: List[MessageCollection] - def __init__(self, collections: List[MessageCollection]): + def __init__(self, collections: List[MessageCollection]) -> None: self._collections = collections @property diff --git a/council/contexts/_context_base.py b/council/contexts/_context_base.py index ff6e68ef..fe2a8ca5 100644 --- a/council/contexts/_context_base.py +++ b/council/contexts/_context_base.py @@ -18,7 +18,7 @@ class ContextBase: The actual data are stored in the underlying `class`:AgentContextStore`. """ - def __init__(self, store: AgentContextStore, execution_context: ExecutionContext, budget: Budget): + def __init__(self, store: AgentContextStore, execution_context: ExecutionContext, budget: Budget) -> None: self._store = store self._execution_context = execution_context self._budget = MonitoredBudget(execution_context.entry, budget) diff --git a/council/contexts/_context_logger.py b/council/contexts/_context_logger.py index 63ed3d33..8540447f 100644 --- a/council/contexts/_context_logger.py +++ b/council/contexts/_context_logger.py @@ -5,7 +5,7 @@ class ContextLogger: - def __init__(self, log_entry: ExecutionLogEntry): + def __init__(self, log_entry: ExecutionLogEntry) -> None: self._log_entry = log_entry def debug(self, message: str, *args: Any) -> None: diff --git a/council/contexts/_execution_context.py b/council/contexts/_execution_context.py index f8c7328f..a688a803 100644 --- a/council/contexts/_execution_context.py +++ b/council/contexts/_execution_context.py @@ -1,9 +1,10 @@ from __future__ import annotations + from typing import Optional -from ._monitorable import Monitorable from ._execution_log import ExecutionLog from ._execution_log_entry import ExecutionLogEntry +from ._monitorable import Monitorable from ._monitored import Monitored diff --git a/council/contexts/_execution_log.py b/council/contexts/_execution_log.py index c723f0c6..34b33a76 100644 --- a/council/contexts/_execution_log.py +++ b/council/contexts/_execution_log.py @@ -1,8 +1,8 @@ import json -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional -from ._monitorable import Monitorable from ._execution_log_entry import ExecutionLogEntry +from ._monitorable import Monitorable class ExecutionLog: @@ -11,8 +11,8 @@ class ExecutionLog: :class:`~council.chains.Chain`, :class:`~council.skills.SkillBase` ...) """ - def __init__(self): - self._entries = [] + def __init__(self) -> None: + self._entries: List[ExecutionLogEntry] = [] def new_entry(self, name: str, node: Optional[Monitorable]) -> ExecutionLogEntry: """ diff --git a/council/contexts/_execution_log_entry.py b/council/contexts/_execution_log_entry.py index b81acbf0..5bb2bdef 100644 --- a/council/contexts/_execution_log_entry.py +++ b/council/contexts/_execution_log_entry.py @@ -1,9 +1,9 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Sequence, Tuple -from ._monitorable import Monitorable from ._budget import Consumption from ._chat_message import ChatMessage +from ._monitorable import Monitorable class ExecutionLogEntry: @@ -11,7 +11,7 @@ class ExecutionLogEntry: represents one entry in the :class:`ExecutionLog` """ - def __init__(self, source: str, node: Optional[Monitorable]): + def __init__(self, source: str, node: Optional[Monitorable]) -> None: self._source = source self._node = node self._start = datetime.now(timezone.utc) @@ -62,7 +62,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._duration = (datetime.now(timezone.utc) - self._start).total_seconds() self._error = exc_val - def __repr__(self): + def __repr__(self) -> str: return ( "ExecutionLogEntry(" f"source={self._source}, start={self._start}, duration={self._duration}, error={self._error}" diff --git a/council/contexts/_llm_context.py b/council/contexts/_llm_context.py index 01485ebb..40ea208b 100644 --- a/council/contexts/_llm_context.py +++ b/council/contexts/_llm_context.py @@ -1,4 +1,5 @@ from __future__ import annotations + from typing import Optional from ._agent_context_store import AgentContextStore diff --git a/council/contexts/_message_collection.py b/council/contexts/_message_collection.py index 9dfd3ce1..278ce15a 100644 --- a/council/contexts/_message_collection.py +++ b/council/contexts/_message_collection.py @@ -4,8 +4,8 @@ from more_itertools import first from typing_extensions import TypeGuard -from ._chat_message import ChatMessage, ChatMessageKind from ..utils import Option +from ._chat_message import ChatMessage, ChatMessageKind class MessageCollection(abc.ABC): @@ -106,7 +106,7 @@ def last_message_from_skill(self, skill_name: str) -> Optional[ChatMessage]: Optional[ChatMessage]: """ - def predicate(message: ChatMessage): + def predicate(message: ChatMessage) -> bool: return message.is_of_kind(ChatMessageKind.Skill) and message.is_from_source(skill_name) return self._last_message_filter(predicate) diff --git a/council/contexts/_message_list.py b/council/contexts/_message_list.py index 56fbe3cb..9b7ee541 100644 --- a/council/contexts/_message_list.py +++ b/council/contexts/_message_list.py @@ -11,7 +11,7 @@ class MessageList(MessageCollection): _messages: List[ChatMessage] = [] - def __init__(self, messages: Optional[Iterable[ChatMessage]] = None): + def __init__(self, messages: Optional[Iterable[ChatMessage]] = None) -> None: """ initialize a new instance """ @@ -28,7 +28,7 @@ def messages(self) -> Iterable[ChatMessage]: def reversed(self) -> Iterable[ChatMessage]: return reversed(self._messages) - def add_user_message(self, message: str, data: Optional[Any] = None): + def add_user_message(self, message: str, data: Optional[Any] = None) -> None: """ adds a user :class:`ChatMessage` into the history @@ -39,7 +39,7 @@ def add_user_message(self, message: str, data: Optional[Any] = None): self._messages.append(ChatMessage.user(message, data)) - def add_agent_message(self, message: str, data: Any = None): + def add_agent_message(self, message: str, data: Any = None) -> None: """ adds an agent class:`ChatMessage` into the history @@ -50,8 +50,11 @@ def add_agent_message(self, message: str, data: Any = None): self._messages.append(ChatMessage.agent(message, data)) - def add_message(self, message: ChatMessage): + def add_message(self, message: ChatMessage) -> None: self._messages.append(message) - def add_messages(self, messages: Iterable[ChatMessage]): + def add_messages(self, messages: Iterable[ChatMessage]) -> None: self._messages.extend(messages) + + def __len__(self): + return len(self._messages) diff --git a/council/contexts/_monitor.py b/council/contexts/_monitor.py index 252016e9..35580f13 100644 --- a/council/contexts/_monitor.py +++ b/council/contexts/_monitor.py @@ -1,18 +1,20 @@ +from __future__ import annotations + from typing import Any, Dict, List, Mapping class Monitor: - _children: Dict[str, "Monitor"] + _children: Dict[str, Monitor] _base_type: str _properties: Dict[str, Any] - def __init__(self, inner: object, base_type: str): + def __init__(self, inner: object, base_type: str) -> None: self._type = inner.__class__.__name__ self._children = {} self._properties = {} self._base_type = base_type - def register_child(self, relation: str, child: "Monitor") -> None: + def register_child(self, relation: str, child: Monitor) -> None: self._children[relation] = child def set(self, name: str, value: Any) -> None: @@ -31,7 +33,7 @@ def name(self, value: str) -> None: self._properties["name"] = value @property - def children(self) -> Mapping[str, "Monitor"]: + def children(self) -> Mapping[str, Monitor]: return self._children @property diff --git a/council/contexts/_monitorable.py b/council/contexts/_monitorable.py index 5235cbf0..1a3d58aa 100644 --- a/council/contexts/_monitorable.py +++ b/council/contexts/_monitorable.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from typing import Any, Dict, Iterable, List, TypeVar @@ -9,7 +11,7 @@ class Monitorable: - def __init__(self, base_type: str): + def __init__(self, base_type: str) -> None: self._monitor = Monitor(self, base_type) @property @@ -26,10 +28,11 @@ def register_monitor(self, monitored: T_monitored) -> T_monitored: def new_monitors(self, name: str, items: Iterable[T]) -> List[Monitored[T]]: result = [Monitored(f"{name}[{index}]", item) for index, item in enumerate(items)] - [self._register_child(item.name, item.inner) for item in result] + for item in result: + self._register_child(item.name, item.inner) return result - def _register_child(self, relation: str, child: "Monitorable"): + def _register_child(self, relation: str, child: Monitorable) -> None: self._monitor.register_child(relation, child._monitor) def render_as_text(self) -> str: diff --git a/council/contexts/_monitored.py b/council/contexts/_monitored.py index 82481e05..7ab3f579 100644 --- a/council/contexts/_monitored.py +++ b/council/contexts/_monitored.py @@ -4,7 +4,7 @@ class Monitored(Generic[T]): - def __init__(self, name: str, monitorable: T): + def __init__(self, name: str, monitorable: T) -> None: self._name = name self._inner = monitorable diff --git a/council/contexts/_monitored_budget.py b/council/contexts/_monitored_budget.py index 54dda1a5..19900024 100644 --- a/council/contexts/_monitored_budget.py +++ b/council/contexts/_monitored_budget.py @@ -5,10 +5,10 @@ class MonitoredBudget(Budget): _log_entry: ExecutionLogEntry - def __init__(self, log_entry: ExecutionLogEntry, budget: Budget): + def __init__(self, log_entry: ExecutionLogEntry, budget: Budget) -> None: super().__init__(budget.remaining_duration, budget._remaining) self._log_entry = log_entry - def _add_consumption(self, consumption: Consumption): + def _add_consumption(self, consumption: Consumption) -> None: self._log_entry.log_consumption(consumption) super()._add_consumption(consumption) diff --git a/council/contexts/_skill_context.py b/council/contexts/_skill_context.py index 7551dce3..89f94912 100644 --- a/council/contexts/_skill_context.py +++ b/council/contexts/_skill_context.py @@ -3,6 +3,7 @@ from typing import Any, Iterable from council.utils import Option + from ._agent_context_store import AgentContextStore from ._budget import Budget from ._chain_context import ChainContext diff --git a/council/controllers/basic_controller.py b/council/controllers/basic_controller.py index 36e303e2..f788f7e6 100644 --- a/council/controllers/basic_controller.py +++ b/council/controllers/basic_controller.py @@ -1,15 +1,16 @@ from typing import List, Sequence from council.contexts import AgentContext + +from ..chains import ChainBase from .controller_base import ControllerBase from .execution_unit import ExecutionUnit -from ..chains import ChainBase class BasicController(ControllerBase): """a basic controller that requests all chains to be executed and returns all results""" - def __init__(self, chains: Sequence[ChainBase], parallelism: bool = False): + def __init__(self, chains: Sequence[ChainBase], parallelism: bool = False) -> None: super().__init__(chains, parallelism) def _execute(self, context: AgentContext) -> List[ExecutionUnit]: diff --git a/council/controllers/controller_base.py b/council/controllers/controller_base.py index 7d5530f0..5ffb8ada 100644 --- a/council/controllers/controller_base.py +++ b/council/controllers/controller_base.py @@ -1,13 +1,14 @@ from abc import ABC, abstractmethod -from typing import List, Sequence, Optional +from typing import List, Optional, Sequence from council.chains import ChainBase from council.contexts import AgentContext, Monitorable + from .execution_unit import ExecutionUnit class ControllerException(Exception): - def __init__(self, message: str): + def __init__(self, message: str) -> None: super().__init__(message) @@ -16,7 +17,7 @@ class ControllerBase(Monitorable, ABC): Abstract base class for an agent controller. """ - def __init__(self, chains: Sequence[ChainBase], parallelism: bool = False): + def __init__(self, chains: Sequence[ChainBase], parallelism: bool = False) -> None: """ Args: chains (List[Chain]): The list of chains available for execution. diff --git a/council/controllers/execution_unit.py b/council/controllers/execution_unit.py index d09a204e..b8d545a3 100644 --- a/council/controllers/execution_unit.py +++ b/council/controllers/execution_unit.py @@ -26,7 +26,7 @@ def __init__( initial_state: Optional[ChatMessage] = None, name: Optional[str] = None, rank: Optional[int] = None, - ): + ) -> None: self._chain = chain self._budget = budget self._initial_state = initial_state diff --git a/council/controllers/llm_controller.py b/council/controllers/llm_controller.py index c0137852..18597d94 100644 --- a/council/controllers/llm_controller.py +++ b/council/controllers/llm_controller.py @@ -1,13 +1,14 @@ from typing import List, Optional, Sequence, Tuple -from typing_extensions import TypeGuard from council.chains import ChainBase from council.contexts import AgentContext, ChatMessage, ContextBase +from council.controllers import ControllerBase, ControllerException from council.llm import LLMBase, LLMMessage, MonitoredLLM +from council.llm.llm_answer import LLMAnswer, LLMParsingException, llm_class_validator, llm_property from council.utils import Option -from council.controllers import ControllerBase, ControllerException +from typing_extensions import TypeGuard + from .execution_unit import ExecutionUnit -from council.llm.llm_answer import llm_property, LLMAnswer, LLMParsingException, llm_class_validator class Specialist: @@ -37,13 +38,13 @@ def justification(self) -> str: """Short and specific explanation of your score to this particular Specialist""" return self._justification - def __str__(self): + def __str__(self) -> str: return ( f"The specialist `{self._name}` was scored `{self._score}` with the justification `{self._justification}`" ) @llm_class_validator - def validate(self): + def validate(self) -> None: if self._score < 0 or self._score > 10: raise LLMParsingException(f"Specialist's score `{self._score}` is invalid, value must be between 0 and 10.") diff --git a/council/evaluators/basic_evaluator.py b/council/evaluators/basic_evaluator.py index e353766a..8f556775 100644 --- a/council/evaluators/basic_evaluator.py +++ b/council/evaluators/basic_evaluator.py @@ -1,10 +1,7 @@ from typing import List -from council.contexts import ( - AgentContext, - ScoredChatMessage, - ChatMessage, -) +from council.contexts import AgentContext, ChatMessage, ScoredChatMessage + from .evaluator_base import EvaluatorBase diff --git a/council/evaluators/evaluator_base.py b/council/evaluators/evaluator_base.py index 7ce782f9..ceeea943 100644 --- a/council/evaluators/evaluator_base.py +++ b/council/evaluators/evaluator_base.py @@ -15,7 +15,7 @@ class EvaluatorBase(Monitorable, ABC): """ - def __init__(self): + def __init__(self) -> None: super().__init__("evaluator") def execute(self, context: AgentContext) -> List[ScoredChatMessage]: diff --git a/council/evaluators/llm_evaluator.py b/council/evaluators/llm_evaluator.py index c9986070..606abae4 100644 --- a/council/evaluators/llm_evaluator.py +++ b/council/evaluators/llm_evaluator.py @@ -6,9 +6,9 @@ from typing import List, Optional -from council.contexts import AgentContext, ChatMessage, ScoredChatMessage, ContextBase +from council.contexts import AgentContext, ChatMessage, ContextBase, ScoredChatMessage from council.evaluators import EvaluatorBase, EvaluatorException -from council.llm import LLMBase, MonitoredLLM, llm_property, LLMAnswer, LLMMessage +from council.llm import LLMAnswer, LLMBase, LLMMessage, MonitoredLLM, llm_property from council.llm.llm_answer import LLMParsingException, llm_class_validator from council.utils import Option @@ -34,11 +34,11 @@ def justification(self) -> str: """Short, helpful and specific explanation your grade""" return self._justification - def __str__(self): + def __str__(self) -> str: return f"Message `{self._index}` graded `{self._grade}` with the justification: `{self._justification}`" @llm_class_validator - def validate(self): + def validate(self) -> None: if self._grade < 0.0 or self._grade > 10.0: raise LLMParsingException(f"Grade `{self._grade}` is invalid, value must be between 0.0 and 10.0") diff --git a/council/filters/basic_filter.py b/council/filters/basic_filter.py index 4dcfa1c1..8aceed09 100644 --- a/council/filters/basic_filter.py +++ b/council/filters/basic_filter.py @@ -9,7 +9,7 @@ class BasicFilter(FilterBase): a basic filter that filters messages based on a score threshold. """ - def __init__(self, score_threshold: Optional[float] = None, top_k: Optional[int] = None): + def __init__(self, score_threshold: Optional[float] = None, top_k: Optional[int] = None) -> None: """ Args: score_threshold: minimum score value for a message to be kept diff --git a/council/filters/filter_base.py b/council/filters/filter_base.py index 02302563..e10b017c 100644 --- a/council/filters/filter_base.py +++ b/council/filters/filter_base.py @@ -5,7 +5,7 @@ class FilterException(Exception): - def __init__(self, message: str): + def __init__(self, message: str) -> None: super().__init__(message) @@ -14,7 +14,7 @@ class FilterBase(Monitorable, ABC): Abstract base class for an agent filter. """ - def __init__(self): + def __init__(self) -> None: super().__init__("filter") def execute(self, context: AgentContext) -> List[ScoredChatMessage]: diff --git a/council/filters/llm_filter.py b/council/filters/llm_filter.py index f9e18685..0ed4e4c8 100644 --- a/council/filters/llm_filter.py +++ b/council/filters/llm_filter.py @@ -6,9 +6,9 @@ from typing import List, Optional -from council.contexts import AgentContext, ScoredChatMessage, ContextBase +from council.contexts import AgentContext, ContextBase, ScoredChatMessage from council.filters import FilterBase, FilterException -from council.llm import LLMBase, MonitoredLLM, llm_property, LLMAnswer, LLMMessage +from council.llm import LLMAnswer, LLMBase, LLMMessage, MonitoredLLM, llm_property from council.llm.llm_answer import LLMParsingException from council.utils import Option @@ -34,7 +34,7 @@ def justification(self) -> str: """Short, helpful and specific explanation your response""" return self._justification - def __str__(self): + def __str__(self) -> str: t = " " if self._filtered else " not " return f"Message {self._index} is{t}filtered with the justification: {self._justification}" diff --git a/council/llm/anthropic.py b/council/llm/anthropic.py index 39f73733..23545aa6 100644 --- a/council/llm/anthropic.py +++ b/council/llm/anthropic.py @@ -1,6 +1,6 @@ import abc from abc import ABC -from typing import Sequence, List +from typing import List, Sequence from council.llm import LLMMessage diff --git a/council/llm/anthropic_completion_llm.py b/council/llm/anthropic_completion_llm.py index ddf06bd0..41356be1 100644 --- a/council/llm/anthropic_completion_llm.py +++ b/council/llm/anthropic_completion_llm.py @@ -1,8 +1,7 @@ -from typing import Sequence, List +from typing import List, Sequence from anthropic import Anthropic from anthropic._types import NOT_GIVEN - from council.llm import AnthropicLLMConfiguration, LLMMessage, LLMMessageRole from council.llm.anthropic import AnthropicAPIClientWrapper diff --git a/council/llm/anthropic_llm.py b/council/llm/anthropic_llm.py index d71b88f1..09a62de8 100644 --- a/council/llm/anthropic_llm.py +++ b/council/llm/anthropic_llm.py @@ -1,23 +1,22 @@ from __future__ import annotations -from typing import Any, Sequence, Optional, List +from typing import Any, List, Optional, Sequence -from anthropic import Anthropic, APITimeoutError, APIStatusError - -from council.contexts import LLMContext, Consumption +from anthropic import Anthropic, APIStatusError, APITimeoutError +from council.contexts import Consumption, LLMContext from council.llm import ( + AnthropicLLMConfiguration, LLMBase, - LLMMessage, - LLMResult, - LLMCallTimeoutException, LLMCallException, - AnthropicLLMConfiguration, - LLMessageTokenCounterBase, + LLMCallTimeoutException, LLMConfigObject, + LLMessageTokenCounterBase, + LLMMessage, LLMProviders, + LLMResult, ) -from .anthropic import AnthropicAPIClientWrapper +from .anthropic import AnthropicAPIClientWrapper from .anthropic_completion_llm import AnthropicCompletionLLM from .anthropic_messages_llm import AnthropicMessagesLLM diff --git a/council/llm/anthropic_llm_configuration.py b/council/llm/anthropic_llm_configuration.py index a5ed6073..fcadcba1 100644 --- a/council/llm/anthropic_llm_configuration.py +++ b/council/llm/anthropic_llm_configuration.py @@ -1,15 +1,15 @@ from __future__ import annotations -from typing import Optional, Any +from typing import Any, Optional from council.llm import LLMConfigSpec -from council.utils import read_env_str, Parameter, read_env_int, greater_than_validator, prefix_validator from council.llm.llm_configuration_base import _DEFAULT_TIMEOUT +from council.utils import Parameter, greater_than_validator, prefix_validator, read_env_int, read_env_str _env_var_prefix = "ANTHROPIC_" -def _tv(x: float): +def _tv(x: float) -> None: """ Temperature and Top_p Validators Sampling temperature to use, between 0. and 1. @@ -23,12 +23,7 @@ class AnthropicLLMConfiguration: Configuration for :class:AnthropicLLM """ - def __init__( - self, - model: str, - api_key: str, - max_tokens: int, - ): + def __init__(self, model: str, api_key: str, max_tokens: int) -> None: """ Initialize a new instance @@ -109,7 +104,7 @@ def max_tokens(self) -> Parameter[int]: """ return self._max_tokens - def _read_optional_env(self): + def _read_optional_env(self) -> None: self._temperature.from_env(_env_var_prefix + "LLM_TEMPERATURE") self._top_p.from_env(_env_var_prefix + "LLM_TOP_P") self._top_k.from_env(_env_var_prefix + "LLM_TOP_K") diff --git a/council/llm/anthropic_messages_llm.py b/council/llm/anthropic_messages_llm.py index d4a05a0b..9a90b7b8 100644 --- a/council/llm/anthropic_messages_llm.py +++ b/council/llm/anthropic_messages_llm.py @@ -1,16 +1,11 @@ from __future__ import annotations -from typing import Sequence, List, Iterable, Literal +from typing import Iterable, List, Literal, Sequence from anthropic import Anthropic from anthropic._types import NOT_GIVEN from anthropic.types import MessageParam - -from council.llm import ( - LLMMessage, - LLMMessageRole, - AnthropicLLMConfiguration, -) +from council.llm import AnthropicLLMConfiguration, LLMMessage, LLMMessageRole from council.llm.anthropic import AnthropicAPIClientWrapper diff --git a/council/llm/azure_llm.py b/council/llm/azure_llm.py index ba6292fd..fb899c05 100644 --- a/council/llm/azure_llm.py +++ b/council/llm/azure_llm.py @@ -1,10 +1,11 @@ from __future__ import annotations + from typing import Any, Optional import httpx -from httpx import TimeoutException, HTTPStatusError +from httpx import HTTPStatusError, TimeoutException -from . import OpenAIChatCompletionsModel, LLMCallTimeoutException, LLMCallException +from . import LLMCallException, LLMCallTimeoutException, OpenAIChatCompletionsModel from .azure_llm_configuration import AzureLLMConfiguration from .llm_config_object import LLMConfigObject, LLMProviders diff --git a/council/llm/azure_llm_configuration.py b/council/llm/azure_llm_configuration.py index dc8e22d6..001b9b2c 100644 --- a/council/llm/azure_llm_configuration.py +++ b/council/llm/azure_llm_configuration.py @@ -1,10 +1,11 @@ from __future__ import annotations + from typing import Optional from council.llm import LLMConfigurationBase from council.llm.llm_config_object import LLMConfigSpec -from council.utils import Parameter, read_env_str, greater_than_validator, not_empty_validator from council.llm.llm_configuration_base import _DEFAULT_TIMEOUT +from council.utils import Parameter, greater_than_validator, not_empty_validator, read_env_str _env_var_prefix = "AZURE_" diff --git a/council/llm/llm_answer.py b/council/llm/llm_answer.py index 160d28bd..7ebda01c 100644 --- a/council/llm/llm_answer.py +++ b/council/llm/llm_answer.py @@ -1,10 +1,9 @@ from __future__ import annotations import inspect -from typing import Any, Dict, List, Optional, Callable +from typing import Any, Callable, Dict, List, Optional import yaml - from council.utils import CodeParser @@ -19,12 +18,12 @@ def __init__(self, fget=None, fset=None, fdel=None, doc=None): class llm_class_validator: - def __init__(self, func: Callable): + def __init__(self, func: Callable) -> None: self.f = func class LLMProperty: - def __init__(self, name: str, prop: llm_property): + def __init__(self, name: str, prop: llm_property) -> None: self._name = name self._type = prop.fget.__annotations__.get("return", str) self._description = prop.__doc__ @@ -38,7 +37,7 @@ def name(self) -> str: def rank(self) -> int: return self._rank - def __str__(self): + def __str__(self) -> str: return f"{self._name}: {{{self._description}, expected response type `{self._type.__name__}`}}" def can_parse(self, value: Any) -> bool: @@ -68,7 +67,7 @@ def converter(x: str) -> bool: class LLMAnswer: - def __init__(self, schema: Any): + def __init__(self, schema: Any) -> None: self._schema = schema self._class_name = schema.__name__ self._valid_func = None diff --git a/council/llm/llm_config_object.py b/council/llm/llm_config_object.py index 455d5e3f..94aff1c8 100644 --- a/council/llm/llm_config_object.py +++ b/council/llm/llm_config_object.py @@ -5,7 +5,6 @@ from typing import Any, Dict, Optional import yaml - from council.utils import DataObject, DataObjectSpecBase from council.utils.parameter import Undefined @@ -75,7 +74,7 @@ def get_value(self, key: str, required: bool = False, default: Optional[Any] = U raise Exception(f"LLMProvider {self.name} - A required key {key} is missing.") return maybe_value - def __str__(self): + def __str__(self) -> str: return f"{self._kind}: {self.name} ({self.description})" @@ -106,7 +105,7 @@ def to_dict(self) -> Dict[str, Any]: result["fallback_provider"] = self.fallback_provider return result - def __str__(self): + def __str__(self) -> str: return f"{self.description}" diff --git a/council/llm/llm_configuration_base.py b/council/llm/llm_configuration_base.py index dccde156..a6cdff8a 100644 --- a/council/llm/llm_configuration_base.py +++ b/council/llm/llm_configuration_base.py @@ -38,7 +38,7 @@ class LLMConfigurationBase(abc.ABC): Configuration for OpenAI LLM Chat Completion GPT Model """ - def __init__(self): + def __init__(self) -> None: self._temperature = Parameter.float(name="temperature", required=False, default=0.0, validator=_tv) self._max_tokens = Parameter.int(name="max_tokens", required=False, validator=_mtv) self._top_p = Parameter.float(name="top_p", required=False) diff --git a/council/llm/openai_chat_completions_llm.py b/council/llm/openai_chat_completions_llm.py index cdc24347..0a8c5fd6 100644 --- a/council/llm/openai_chat_completions_llm.py +++ b/council/llm/openai_chat_completions_llm.py @@ -1,14 +1,14 @@ from __future__ import annotations -import httpx +from typing import Any, List, Optional, Protocol, Sequence -from typing import List, Any, Protocol, Sequence, Optional +import httpx +from council.contexts import Consumption, LLMContext from . import LLMConfigurationBase -from .llm_message import LLMMessage, LLMessageTokenCounterBase -from .llm_exception import LLMCallException from .llm_base import LLMBase, LLMResult -from council.contexts import LLMContext, Consumption +from .llm_exception import LLMCallException +from .llm_message import LLMessageTokenCounterBase, LLMMessage class Provider(Protocol): diff --git a/council/llm/openai_llm.py b/council/llm/openai_llm.py index ed030933..2f5eead9 100644 --- a/council/llm/openai_llm.py +++ b/council/llm/openai_llm.py @@ -1,15 +1,11 @@ from __future__ import annotations + from typing import Any, Optional import httpx -from httpx import TimeoutException, HTTPStatusError +from httpx import HTTPStatusError, TimeoutException -from . import ( - OpenAIChatCompletionsModel, - OpenAITokenCounter, - LLMCallTimeoutException, - LLMCallException, -) +from . import LLMCallException, LLMCallTimeoutException, OpenAIChatCompletionsModel, OpenAITokenCounter from .llm_config_object import LLMConfigObject, LLMProviders from .openai_llm_configuration import OpenAILLMConfiguration diff --git a/council/llm/openai_llm_configuration.py b/council/llm/openai_llm_configuration.py index 42bbbf46..02f24f49 100644 --- a/council/llm/openai_llm_configuration.py +++ b/council/llm/openai_llm_configuration.py @@ -1,16 +1,11 @@ from __future__ import annotations + from typing import Any, Optional from council.llm import LLMConfigurationBase from council.llm.llm_config_object import LLMConfigSpec -from council.utils import ( - read_env_str, - read_env_int, - Parameter, - greater_than_validator, - prefix_validator, -) from council.llm.llm_configuration_base import _DEFAULT_TIMEOUT +from council.utils import Parameter, greater_than_validator, prefix_validator, read_env_int, read_env_str _env_var_prefix = "OPENAI_" diff --git a/council/llm/openai_token_counter.py b/council/llm/openai_token_counter.py index 9a148b3a..d75aad23 100644 --- a/council/llm/openai_token_counter.py +++ b/council/llm/openai_token_counter.py @@ -1,11 +1,12 @@ from __future__ import annotations -import logging -import tiktoken +import logging from typing import List, Optional, Sequence + +import tiktoken from tiktoken import Encoding -from . import LLMMessage, LLMessageTokenCounterBase, LLMTokenLimitException +from . import LLMessageTokenCounterBase, LLMMessage, LLMTokenLimitException logger = logging.getLogger(__name__) diff --git a/council/mocks/__init__.py b/council/mocks/__init__.py index 6970b96d..0ef6311e 100644 --- a/council/mocks/__init__.py +++ b/council/mocks/__init__.py @@ -66,7 +66,7 @@ def __init__(self, name: str = "mock", action: Optional[Callable[[SkillContext], def execute(self, context: SkillContext) -> ChatMessage: return self._action(context) - def empty_message(self, context: SkillContext): + def empty_message(self, context: SkillContext) -> ChatMessage: return self.build_success_message("") def set_action_custom_message(self, message: str) -> None: diff --git a/council/prompt/prompt_builder.py b/council/prompt/prompt_builder.py index 5ce2911d..0c38f408 100644 --- a/council/prompt/prompt_builder.py +++ b/council/prompt/prompt_builder.py @@ -1,9 +1,8 @@ from typing import Any, List, Optional +from council.contexts import ChainContext, ChatMessageKind, ContextBase from jinja2 import Template -from council.contexts import ChatMessageKind, ChainContext, ContextBase - class PromptBuilder: """ diff --git a/council/runners/do_while_runner.py b/council/runners/do_while_runner.py index 167a4787..6cfbfb3b 100644 --- a/council/runners/do_while_runner.py +++ b/council/runners/do_while_runner.py @@ -1,5 +1,5 @@ from council.contexts import ChainContext, ChatMessage -from council.runners import RunnerBase, RunnerPredicate, RunnerExecutor, RunnerPredicateError +from council.runners import RunnerBase, RunnerExecutor, RunnerPredicate, RunnerPredicateError class DoWhile(RunnerBase): diff --git a/council/runners/if_runner.py b/council/runners/if_runner.py index abbb363c..2c297b44 100644 --- a/council/runners/if_runner.py +++ b/council/runners/if_runner.py @@ -1,6 +1,6 @@ from typing import Optional -from council.contexts import ChatMessage, ChainContext +from council.contexts import ChainContext, ChatMessage from .errrors import RunnerPredicateError from .runner_base import RunnerBase @@ -25,11 +25,7 @@ def __init__(self, predicate: RunnerPredicate, runner: RunnerBase, else_runner: self._then = self.new_monitor("then", runner) self._maybe_else = self.new_monitor("else", else_runner) if else_runner is not None else None - def _run( - self, - context: ChainContext, - executor: RunnerExecutor, - ) -> None: + def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: try: result = self._predicate(context) except Exception as e: diff --git a/council/runners/parallel.py b/council/runners/parallel.py index 02b0aaee..ffb851ee 100644 --- a/council/runners/parallel.py +++ b/council/runners/parallel.py @@ -1,4 +1,5 @@ from concurrent import futures + from council.contexts import ChainContext from .runner_base import RunnerBase @@ -10,15 +11,11 @@ class Parallel(RunnerBase): Runner that execution multiple :class:`.RunnerBase` in parallel """ - def __init__(self, *runners: RunnerBase): + def __init__(self, *runners: RunnerBase) -> None: super().__init__("parallelRunner") self._runners = self.new_monitors("parallel", runners) - def _run( - self, - context: ChainContext, - executor: RunnerExecutor, - ) -> None: + def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: contexts = [(runner.inner, context.fork_for(runner)) for runner in self._runners] # Seems like it is a bad idea using lambda as the function in submit, diff --git a/council/runners/parallel_for.py b/council/runners/parallel_for.py index 79fab6b5..ca097339 100644 --- a/council/runners/parallel_for.py +++ b/council/runners/parallel_for.py @@ -1,16 +1,15 @@ from concurrent import futures from typing import Iterable -from more_itertools import batched - -from council.contexts import IterationContext, ChainContext +from council.contexts import ChainContext, IterationContext from council.utils import Option +from more_itertools import batched from .errrors import RunnerGeneratorError from .loop_runner_base import LoopRunnerBase from .runner_executor import RunnerExecutor -from .types import RunnerGenerator from .skill_runner_base import SkillRunnerBase +from .types import RunnerGenerator class ParallelFor(LoopRunnerBase): @@ -30,7 +29,7 @@ class ParallelFor(LoopRunnerBase): the order of results. """ - def __init__(self, generator: RunnerGenerator, skill: SkillRunnerBase, parallelism: int = 5): + def __init__(self, generator: RunnerGenerator, skill: SkillRunnerBase, parallelism: int = 5) -> None: """ Initialize a new instance @@ -58,7 +57,7 @@ def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: [f.cancel() for f in all_fs] context.merge(inner_contexts) - def _run_skill(self, context: ChainContext, iteration: IterationContext): + def _run_skill(self, context: ChainContext, iteration: IterationContext) -> None: index = iteration.index context.logger.debug(f'message="start iteration" index="{index}"') try: diff --git a/council/runners/runner_base.py b/council/runners/runner_base.py index 2ca2246d..a5dedbf7 100644 --- a/council/runners/runner_base.py +++ b/council/runners/runner_base.py @@ -5,6 +5,7 @@ from concurrent import futures from council.contexts import ChainContext, Monitorable, Monitored + from .errrors import RunnerError, RunnerTimeoutError from .runner_executor import RunnerExecutor @@ -17,18 +18,14 @@ def run_from_chain_context(self, context: ChainContext, executor: RunnerExecutor Base runner class that handles common execution logic, including error management and timeout """ - def fork_run_merge(self, runner: Monitored[RunnerBase], context: ChainContext, executor: RunnerExecutor): + def fork_run_merge(self, runner: Monitored[RunnerBase], context: ChainContext, executor: RunnerExecutor) -> None: inner = context.fork_for(runner) try: runner.inner.run(inner, executor) finally: context.merge([inner]) - def run( - self, - context: ChainContext, - executor: RunnerExecutor, - ) -> None: + def run(self, context: ChainContext, executor: RunnerExecutor) -> None: if context.should_stop(): return @@ -52,13 +49,9 @@ def run( context.logger.debug("done running %s", self.__class__.__name__) @staticmethod - def rethrow_if_exception(fs: Set[futures.Future]): + def rethrow_if_exception(fs: Set[futures.Future]) -> None: [f.result(timeout=0) for f in fs] @abc.abstractmethod - def _run( - self, - context: ChainContext, - executor: RunnerExecutor, - ) -> None: + def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: pass diff --git a/council/runners/runner_executor.py b/council/runners/runner_executor.py index f2d7b04e..1d0e4433 100644 --- a/council/runners/runner_executor.py +++ b/council/runners/runner_executor.py @@ -1,6 +1,5 @@ from concurrent import futures - RunnerExecutor = futures.ThreadPoolExecutor diff --git a/council/runners/sequential.py b/council/runners/sequential.py index a139e41b..8bb62d00 100644 --- a/council/runners/sequential.py +++ b/council/runners/sequential.py @@ -1,6 +1,7 @@ from typing import Sequence from council.contexts import ChainContext + from .runner_base import RunnerBase from .runner_executor import RunnerExecutor @@ -10,15 +11,11 @@ class Sequential(RunnerBase): Runner that executes a list of :class:`.RunnerBase` in sequence """ - def __init__(self, *runners: RunnerBase): + def __init__(self, *runners: RunnerBase) -> None: super().__init__("sequenceRunner") self._runners = self.new_monitors("sequence", runners) - def _run( - self, - context: ChainContext, - executor: RunnerExecutor, - ) -> None: + def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: for runner in self._runners: if context.should_stop(): return diff --git a/council/runners/skill_runner_base.py b/council/runners/skill_runner_base.py index b5cfd913..67ed718e 100644 --- a/council/runners/skill_runner_base.py +++ b/council/runners/skill_runner_base.py @@ -1,11 +1,11 @@ import abc -from council.contexts import SkillContext, IterationContext, ChatMessage, ChainContext -from . import RunnerSkillError +from council.contexts import ChainContext, ChatMessage, IterationContext, SkillContext +from ..utils import Option +from . import RunnerSkillError from .runner_base import RunnerBase from .runner_executor import RunnerExecutor -from ..utils import Option class SkillRunnerBase(RunnerBase): diff --git a/council/runners/types.py b/council/runners/types.py index 9fc1bca2..3a1dbfcc 100644 --- a/council/runners/types.py +++ b/council/runners/types.py @@ -1,4 +1,4 @@ -from typing import Callable, Iterable, Any +from typing import Any, Callable, Iterable from council.contexts import ChainContext diff --git a/council/runners/while_runner.py b/council/runners/while_runner.py index e185155b..7144dcd4 100644 --- a/council/runners/while_runner.py +++ b/council/runners/while_runner.py @@ -1,5 +1,5 @@ from council.contexts import ChainContext, ChatMessage -from council.runners import RunnerBase, RunnerPredicate, RunnerExecutor, RunnerPredicateError +from council.runners import RunnerBase, RunnerExecutor, RunnerPredicate, RunnerPredicateError class While(RunnerBase): @@ -17,7 +17,7 @@ def __init__(self, predicate: RunnerPredicate, runner: RunnerBase): self._predicate = predicate self._body = self.new_monitor("whileBody", runner) - def _run(self, context: ChainContext, executor: RunnerExecutor): + def _run(self, context: ChainContext, executor: RunnerExecutor) -> None: while self.check_predicate(context): self._body.inner.run(context, executor) diff --git a/council/scorers/llm_similarity_scorer.py b/council/scorers/llm_similarity_scorer.py index 257c5cfc..9dd4999b 100644 --- a/council/scorers/llm_similarity_scorer.py +++ b/council/scorers/llm_similarity_scorer.py @@ -1,15 +1,16 @@ -from typing import List, Dict, Any, Optional +from typing import Any, Dict, List, Optional + +from council.contexts import ChatMessage, ContextBase, ScorerContext +from council.llm import LLMAnswer, LLMBase, LLMMessage, MonitoredLLM, llm_property -from . import ScorerException -from .scorer_base import ScorerBase -from council.contexts import ChatMessage, ScorerContext, ContextBase -from council.llm import LLMBase, LLMMessage, MonitoredLLM, llm_property, LLMAnswer from ..llm.llm_answer import LLMParsingException, llm_class_validator from ..utils import Option +from . import ScorerException +from .scorer_base import ScorerBase class SimilarityScore: - def __init__(self, score: float, justification: str): + def __init__(self, score: float, justification: str) -> None: self._score = score self._justification = justification @@ -23,11 +24,11 @@ def justification(self) -> str: """Short, helpful and specific explanation your score""" return self._justification - def __str__(self): + def __str__(self) -> str: return f"Similarity score is {self.score} with the justification: {self._justification}" @llm_class_validator - def validate(self): + def validate(self) -> None: if self._score < 0 or self._score > 100: raise LLMParsingException(f"Similarity Score `{self._score}` is invalid, value must be between 0 and 100.") diff --git a/council/scorers/scorer_base.py b/council/scorers/scorer_base.py index d33e4c59..65d57577 100644 --- a/council/scorers/scorer_base.py +++ b/council/scorers/scorer_base.py @@ -2,6 +2,7 @@ from typing import Any, Dict from council.contexts import ChatMessage, Monitorable, ScorerContext + from .scorer_exception import ScorerException @@ -10,7 +11,7 @@ class ScorerBase(Monitorable, abc.ABC): Base class for implementing a Scorer """ - def __init__(self): + def __init__(self) -> None: super().__init__("scorer") def score(self, context: ScorerContext, message: ChatMessage) -> float: diff --git a/council/skills/google/google_context/context_provider.py b/council/skills/google/google_context/context_provider.py index 225f8232..45e04ac4 100644 --- a/council/skills/google/google_context/context_provider.py +++ b/council/skills/google/google_context/context_provider.py @@ -1,5 +1,4 @@ import logging - from abc import ABC, abstractmethod from .schemas import ResponseReference diff --git a/council/skills/google/google_context/google_news.py b/council/skills/google/google_context/google_news.py index 2ccfee66..c3c7dbd8 100644 --- a/council/skills/google/google_context/google_news.py +++ b/council/skills/google/google_context/google_news.py @@ -1,7 +1,6 @@ -from datetime import datetime import logging - -from typing import Optional, List, Any +from datetime import datetime +from typing import Any, List, Optional from GoogleNews import GoogleNews diff --git a/council/skills/google/google_context/google_search.py b/council/skills/google/google_context/google_search.py index b23c7377..6fd442a8 100644 --- a/council/skills/google/google_context/google_search.py +++ b/council/skills/google/google_context/google_search.py @@ -1,15 +1,14 @@ from __future__ import annotations from abc import ABC -from typing import Optional, Any +from typing import Any, Optional -from council.utils import OptionException, read_env_str, MissingEnvVariableException +from council.utils import MissingEnvVariableException, OptionException, read_env_str +from googleapiclient.discovery import build from .context_provider import ContextProvider from .schemas import ResponseReference -from googleapiclient.discovery import build - class GoogleSearchEngine(ContextProvider, ABC): """ diff --git a/council/skills/google/google_context/schemas.py b/council/skills/google/google_context/schemas.py index 30e1de27..06c2bff9 100644 --- a/council/skills/google/google_context/schemas.py +++ b/council/skills/google/google_context/schemas.py @@ -1,4 +1,4 @@ -from typing import Optional, Any, Dict +from typing import Any, Dict, Optional class ResponseReference: @@ -15,8 +15,9 @@ def __init__(self, title: str, url: str, snippet: Optional[str], date: Optional[ self.snippet = snippet self.date = date - def __str__(self): - return "ResponseReference(title=" + str(self.title) + " ,url=" + self.url + ", date=" + self.date + " )" + def __str__(self) -> str: + date = self.date or "Undefined" + return f"ResponseReference(title={self.title} ,url={self.url}, date={date})" def dict(self) -> Dict[str, Any]: return { diff --git a/council/skills/google/google_news_skill.py b/council/skills/google/google_news_skill.py index 429c4689..8828d85a 100644 --- a/council/skills/google/google_news_skill.py +++ b/council/skills/google/google_news_skill.py @@ -1,11 +1,11 @@ -from typing import Optional -from datetime import datetime - import json +from datetime import datetime +from typing import Optional from council.contexts import ChatMessage, SkillContext -from .google_context import GoogleNewsSearchEngine + from .. import SkillBase +from .google_context import GoogleNewsSearchEngine class GoogleNewsSkill(SkillBase): @@ -17,11 +17,11 @@ class GoogleNewsSkill(SkillBase): def __init__( self, suffix: str = "", - nb_results=5, + nb_results: int = 5, period: Optional[str] = "90d", start: Optional[datetime] = None, end: Optional[datetime] = None, - ): + ) -> None: super().__init__("gnews") self.gn = GoogleNewsSearchEngine(period=period, suffix=suffix, start=start, end=end) self.nb_results = nb_results diff --git a/council/skills/google/google_search_skill.py b/council/skills/google/google_search_skill.py index 1add55aa..b11660a7 100644 --- a/council/skills/google/google_search_skill.py +++ b/council/skills/google/google_search_skill.py @@ -1,8 +1,9 @@ import json from council.contexts import ChatMessage, SkillContext -from .google_context import GoogleSearchEngine + from .. import SkillBase +from .google_context import GoogleSearchEngine class GoogleSearchSkill(SkillBase): @@ -15,14 +16,14 @@ class GoogleSearchSkill(SkillBase): """ - def __init__(self, nb_results=5): + def __init__(self, nb_results: int = 5) -> None: super().__init__("gsearch") self.gs = GoogleSearchEngine.from_env() self.nb_results = nb_results def execute(self, context: SkillContext) -> ChatMessage: prompt = context.chat_history.try_last_user_message.unwrap("no user message") - resp = self.gs.execute(query=prompt.message, nb_results=self.nb_results) + resp = self.gs.execute(query=prompt.message, nb_results=self.nb_results) # type: ignore response_count = len(resp) if response_count > 0: return self.build_success_message( diff --git a/council/skills/llm_skill.py b/council/skills/llm_skill.py index 743a9881..5033b17e 100644 --- a/council/skills/llm_skill.py +++ b/council/skills/llm_skill.py @@ -1,6 +1,6 @@ from typing import List, Protocol -from council.contexts import SkillContext, ChatMessage +from council.contexts import ChatMessage, SkillContext from council.llm import LLMBase, LLMMessage, MonitoredLLM from council.prompt import PromptBuilder from council.skills import SkillBase diff --git a/council/skills/python/python_code_execution_skill.py b/council/skills/python/python_code_execution_skill.py index 20c24154..c6a8d719 100644 --- a/council/skills/python/python_code_execution_skill.py +++ b/council/skills/python/python_code_execution_skill.py @@ -20,7 +20,7 @@ class PythonCodeExecutionSkill(SkillBase): The return message data contains a dictionary with the status code, stdout and stderr. """ - def __init__(self, env_var: Optional[Mapping[str, str]] = None, decode_stdout: bool = True): + def __init__(self, env_var: Optional[Mapping[str, str]] = None, decode_stdout: bool = True) -> None: """ Initialize a new instance diff --git a/council/skills/python/python_code_generation_skill.py b/council/skills/python/python_code_generation_skill.py index fcb6eaf9..c5d96cd8 100644 --- a/council/skills/python/python_code_generation_skill.py +++ b/council/skills/python/python_code_generation_skill.py @@ -6,7 +6,6 @@ from council.llm import LLMBase, LLMMessage from council.skills import LLMSkill - instruction = """ # Instructions You are a coding assistant generating python code. @@ -38,7 +37,7 @@ class PythonCodeGenerationSkill(LLMSkill): SKILL_NAME: str = "PythonCodeGenSkill" - def __init__(self, llm: LLMBase, code_template: str = "", additional_instructions: str = ""): + def __init__(self, llm: LLMBase, code_template: str = "", additional_instructions: str = "") -> None: system_prompt = instruction.format(code_template=code_template, additional_instructions=additional_instructions) super().__init__(llm, self.SKILL_NAME, system_prompt, context_messages=self.build_messages) diff --git a/council/skills/python/python_code_verification_skill.py b/council/skills/python/python_code_verification_skill.py index cda8240e..6f9d70aa 100644 --- a/council/skills/python/python_code_verification_skill.py +++ b/council/skills/python/python_code_verification_skill.py @@ -58,7 +58,7 @@ def say_hi() -> str: """ - def __init__(self, code_template: str = ""): + def __init__(self, code_template: str = "") -> None: """ initialize a new instance @@ -113,7 +113,7 @@ def execute(self, context: SkillContext) -> ChatMessage: context.logger.debug(error) return self.build_error_message(error) - def _validate_code(self, code: str): + def _validate_code(self, code: str) -> None: errors = [] code_lines = self.normalize_snippet(code) if self._code_before_line_count > 0: @@ -130,6 +130,6 @@ def _validate_code(self, code: str): raise Exception("\n".join(errors)) @staticmethod - def normalize_code(code): + def normalize_code(code: str) -> str: module = ast.parse(code, type_comments=True) return ast.unparse(module).strip() + "\n" diff --git a/council/skills/skill_base.py b/council/skills/skill_base.py index 513cec7f..34a3bceb 100644 --- a/council/skills/skill_base.py +++ b/council/skills/skill_base.py @@ -1,9 +1,9 @@ from __future__ import annotations -from typing import Any from abc import abstractmethod +from typing import Any -from council.contexts import SkillContext, ChatMessage +from council.contexts import ChatMessage, SkillContext from council.runners import SkillRunnerBase @@ -87,8 +87,8 @@ def execute_skill(self, context: SkillContext) -> ChatMessage: ) return skill_message - def __repr__(self): + def __repr__(self) -> str: return f"SkillBase({self.name})" - def __str__(self): + def __str__(self) -> str: return f"Skill {self.name}" diff --git a/council/skills/wikipedia/wikipedia_client.py b/council/skills/wikipedia/wikipedia_client.py index 0bc50413..30c918a1 100644 --- a/council/skills/wikipedia/wikipedia_client.py +++ b/council/skills/wikipedia/wikipedia_client.py @@ -9,7 +9,7 @@ class WikipediaPageSection: Represents partial content from a Wikipedia Page """ - def __init__(self, title: str, content: str, page_id: int): + def __init__(self, title: str, content: str, page_id: int) -> None: self._title = title self._content = content self._page_id = page_id diff --git a/council/skills/wikipedia/wikipedia_search_skill.py b/council/skills/wikipedia/wikipedia_search_skill.py index 367bb13d..ddcc36e1 100644 --- a/council/skills/wikipedia/wikipedia_search_skill.py +++ b/council/skills/wikipedia/wikipedia_search_skill.py @@ -1,7 +1,7 @@ import yaml - from council import ChatMessage, SkillContext from council.skills import SkillBase + from .wikipedia_client import WikipediaClient diff --git a/council/utils/code_parser.py b/council/utils/code_parser.py index ef18bc74..08549dcd 100644 --- a/council/utils/code_parser.py +++ b/council/utils/code_parser.py @@ -1,11 +1,13 @@ from __future__ import annotations -from more_itertools import first, last + import re -from typing import Optional, List, Iterable +from typing import Iterable, List, Optional + +from more_itertools import first, last class CodeBlock: - def __init__(self, language: Optional[str], code: str): + def __init__(self, language: Optional[str], code: str) -> None: self._language = language self._code = code diff --git a/council/utils/data_object.py b/council/utils/data_object.py index 2e56513d..228b712d 100644 --- a/council/utils/data_object.py +++ b/council/utils/data_object.py @@ -1,14 +1,14 @@ from __future__ import annotations -from typing_extensions import Self import abc -from typing import Any, Dict, TypeVar, Generic, Type, Optional +from typing import Any, Dict, Generic, Optional, Type, TypeVar import yaml +from typing_extensions import Self class DataObjectMetadata: - def __init__(self, name: str, labels: Dict[str, Any], description: Optional[str] = None): + def __init__(self, name: str, labels: Dict[str, Any], description: Optional[str] = None) -> None: self.name = name self.description = description self.labels = labels @@ -56,7 +56,7 @@ def from_dict(cls, values: Dict[str, Any]) -> Self: class DataObject(Generic[T]): - def __init__(self, kind: str, version: str, metadata: DataObjectMetadata, spec: T): + def __init__(self, kind: str, version: str, metadata: DataObjectMetadata, spec: T) -> None: self.kind = kind self.version = version self.metadata = metadata @@ -82,7 +82,7 @@ def _from_dict(cls, inner: Type[T], values: Dict[str, Any]) -> Self: spec = inner.from_dict(values["spec"]) return cls(values["kind"], values["version"], metadata, spec) - def to_yaml(self, filename: str): + def to_yaml(self, filename: str) -> None: values = self.to_dict() with open(filename, "w", encoding="utf-8") as f: yaml.safe_dump(values, f, default_flow_style=False) diff --git a/council/utils/env.py b/council/utils/env.py index c939d8ee..bd5178be 100644 --- a/council/utils/env.py +++ b/council/utils/env.py @@ -1,5 +1,5 @@ import os -from typing import Optional, Type, Callable, TypeVar, Any +from typing import Any, Callable, Optional, Type, TypeVar from council.utils import Option @@ -9,7 +9,7 @@ class MissingEnvVariableException(Exception): Custom exception raised when a required environment variable is missing. """ - def __init__(self, name: str): + def __init__(self, name: str) -> None: """ Initializes an instance of MissingEnvVariableError. @@ -111,5 +111,5 @@ def _set(self, value: Optional[str]): if value is not None: os.environ[self.name] = value - def __str__(self): + def __str__(self) -> str: return f"Env var:`{self.name}` value:{self.value} (previous value: {self.previous_value})" diff --git a/council/utils/option.py b/council/utils/option.py index 1db81e56..076996a7 100644 --- a/council/utils/option.py +++ b/council/utils/option.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TypeVar, Generic, Optional, Callable +from typing import Callable, Generic, Optional, TypeVar T = TypeVar("T") R = TypeVar("R") @@ -15,9 +15,7 @@ class Option(Generic[T]): Convenient class to manage optional values. """ - _some: Optional[T] - - def __init__(self, some: Optional[T]): + def __init__(self, some: Optional[T]) -> None: """ Initialize a new instance diff --git a/council/utils/parameter.py b/council/utils/parameter.py index e52ff8b8..282878c2 100644 --- a/council/utils/parameter.py +++ b/council/utils/parameter.py @@ -1,8 +1,8 @@ from __future__ import annotations -from typing import Callable, TypeVar, Optional, Generic, Any, Union +from typing import Any, Callable, Generic, Optional, TypeVar, Union -from council.utils import Option, read_env_int, read_env_float, read_env_str +from council.utils import Option, read_env_float, read_env_int, read_env_str T = TypeVar("T") Validator = Callable[[T], None] @@ -10,7 +10,7 @@ def greater_than_validator(value: int) -> Validator: - def validator(x: int): + def validator(x: int) -> None: if x <= value: raise ValueError(f"must be greater than {value}") @@ -18,14 +18,14 @@ def validator(x: int): def prefix_validator(value: str) -> Validator: - def validator(x: str): + def validator(x: str) -> None: if not x.startswith(value): raise ValueError(f"must start with `{value}`") return validator -def not_empty_validator(x: str): +def not_empty_validator(x: str) -> None: if len(x.strip()) == 0: raise ValueError("must not be empty") @@ -49,7 +49,7 @@ class ParameterValueException(Exception): Custom exception raised when a required environment variable is missing. """ - def __init__(self, name: str, value: Any, message: Exception): + def __init__(self, name: str, value: Any, message: Exception) -> None: """ Initializes an instance of ParameterValueException. @@ -73,7 +73,7 @@ def __init__( value: OptionalOrUndefined[T] = _undefined, default: OptionalOrUndefined[T] = _undefined, validator: Optional[Validator] = None, - ): + ) -> None: self._name = name self._required = required self._validator: Validator = validator if validator is not None else lambda x: None diff --git a/council/utils/result.py b/council/utils/result.py index 29767e59..f4d2d33d 100644 --- a/council/utils/result.py +++ b/council/utils/result.py @@ -1,11 +1,11 @@ -from typing import Union, Optional, Any +from typing import Any, Optional, Union class Ok: - def __init__(self, value: Optional[Any] = None): + def __init__(self, value: Optional[Any] = None) -> None: self.value = value - def __repr__(self): + def __repr__(self) -> str: return f"Ok({self.value})" @staticmethod @@ -18,10 +18,10 @@ def is_err() -> bool: class Err: - def __init__(self, error): + def __init__(self, error) -> None: self.error = error - def __repr__(self): + def __repr__(self) -> str: return f"Err({self.error})" @staticmethod diff --git a/dev-requirements.txt b/dev-requirements.txt index edb8e1a9..0bb8b027 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,19 @@ +# requirements for developers and CI +-r requirements.txt + hatch==1.7.0 +# Types +types-PyYAML==6.0.12.20240311 +google-api-python-client-stubs==1.26.0 +types-beautifulsoup4~=4.12.0.7 + # Lint -black==24.2.0 -mypy==1.9.0 -ruff==0.1.4 +black==24.4.2 +mypy==1.10.0 +ruff==0.4.6 +pylint==3.2.2 +isort==5.13.2 # Test ipykernel==6.26.0 diff --git a/pyproject.toml b/pyproject.toml index 5bcf487c..80df2cc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,3 +45,11 @@ line-length = 120 exclude = "(?x)(venv|docs)" mypy_path= "./stubs" explicit_package_bases = "true" + +[tool.isort] +profile = "black" +line_length = 120 +py_version=39 +src_paths = ["src"] +skip = ["__init__.py"] +skip_glob = ["**/venv/*", "**/docs/*", "**/stubs/*", "**/tests/*"] diff --git a/requirements.txt b/requirements.txt index 173b7613..6cce1e0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,24 @@ -httpx==0.25.1 +httpx~=0.25.1 requests~=2.31.0 Jinja2~=3.1.2 -python-dotenv==1.0.0 -more-itertools==10.1.0 -progressbar==2.5 -tiktoken==0.5.1 +python-dotenv~=1.0.0 +more-itertools~=10.1.0 +progressbar~=2.5 +tiktoken~=0.5.1 # LLMs -anthropic>=0.20.0 +anthropic~=0.20.0 # Skills ## Google -google-api-python-client==2.106.0 +google-api-python-client>=2.106.0 GoogleNews>=1.6.10 -google-api-python-client-stubs==1.18.0 + +## Wikipedia pymediawiki~=0.7.3 beautifulsoup4~=4.12.2 -types-beautifulsoup4~=4.12.0.7 - toml~=0.10.2 -nbformat==5.9.2 -nbconvert==7.11.0 -PyYAML==6.0.1 -types-PyYAML==6.0.12.12 \ No newline at end of file +nbformat~=5.9.2 +nbconvert~=7.11.0 +PyYAML~=6.0.1 diff --git a/tests/integration/skills/test_google_skills.py b/tests/integration/skills/test_google_skills.py index c856e076..25b4f152 100644 --- a/tests/integration/skills/test_google_skills.py +++ b/tests/integration/skills/test_google_skills.py @@ -41,7 +41,7 @@ def test_gnews_skill(self): for d in json_loads: self.assertGreater(len(d["title"]), 0) self.assertGreater(len(d["url"]), 0) - self.assertEqual(len(d["snippet"]), 0) + self.assertGreater(len(d["snippet"]), 0) self.assertTrue(is_within_period(d["date"], 15)) def test_gnews_skill_range(self): @@ -64,7 +64,7 @@ def test_gnews_skill_range(self): for d in json_loads: self.assertGreater(len(d["title"]), 0) self.assertGreater(len(d["url"]), 0) - self.assertEqual(len(d["snippet"]), 0) + self.assertGreater(len(d["snippet"]), 0) def test_gsearch_skill(self): context = ChainContext.from_user_message("USD", budget=Budget(duration=10))